feat(web): drop material gloss, full punktfunk theme for Scalar, center mobile tabs
ci / web (push) Successful in 38s
apple / swift (push) Successful in 54s
android / android (push) Successful in 3m58s
ci / rust (push) Successful in 4m30s
ci / docs-site (push) Successful in 54s
deb / build-publish (push) Successful in 2m18s
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 18s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 42s
ci / bench (push) Successful in 4m44s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m13s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m16s
docker / deploy-docs (push) Successful in 19s

- console: remove @unom/ui's specular "material" gloss (drop UnomProviders +
  the material.css import) so components render flat like the marketing site;
  the violet brand + Geist stay.
- mobile bottom tab bar: center the labels (w-full text-center, leading-tight)
  and even out the per-tab layout.
- docs /api: roll the punktfunk dark-violet palette across the whole Scalar
  reference (surfaces/text/sidebar/links/buttons/method colours via the full
  --scalar-* token set), locked to dark (hideDarkModeToggle).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 12:19:51 +00:00
parent f3555d5eb5
commit 527c2f677e
6 changed files with 73 additions and 43 deletions
+63 -5
View File
@@ -17,19 +17,75 @@ export const Route = createFileRoute('/api/')({
}), }),
}) })
// Brand the Scalar reference to the punktfunk violet + Geist, in both light and // The full punktfunk theme rolled out onto Scalar — the same dark-violet
// dark. Scalar ignores unknown custom-property names, so this is forward-safe. // product chrome as the management console (bg #141019 / cards #1c1530, the
// violet lens brand, Geist). Scalar is locked to dark mode below; the palette
// maps every Scalar token (surfaces, text, sidebar, links, buttons, method
// colours). Scalar ignores unknown custom-property names, so this is forward-safe.
const SCALAR_CSS = ` const SCALAR_CSS = `
:root { .light-mode,
--scalar-color-accent: #6c5bf3; .dark-mode {
--scalar-font: 'Geist Variable', ui-sans-serif, system-ui, sans-serif; --scalar-font: 'Geist Variable', ui-sans-serif, system-ui, sans-serif;
--scalar-font-code: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
--scalar-radius: 0.5rem;
--scalar-radius-lg: 0.75rem;
--scalar-radius-xl: 0.875rem;
} }
.dark-mode { .dark-mode {
--scalar-color-accent: #a79ff8; /* Surfaces — the violet-tinted app-icon chrome. */
--scalar-background-1: #141019; --scalar-background-1: #141019;
--scalar-background-2: #1c1530; --scalar-background-2: #1c1530;
--scalar-background-3: #221a36; --scalar-background-3: #221a36;
--scalar-background-accent: #6c5bf32e;
--scalar-border-color: #2a2148; --scalar-border-color: #2a2148;
/* Text. */
--scalar-color-1: #f4f2fb;
--scalar-color-2: #b7b1c9;
--scalar-color-3: #8a85a0;
--scalar-color-accent: #a79ff8;
/* Links. */
--scalar-link-color: #a79ff8;
--scalar-link-color-hover: #c8c0fb;
/* Primary action button (brand violet). */
--scalar-button-1: #6c5bf3;
--scalar-button-1-color: #ffffff;
--scalar-button-1-hover: #5d4ee0;
/* Sidebar. */
--scalar-sidebar-background-1: #17121f;
--scalar-sidebar-color-1: #e9e6f4;
--scalar-sidebar-color-2: #9a94ad;
--scalar-sidebar-color-active: #c8c0fb;
--scalar-sidebar-item-hover-background: #6c5bf31f;
--scalar-sidebar-item-hover-color: #f4f2fb;
--scalar-sidebar-item-active-background: #6c5bf333;
--scalar-sidebar-border-color: #241c3d;
--scalar-sidebar-search-background: #1c1530;
--scalar-sidebar-search-border-color: #2a2148;
--scalar-sidebar-search-color: #9a94ad;
--scalar-sidebar-indent-border: #2a2148;
--scalar-sidebar-indent-border-active: #6c5bf3;
--scalar-sidebar-indent-border-hover: #463a78;
/* Header (if shown). */
--scalar-header-background-1: #141019;
--scalar-header-color-1: #f4f2fb;
--scalar-header-border-color: #2a2148;
/* Scrollbar. */
--scalar-scrollbar-color: #2a2148;
--scalar-scrollbar-color-active: #463a78;
/* HTTP method / status colours — kept distinct, tuned to read on dark. */
--scalar-color-green: #4ade80;
--scalar-color-red: #f87171;
--scalar-color-yellow: #fbbf24;
--scalar-color-blue: #60a5fa;
--scalar-color-orange: #fb923c;
--scalar-color-purple: #a79ff8;
} }
` `
@@ -75,6 +131,8 @@ function ApiReference() {
configuration={{ configuration={{
url: '/openapi.json', url: '/openapi.json',
darkMode: true, darkMode: true,
// Lock to the punktfunk dark-violet theme — no light-mode escape hatch.
hideDarkModeToggle: true,
metaData: { title: 'punktfunk Management API' }, metaData: { title: 'punktfunk Management API' },
hideDownloadButton: false, hideDownloadButton: false,
customCss: SCALAR_CSS, customCss: SCALAR_CSS,
+1 -2
View File
@@ -7,7 +7,7 @@ clients, the pairing-PIN flow, and session controls.
Stack: **TanStack Start** (full SSR) on **Bun** via **Nitro v2** (`bun` preset) · **React Stack: **TanStack Start** (full SSR) on **Bun** via **Nitro v2** (`bun` preset) · **React
Query** through **orval** codegen from the OpenAPI spec · **[`@unom/ui`](https://git.unom.io/unom/ui)** Query** through **orval** codegen from the OpenAPI spec · **[`@unom/ui`](https://git.unom.io/unom/ui)**
— the shared punktfunk/unom design system the marketing site + docs are built on (Tailwind v4, — the shared punktfunk/unom design system the marketing site + docs are built on (Tailwind v4,
animated components + specular "material" gloss, the violet brand on dark chrome) · animated components on the violet brand over dark chrome) ·
**Paraglide** i18n (en/de). Package manager + runtime: **Bun**. **Paraglide** i18n (en/de). Package manager + runtime: **Bun**.
The `@unom` registry mapping lives in [`.npmrc`](.npmrc); the auth token comes from The `@unom` registry mapping lives in [`.npmrc`](.npmrc); the auth token comes from
@@ -98,7 +98,6 @@ src/
components/ components/
app-shell.tsx sidebar nav (brand lens + wordmark) + language switcher app-shell.tsx sidebar nav (brand lens + wordmark) + language switcher
brand-mark/wordmark/logo.tsx punktfunk lens mark + wordmark (shared with the site/docs) brand-mark/wordmark/logo.tsx punktfunk lens mark + wordmark (shared with the site/docs)
unom-providers.tsx @unom/ui Material provider (specular gloss; no sounds, like the site)
ui/ @unom/ui-backed primitives (button, input, label, card; badge/table/skeleton) ui/ @unom/ui-backed primitives (button, input, label, card; badge/table/skeleton)
query-state.tsx loading/error wrapper (incl. 401 → "set a token") query-state.tsx loading/error wrapper (incl. 401 → "set a token")
api/ api/
+3 -3
View File
@@ -76,11 +76,11 @@ export function AppShell({ children }: { children: ReactNode }) {
key={to} key={to}
to={to} to={to}
activeOptions={{ exact: to === '/' }} activeOptions={{ exact: to === '/' }}
className="flex flex-1 flex-col items-center gap-1 py-2 text-[10px] text-muted-foreground transition-colors" className="flex flex-1 flex-col items-center justify-center gap-1 px-0.5 py-2 text-muted-foreground transition-colors"
activeProps={{ className: 'text-[var(--brand-light)]' }} activeProps={{ className: 'text-[var(--brand-light)]' }}
> >
<Icon className="size-5" /> <Icon className="size-5 shrink-0" />
<span className="leading-none">{label()}</span> <span className="w-full text-center text-[10px] leading-tight">{label()}</span>
</Link> </Link>
))} ))}
</nav> </nav>
-21
View File
@@ -1,21 +0,0 @@
import type { ReactNode } from 'react'
import { MaterialProvider } from '@unom/ui/material'
// Turn on @unom/ui's specular "material" gloss for the whole console — the same
// design system the punktfunk marketing site is built on. SSR-safe (material
// gates its effects on a mounted flag). No SoundProvider: like the marketing
// site, the console stays silent (@unom/ui's useSound no-ops without a provider).
const MATERIAL = {
button: { enabled: true },
card: { enabled: true },
dialog: { enabled: true },
tabs: { enabled: true },
select: { enabled: true },
input: { enabled: true },
checkbox: { enabled: true },
toast: { enabled: true },
} as const
export function UnomProviders({ children }: { children: ReactNode }) {
return <MaterialProvider theme={MATERIAL}>{children}</MaterialProvider>
}
+6 -9
View File
@@ -9,7 +9,6 @@ import {
import type { QueryClient } from '@tanstack/react-query' import type { QueryClient } from '@tanstack/react-query'
import '@fontsource-variable/geist' import '@fontsource-variable/geist'
import { AppShell } from '@/components/app-shell' import { AppShell } from '@/components/app-shell'
import { UnomProviders } from '@/components/unom-providers'
import appCss from '@/styles.css?url' import appCss from '@/styles.css?url'
export interface RouterContext { export interface RouterContext {
@@ -38,15 +37,13 @@ function RootComponent() {
<HeadContent /> <HeadContent />
</head> </head>
<body className="min-h-screen"> <body className="min-h-screen">
<UnomProviders> {isLogin ? (
{isLogin ? ( <Outlet />
) : (
<AppShell>
<Outlet /> <Outlet />
) : ( </AppShell>
<AppShell> )}
<Outlet />
</AppShell>
)}
</UnomProviders>
<Scripts /> <Scripts />
</body> </body>
</html> </html>
-3
View File
@@ -1,9 +1,6 @@
@import 'tailwindcss'; @import 'tailwindcss';
@import 'tw-animate-css'; @import 'tw-animate-css';
@import './timing-functions.css'; @import './timing-functions.css';
/* @unom/ui's specular "material" gloss layer (.material / .material-fx). Colours
derive from --neutral / --neutral-accent via the --mat-* knobs it ships. */
@import '@unom/ui/styles/material.css';
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));