feat(theme): SRC theme v1.1.0 — MD3 components (shape scale, buttons, cards, dialogs, state layers)
This commit is contained in:
+228
-2
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -45,4 +45,7 @@ data dir (`/app/data/*`); Vercel serverless has a read-only filesystem → crash
|
||||
`vncmail.sandbox.vnc.de`, with 4 persistent volumes — see `deploy/k8s/`. A
|
||||
microfrontends integration was also added and reverted the same day._
|
||||
|
||||
| 2026-08-03 | `lib/stalwart/auth-context.ts` | give `jmap_stalwart_ctx` a 6-hour maxAge (was session-cookie → expired on tab close) | session survival across browser restarts |
|
||||
| 2026-08-03 | `lib/builtin-themes.ts` | add `srcSkin` (MD3 component overrides: shape scale, filled buttons, text fields, cards, dialogs, state layers, switches, login card); add @font-face + typography to `builtin-src`; bump to v1.1.0 | SRC theme: keep colors + fonts, apply MD3 design system |
|
||||
|
||||
_(append new rows as you diverge)_
|
||||
|
||||
Reference in New Issue
Block a user