feat(theme): SRC theme v1.1.0 — MD3 components (shape scale, buttons, cards, dialogs, state layers)

This commit is contained in:
Bernd Rodler
2026-08-03 19:13:07 +02:00
parent 88670d4bcf
commit f0e63de09b
3 changed files with 234 additions and 4 deletions
+228 -2
View File
@@ -1014,6 +1014,11 @@ body[data-theme-skin="builtin-vnclagoon"] .rounded-2xl.border.bg-background\\/80
// near-black stone-grey neutral. Dark variant brightens the red (#EF4444) on a
// warm near-black. Info stays blue so it never collides with the red accent.
const srcCSS = `
@font-face { font-family: 'DM Sans'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/dmsans-400.woff2') format('woff2'); }
@font-face { font-family: 'DM Sans'; font-style: normal; font-weight: 500; font-display: swap; src: url('/fonts/dmsans-500.woff2') format('woff2'); }
@font-face { font-family: 'DM Sans'; font-style: normal; font-weight: 700; font-display: swap; src: url('/fonts/dmsans-700.woff2') format('woff2'); }
@font-face { font-family: 'Syne'; font-style: normal; font-weight: 700; font-display: swap; src: url('/fonts/syne-700.woff2') format('woff2'); }
@font-face { font-family: 'Syne'; font-style: normal; font-weight: 800; font-display: swap; src: url('/fonts/syne-800.woff2') format('woff2'); }
:root {
--color-border: #e7e5e4;
--color-input: #e7e5e4;
@@ -1095,6 +1100,225 @@ const srcCSS = `
--color-chart-5: #a78bfa;
}`;
// MD3 component overrides for the SRC theme — shape scale, filled buttons,
// text fields, cards, dialogs, state layers, switches, login card treatment.
// All scoped under the skin body attribute so they detach cleanly on switch-off.
const srcSkin = `
body[data-theme-skin="builtin-src"] {
font-family: "DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
}
body[data-theme-skin="builtin-src"] h1,
body[data-theme-skin="builtin-src"] h2,
body[data-theme-skin="builtin-src"] h3 {
font-family: "Syne", "DM Sans", sans-serif;
font-weight: 700;
letter-spacing: -0.01em;
}
/* ── MD3 Shape scale ─────────────────────────────────────────── */
/* Remap Tailwind rounded-* to M3 shape tokens. rounded-full (pill */
/* avatars, badges, toggles) is intentionally left untouched. */
body[data-theme-skin="builtin-src"] .rounded-sm { border-radius: 4px !important; }
body[data-theme-skin="builtin-src"] .rounded { border-radius: 4px !important; }
body[data-theme-skin="builtin-src"] .rounded-md { border-radius: 8px !important; }
body[data-theme-skin="builtin-src"] .rounded-lg { border-radius: 12px !important; }
body[data-theme-skin="builtin-src"] .rounded-xl { border-radius: 16px !important; }
body[data-theme-skin="builtin-src"] .rounded-2xl { border-radius: 28px !important; }
body[data-theme-skin="builtin-src"] .rounded-3xl { border-radius: 28px !important; }
/* ── MD3 Buttons: full shape (20 dp) ─────────────────────────── */
/* All button variants (filled, tonal, outlined, text) use 20 dp */
/* corners per M3. Circle icon buttons (.rounded-full) are skipped; */
/* switches ([role="switch"]) are handled separately. */
body[data-theme-skin="builtin-src"] button:not(.rounded-full):not([role="switch"]) {
border-radius: 20px !important;
}
/* MD3 filled button — primary surface, M3 label-large, state layers */
body[data-theme-skin="builtin-src"] .bg-primary.text-primary-foreground {
border-radius: 20px !important;
padding-inline: 24px !important;
min-height: 40px !important;
font-weight: 500 !important;
letter-spacing: 0.0063em !important;
border: none !important;
box-shadow: none !important;
transition: box-shadow 200ms ease, filter 200ms ease;
}
/* hover: M3 elevation 1 + 8 % on-primary state layer */
body[data-theme-skin="builtin-src"] .bg-primary.text-primary-foreground:hover {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.30),
0 2px 6px 2px rgba(0, 0, 0, 0.15) !important;
filter: brightness(1.06);
}
/* focus: +12 % tint + M3 focus ring */
body[data-theme-skin="builtin-src"] .bg-primary.text-primary-foreground:focus-visible {
filter: brightness(1.10) !important;
outline: 3px solid var(--color-ring) !important;
outline-offset: 2px !important;
}
/* pressed: +12 % darker, no shadow */
body[data-theme-skin="builtin-src"] .bg-primary.text-primary-foreground:active {
box-shadow: none !important;
filter: brightness(0.94) !important;
}
/* ── MD3 Text fields: outlined style, extra-small (4 dp) ─────── */
body[data-theme-skin="builtin-src"] input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
body[data-theme-skin="builtin-src"] textarea,
body[data-theme-skin="builtin-src"] select {
border-radius: 4px !important;
transition: outline 150ms ease;
}
body[data-theme-skin="builtin-src"] input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):focus-visible,
body[data-theme-skin="builtin-src"] textarea:focus-visible,
body[data-theme-skin="builtin-src"] select:focus-visible {
outline: 2px solid var(--color-primary) !important;
outline-offset: -2px !important;
}
/* ── MD3 Cards: elevated (level 1), medium shape (12 dp) ─────── */
body[data-theme-skin="builtin-src"] .bg-card {
border-radius: 12px !important;
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.10),
0 1px 3px 1px rgba(0, 0, 0, 0.06) !important;
}
.dark body[data-theme-skin="builtin-src"] .bg-card {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.35),
0 1px 3px 1px rgba(0, 0, 0, 0.20) !important;
}
/* ── MD3 Menus / popovers / listboxes: extra-small (4 dp) ───── */
body[data-theme-skin="builtin-src"] .bg-popover,
body[data-theme-skin="builtin-src"] [role="listbox"] {
border-radius: 4px !important;
border: none !important;
box-shadow:
0 2px 6px 2px rgba(0, 0, 0, 0.15),
0 1px 2px rgba(0, 0, 0, 0.30) !important;
}
.dark body[data-theme-skin="builtin-src"] .bg-popover,
.dark body[data-theme-skin="builtin-src"] [role="listbox"] {
box-shadow:
0 2px 8px 2px rgba(0, 0, 0, 0.50),
0 1px 2px rgba(0, 0, 0, 0.60) !important;
}
/* ── MD3 Dialogs: extra-large shape (28 dp) ──────────────────── */
body[data-theme-skin="builtin-src"] [role="dialog"] {
border-radius: 28px !important;
border: none !important;
box-shadow:
0 6px 10px 4px rgba(0, 0, 0, 0.15),
0 2px 3px rgba(0, 0, 0, 0.30) !important;
}
.dark body[data-theme-skin="builtin-src"] [role="dialog"] {
box-shadow:
0 6px 10px 4px rgba(0, 0, 0, 0.50),
0 2px 3px rgba(0, 0, 0, 0.60) !important;
}
/* ── MD3 Menu items: 8 % on-surface state layer on hover ─────── */
body[data-theme-skin="builtin-src"] [role="menu"] [role="menuitem"]:hover,
body[data-theme-skin="builtin-src"] [role="menu"] [role="menuitem"]:focus,
body[data-theme-skin="builtin-src"] [role="menu"] [role="menuitem"]:focus-visible {
background-color: color-mix(in srgb, var(--color-foreground) 8%, transparent) !important;
color: var(--color-foreground) !important;
}
.dark body[data-theme-skin="builtin-src"] [role="menu"] [role="menuitem"]:hover,
.dark body[data-theme-skin="builtin-src"] [role="menu"] [role="menuitem"]:focus {
background-color: color-mix(in srgb, var(--color-foreground) 10%, transparent) !important;
}
/* ── MD3 Switch ──────────────────────────────────────────────── */
/* M3 switch: unselected = outline + icon; selected = primary fill */
body[data-theme-skin="builtin-src"] [role="switch"] {
background-color: var(--color-input) !important;
border: 2px solid var(--color-muted-foreground) !important;
transition: background-color 150ms ease, border-color 150ms ease;
}
body[data-theme-skin="builtin-src"] [role="switch"] > span {
background-color: var(--color-muted-foreground) !important;
}
body[data-theme-skin="builtin-src"] [role="switch"][aria-checked="true"] {
background-color: var(--color-primary) !important;
border-color: var(--color-primary) !important;
}
body[data-theme-skin="builtin-src"] [role="switch"][aria-checked="true"] > span {
background-color: var(--color-primary-foreground) !important;
}
.dark body[data-theme-skin="builtin-src"] [role="switch"] {
background-color: #3a2524 !important;
border-color: var(--color-muted-foreground) !important;
}
.dark body[data-theme-skin="builtin-src"] [role="switch"][aria-checked="true"] {
background-color: var(--color-primary) !important;
border-color: var(--color-primary) !important;
}
.dark body[data-theme-skin="builtin-src"] [role="switch"][aria-checked="true"] > span {
background-color: #1c1917 !important;
}
/* ── Selected folder: M3 active-indicator treatment ─────────── */
/* M3 uses a pill-shaped tonal container for the active nav item. */
/* Bulwark's left-border accent becomes a 3 dp primary accent line */
/* + secondary-container (--color-accent) fill + gentle corner. */
body[data-theme-skin="builtin-src"] .bg-secondary.border-r .border-l-2.border-primary {
border-left-width: 3px !important;
border-left-color: var(--color-primary) !important;
background-color: var(--color-accent) !important;
border-radius: 0 12px 12px 0 !important;
}
/* ── Login card: MD3 extra-large + SRC red top accent strip ───── */
body[data-theme-skin="builtin-src"] .rounded-2xl.border.bg-background\\/80.backdrop-blur-sm {
position: relative;
background-color: var(--color-card) !important;
border-color: rgba(213, 43, 30, 0.14) !important;
border-radius: 28px !important;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.08),
0 8px 32px rgba(0, 0, 0, 0.06) !important;
overflow: hidden;
}
body[data-theme-skin="builtin-src"] .rounded-2xl.border.bg-background\\/80.backdrop-blur-sm::before {
content: "";
position: absolute;
left: 0; right: 0; top: 0;
height: 3px;
background: linear-gradient(90deg, transparent, #d52b1e 30%, #d52b1e 70%, transparent);
pointer-events: none;
}
/* Login page background: faint SRC red ambient wash */
body[data-theme-skin="builtin-src"] .min-h-screen.bg-gradient-to-br {
background-color: #f9f7f7 !important;
background-image: radial-gradient(56rem 38rem at 50% -12%, rgba(213, 43, 30, 0.05), transparent 60%) !important;
}
.dark body[data-theme-skin="builtin-src"] .rounded-2xl.border.bg-background\\/80.backdrop-blur-sm {
border-color: rgba(239, 68, 68, 0.18) !important;
box-shadow:
0 4px 16px rgba(0, 0, 0, 0.45),
0 0 40px -20px rgba(239, 68, 68, 0.18) !important;
}
.dark body[data-theme-skin="builtin-src"] .rounded-2xl.border.bg-background\\/80.backdrop-blur-sm::before {
background: linear-gradient(90deg, transparent, #ef4444 30%, #ef4444 70%, transparent);
}
.dark body[data-theme-skin="builtin-src"] .min-h-screen.bg-gradient-to-br {
background-color: var(--color-background) !important;
background-image: radial-gradient(56rem 38rem at 50% -12%, rgba(239, 68, 68, 0.07), transparent 60%) !important;
}
/* ── Single-surface panes (M3 has no gradient empty states) ───── */
body[data-theme-skin="builtin-src"] .bg-gradient-to-br.from-muted\\/30.to-muted\\/50 {
background: var(--color-background) !important;
}
body[data-theme-skin="builtin-src"] .bg-muted\\/30 {
background-color: var(--color-background) !important;
}`;
export const BUILTIN_THEMES: InstalledTheme[] = [
{
id: 'builtin-vnclagoon',
@@ -1114,13 +1338,15 @@ export const BUILTIN_THEMES: InstalledTheme[] = [
{
id: 'builtin-src',
name: 'SRC',
version: '1.0.0',
version: '1.1.0',
author: 'VNC',
description: 'SRC Advisory brand theme — Swiss red on white, light-first',
description: 'SRC Advisory brand theme — Swiss red on white, MD3 components, light-first',
css: srcCSS,
skin: srcSkin,
logoLightUrl: '/branding/src-logo.svg',
logoDarkUrl: '/branding/src-logo.svg',
variants: ['light', 'dark'],
typography: { fontSans: '"DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif' },
enabled: true,
builtIn: true,
},
+3 -2
View File
@@ -27,9 +27,10 @@ function isValidContext(payload: unknown): payload is StalwartAuthContext {
&& typeof candidate.authHeader === 'string';
}
const AUTH_CONTEXT_MAX_AGE = 6 * 60 * 60; // 6 hours
function getSessionCookieOptions() {
const { maxAge: _maxAge, ...cookieOptions } = getCookieOptions();
return cookieOptions;
return { ...getCookieOptions(), maxAge: AUTH_CONTEXT_MAX_AGE };
}
export function readStalwartAuthContextFromStore(