Merge remote-tracking branch 'origin/main'
apple / swift (push) Successful in 54s
ci / rust (push) Successful in 1m39s
ci / web (push) Successful in 28s
windows-host / package (push) Successful in 2m25s
ci / docs-site (push) Successful in 40s
android / android (push) Successful in 3m18s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m18s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m15s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m0s
deb / build-publish (push) Successful in 3m8s
decky / build-publish (push) Successful in 13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
ci / bench (push) Successful in 4m56s
flatpak / build-publish (push) Successful in 4m46s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m13s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 7m56s

This commit is contained in:
2026-06-20 14:50:09 +00:00
11 changed files with 1030 additions and 20 deletions
+764 -11
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -10,8 +10,11 @@
"lint": "tsc --noEmit"
},
"dependencies": {
"@fontsource-variable/geist": "^5.2.9",
"@tanstack/react-router": "^1.121.0",
"@tanstack/react-start": "^1.121.0",
"@unom/style": "^0.4.4",
"@unom/ui": "^0.8.16",
"fumadocs-core": "^16.10.1",
"fumadocs-ui": "^16.10.1",
"react": "^19.0.0",
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1000" height="1000" viewBox="0 0 1000 1000" version="1.1"
xmlns="http://www.w3.org/2000/svg"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<!-- punktfunk brand mark: deep-purple lens of two overlapping circles on the
dark icon background. Flattened from clients/apple punktfunk_Logo.icon. -->
<rect x="0" y="0" width="1000" height="1000" rx="180" ry="180" fill="#1c1530"/>
<path d="M403.037,791.672c107.586,0 194.41,-86.824 194.41,-194.41c0,-107.586 -86.824,-194.41 -194.41,-194.41c-107.586,0 -194.41,86.824 -194.41,194.41c0,107.586 86.824,194.41 194.41,194.41Z" fill="#a79ff8"/>
<path d="M735.276,540.321c76.075,-76.075 76.075,-198.862 0,-274.937c-76.075,-76.075 -198.862,-76.075 -274.937,0c-76.075,76.075 -76.075,198.862 0,274.937c76.075,76.075 198.862,76.075 274.937,0Z" fill="#6c5bf3"/>
<path d="M647.84,590.737c-64.853,17.403 -136.871,0.597 -187.885,-50.416c-51.013,-51.013 -67.819,-123.032 -50.416,-187.885c64.853,-17.403 136.871,-0.597 187.885,50.416c51.013,51.013 67.819,123.032 50.416,187.885Z" fill="#d2c9fb"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+29
View File
@@ -0,0 +1,29 @@
// punktfunk brand mark: two overlapping circles forming a lens — the violet
// brand identity. Copied verbatim from the marketing site (flattened from the
// clients/apple punktfunk_Logo.icon). Back-to-front: large light-violet circle,
// deep-violet circle, light highlight where they overlap.
export default function BrandMark({ className }: { className?: string }) {
return (
<svg
aria-label="punktfunk"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000"
className={className}
>
<title>punktfunk</title>
<path
d="M403.037,791.672c107.586,0 194.41,-86.824 194.41,-194.41c0,-107.586 -86.824,-194.41 -194.41,-194.41c-107.586,0 -194.41,86.824 -194.41,194.41c0,107.586 86.824,194.41 194.41,194.41Z"
fill="#a79ff8"
/>
<path
d="M735.276,540.321c76.075,-76.075 76.075,-198.862 0,-274.937c-76.075,-76.075 -198.862,-76.075 -274.937,0c-76.075,76.075 -76.075,198.862 0,274.937c76.075,76.075 198.862,76.075 274.937,0Z"
fill="#6c5bf3"
/>
<path
d="M647.84,590.737c-64.853,17.403 -136.871,0.597 -187.885,-50.416c-51.013,-51.013 -67.819,-123.032 -50.416,-187.885c64.853,-17.403 136.871,-0.597 187.885,50.416c51.013,51.013 67.819,123.032 50.416,187.885Z"
fill="#d2c9fb"
/>
</svg>
)
}
+51
View File
@@ -0,0 +1,51 @@
import { getRouteApi } from '@tanstack/react-router'
import type { NavigationLink, NavigationSection } from '@/lib/cms'
const rootApi = getRouteApi('__root__')
// The docs share the marketing site's footer (same CMS global). Root-relative
// links target the website, so resolve them against its origin — the docs don't
// host /legal/* etc. themselves. Mirrors the website Footer, themed for docs.
const SITE_URL = 'https://punktfunk.unom.io'
const resolve = (to?: string | null) =>
to ? (to.startsWith('/') ? `${SITE_URL}${to}` : to) : '#'
export default function Footer() {
const { footer } = rootApi.useLoaderData()
const sections: NavigationSection[] = footer?.sections ?? []
const tagline = footer?.tagline?.trim()
if (!sections.length && !tagline) return null
return (
<footer className="border-t border-fd-border bg-fd-card">
<div className="mx-auto flex w-full max-w-6xl flex-row flex-wrap gap-12 px-4 py-12 sm:px-6">
{sections.map((group, gi) => (
<div key={group.id ?? gi}>
{group.title && (
<h3 className="mb-2 text-sm font-semibold text-fd-foreground">
{group.title}
</h3>
)}
<div className="flex flex-col gap-1">
{(group.entries ?? []).map((item: NavigationLink, i) => (
<a
key={item.id ?? `${item.to}-${i}`}
href={resolve(item.to)}
className="text-sm text-fd-muted-foreground transition-colors hover:text-fd-foreground"
>
{item.label}
</a>
))}
</div>
</div>
))}
{tagline && (
<p className="ml-auto self-end text-sm text-fd-muted-foreground">
{tagline}
</p>
)}
</div>
</footer>
)
}
+23
View File
@@ -0,0 +1,23 @@
// The punktfunk "funk" wordmark — vectorised from
// Export/Punktfunk_Logo-Text_No-Border_Dark.svg (the white export background is
// dropped). Inline SVG using currentColor so it recolours per theme: the
// deep-violet brand on light, the light-violet lens highlight on dark (matching
// the marketing site). Size via height (e.g. `h-4`); width follows the viewBox.
export default function Wordmark({ className = '' }: { className?: string }) {
return (
<svg
role="img"
aria-label="punktfunk"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 579 136"
fill="currentColor"
className={`w-auto text-[var(--pf-brand)] dark:text-[var(--pf-highlight)] ${className}`}
>
<title>punktfunk</title>
<path d="M16.782,16.051l0,102.687l31.253,0l0,-35.563l73.436,0l0,-23.555l-73.436,0l0,-19.398l77.285,0l0,-24.171l-108.537,0Z" />
<path d="M131.785,16.051l0,47.264c0.154,16.627 0.154,16.627 0.308,20.014c0.77,15.087 2.463,21.4 7.544,26.634c7.698,8.16 20.014,10.315 59.272,10.315c23.863,0 34.178,-0.616 43.415,-2.463c11.7,-2.463 19.552,-10.623 21.246,-22.323c0.924,-7.236 1.078,-8.929 1.54,-32.176l0,-47.264l-31.253,0l0,47.264c0,2.155 -0.154,7.082 -0.308,10.623c-0.462,9.699 -1.232,12.47 -3.695,15.087c-3.387,3.695 -9.853,4.619 -31.407,4.619c-26.634,0 -32.638,-1.693 -34.332,-9.853c-0.77,-4.157 -0.77,-4.311 -1.078,-20.476l0,-47.264l-31.253,0Z" />
<path d="M271.575,15.943l0,102.687l31.868,0l-0.77,-76.669l3.387,0l54.038,76.669l54.346,0l0,-102.687l-31.868,0l0.77,76.515l-3.233,0l-53.73,-76.515l-54.808,0Z" />
<path d="M420.91,15.943l0,102.687l31.253,0l0,-39.258l17.089,0l46.032,39.258l47.418,0l-64.353,-52.344l59.426,-50.959l-47.88,0l-40.644,37.873l-17.089,0l0,-37.257l-31.253,0Z" />
</svg>
)
}
+27
View File
@@ -0,0 +1,27 @@
// The docs reuse the punktfunk marketing site's footer — the same Payload CMS
// global on the shared unom CMS (cms.unom.io). It's a read-only GET, so a plain
// typed fetch rather than pulling in the Payload SDK + generated types.
const CMS_URL = 'https://cms.unom.io'
export interface NavigationLink {
id?: string | null
label?: string | null
to?: string | null
}
export interface NavigationSection {
id?: string | null
title?: string | null
entries?: NavigationLink[] | null
}
export interface Footer {
tagline?: string | null
sections?: NavigationSection[] | null
}
export async function findFooter(): Promise<Footer> {
const res = await fetch(`${CMS_URL}/api/globals/footer?locale=en&depth=1`)
if (!res.ok) throw new Error(`CMS footer request failed: ${res.status}`)
return res.json() as Promise<Footer>
}
+14 -1
View File
@@ -1,10 +1,23 @@
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'
import BrandMark from '@/components/BrandMark'
import Wordmark from '@/components/Wordmark'
// Shared chrome (nav title, links) for both the docs layout and the home layout.
// The lens mark + wordmark mirror the punktfunk marketing site's header.
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: 'punktfunk',
title: (
<>
<BrandMark className="size-6" />
<Wordmark className="h-4" />
</>
),
},
links: [
{ text: 'Docs', url: '/docs' },
{ text: 'Website', url: 'https://punktfunk.unom.io' },
{ text: 'Source code', url: 'https://git.unom.io/unom/punktfunk' },
],
}
}
+28 -2
View File
@@ -1,10 +1,30 @@
/// <reference types="vite/client" />
import { createRootRoute, HeadContent, Outlet, Scripts } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import * as React from 'react'
import { RootProvider } from 'fumadocs-ui/provider/tanstack'
import '@fontsource-variable/geist'
import Footer from '@/components/Footer'
import { type Footer as FooterData, findFooter } from '@/lib/cms'
import appCss from '@/styles/app.css?url'
// The footer is global and shared with the marketing site (one CMS global).
// Fetch it once at the root, server-side, falling back to null so a CMS hiccup
// never breaks the page.
const getFooter = createServerFn({ method: 'GET' }).handler(
async (): Promise<FooterData | null> => {
try {
return await findFooter()
} catch {
return null
}
},
)
export const Route = createRootRoute({
loader: async (): Promise<{ footer: FooterData | null }> => ({
footer: await getFooter(),
}),
head: () => ({
meta: [
{ charSet: 'utf-8' },
@@ -12,7 +32,10 @@ export const Route = createRootRoute({
{ name: 'color-scheme', content: 'dark light' },
{ title: 'punktfunk docs' },
],
links: [{ rel: 'stylesheet', href: appCss }],
links: [
{ rel: 'stylesheet', href: appCss },
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
],
}),
component: RootComponent,
})
@@ -32,7 +55,10 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<HeadContent />
</head>
<body className="flex flex-col min-h-screen">
<RootProvider>{children}</RootProvider>
<RootProvider>
{children}
<Footer />
</RootProvider>
<Scripts />
</body>
</html>
+7 -4
View File
@@ -1,5 +1,7 @@
import { createFileRoute, Link } from '@tanstack/react-router'
import { HomeLayout } from 'fumadocs-ui/layouts/home'
import BrandMark from '@/components/BrandMark'
import Wordmark from '@/components/Wordmark'
import { baseOptions } from '@/lib/layout.shared'
export const Route = createFileRoute('/')({ component: Home })
@@ -8,15 +10,16 @@ function Home() {
return (
<HomeLayout {...baseOptions()}>
<main className="flex flex-1 flex-col items-center justify-center gap-6 px-4 py-24 text-center">
<h1 className="text-4xl font-bold tracking-tight">punktfunk</h1>
<BrandMark className="size-20 drop-shadow-[0_8px_30px_rgba(108,91,243,0.45)]" />
<Wordmark className="h-12 md:h-14" />
<p className="max-w-xl text-fd-muted-foreground">
A ground-up low-latency desktop and game streaming stack, built Linux-first, with a
shared Rust protocol core and native clients per platform.
Linux-first, low-latency desktop and game streaming a shared Rust protocol
core with native clients per platform.
</p>
<Link
to="/docs/$"
params={{ _splat: '' }}
className="rounded-lg bg-fd-primary px-5 py-2.5 font-medium text-fd-primary-foreground"
className="rounded-lg bg-brand px-5 py-2.5 font-medium text-white transition-colors hover:bg-brand/90"
>
Read the docs
</Link>
+73 -2
View File
@@ -1,6 +1,77 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/purple.css';
@import 'fumadocs-ui/css/preset.css';
/* Pull Fumadocs UI's own classes into the Tailwind 4 scan so they aren't purged. */
/* Pull Fumadocs UI's own classes — and @unom/ui's compiled components — into
the Tailwind 4 scan so their utilities aren't purged. @unom/ui is the shared
design-token system the punktfunk marketing site also builds on. */
@source '../../node_modules/fumadocs-ui/dist/**/*.js';
@source '../../node_modules/@unom/ui/dist/**/*.{js,mjs}';
/* ── punktfunk brand ────────────────────────────────────────────────────────
The brand colour is the violet lens mark. (The marketing site's blue is just
a page background, not the brand.) These values feed both @unom/ui's semantic
token contract (--brand/--primary/--accent/--highlight) and the Fumadocs
--color-fd-* chrome, so the docs carry the same identity in light and dark. */
:root {
--pf-brand: #6c5bf3; /* deep violet — primary */
--pf-brand-light: #a79ff8; /* light violet */
--pf-highlight: #d2c9fb; /* lens highlight */
/* @unom/ui token contract. */
--brand: var(--pf-brand);
--primary: var(--pf-brand);
--accent: var(--pf-brand);
--highlight: var(--pf-highlight);
--radius: 0.625rem;
/* Fumadocs accent → brand violet (light mode). */
--color-fd-primary: var(--pf-brand);
--color-fd-ring: var(--pf-brand);
}
.dark {
/* Lighter violet reads better against the dark surface. */
--primary: var(--pf-brand-light);
--accent: var(--pf-brand-light);
--color-fd-primary: var(--pf-brand-light);
--color-fd-ring: var(--pf-brand-light);
/* Tint Fumadocs' dark chrome toward the brand's violet-dark (the app-icon
surface #1c1530) instead of the default neutral-dark. */
--color-fd-background: #141019;
--color-fd-card: #1c1530;
--color-fd-popover: #1c1530;
--color-fd-secondary: #221a36;
--color-fd-muted: #1f1830;
--color-fd-accent: #241c3d;
--color-fd-border: #2a2148;
}
@theme {
/* Geist — the punktfunk brand typeface, same as the marketing site
(the @fontsource-variable/geist face is loaded in __root.tsx). */
--font-sans: 'Geist Variable', ui-sans-serif, system-ui, sans-serif;
--font-display: 'Geist Variable', ui-sans-serif, system-ui, sans-serif;
}
/* Expose the shared brand + surface tokens as Tailwind colours (bg-brand,
text-primary, bg-neutral…), mirroring @unom/ui's own @theme mapping so its
utilities resolve to the docs' current (light/dark) surfaces. */
@theme inline {
--color-brand: var(--brand);
--color-primary: var(--primary);
--color-accent: var(--accent);
--color-highlight: var(--highlight);
--color-main: var(--color-fd-foreground);
--color-secondary: var(--color-fd-muted-foreground);
--color-neutral: var(--color-fd-background);
--color-neutral-accent: var(--color-fd-secondary);
}
@layer base {
body {
font-family: var(--font-sans);
}
}