feat(theme): VNClagoon brand theme (navy + cyan) as default, dark-first
Add builtin-vnclagoon theme (cyan #00D4FF accent on navy #0A0E1A, DM Sans body + Syne headings, self-hosted OFL fonts); set as default theme policy; default mode dark. Add VNCmail wordmark SVGs (on-dark/on-light) + wire logo/company via k8s secret template. Placeholder wordmark — swap official styleguide SVG. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
476c76c420
commit
22e5e97da9
@@ -10,10 +10,15 @@ stringData:
|
||||
# Core — connect to Stalwart over JMAP
|
||||
JMAP_SERVER_URL: "https://stalwart.sandbox.vnc.de"
|
||||
SESSION_SECRET: "REPLACE_ME__openssl_rand_base64_32"
|
||||
# Branding
|
||||
# Branding (theme defaults to VNClagoon in code; these set name + logo)
|
||||
APP_NAME: "VNCmail+"
|
||||
APP_SHORT_NAME: "VNCmail+"
|
||||
LOGIN_COMPANY_NAME: "VNC"
|
||||
LOGIN_COMPANY_NAME: "VNClagoon"
|
||||
LOGIN_LOGO_DARK_URL: "/branding/vncmail-wordmark-on-dark.svg"
|
||||
LOGIN_LOGO_LIGHT_URL: "/branding/vncmail-wordmark-on-light.svg"
|
||||
APP_LOGO_DARK_URL: "/branding/vncmail-wordmark-on-dark.svg"
|
||||
APP_LOGO_LIGHT_URL: "/branding/vncmail-wordmark-on-light.svg"
|
||||
LOGIN_LOGO_MAX_HEIGHT: "52"
|
||||
# Housekeeping
|
||||
BULWARK_UPDATE_CHECK: "off"
|
||||
# Data dirs default to /app/data/* (mounted to the PVCs) — no need to set them.
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ export interface ThemePolicy {
|
||||
export const DEFAULT_THEME_POLICY: ThemePolicy = {
|
||||
disabledBuiltinThemes: [],
|
||||
disabledThemes: [],
|
||||
defaultThemeId: null,
|
||||
defaultThemeId: 'builtin-vnclagoon',
|
||||
};
|
||||
|
||||
export interface SettingsPolicy {
|
||||
|
||||
@@ -874,7 +874,125 @@ body[data-theme-skin="builtin-aurora-glass"] [role="menu"] [role="menuitem"]:foc
|
||||
background-color: rgba(139, 123, 255, 0.22) !important;
|
||||
}`;
|
||||
|
||||
// "VNClagoon" — VNC's brand theme. Deep-navy grounds (#0A0E1A) with the
|
||||
// VNClagoon cyan (#00D4FF) as the single accent, DM Sans for body and Syne for
|
||||
// display. Dark is the brand-primary variant; the light variant deepens the
|
||||
// cyan (#00A5CC) so it stays legible on white. Fonts are self-hosted under
|
||||
// /fonts (OFL) so they render offline / behind the CSP.
|
||||
const vnclagoonCSS = `
|
||||
@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: #dce3ee;
|
||||
--color-input: #dce3ee;
|
||||
--color-ring: #00a5cc;
|
||||
--color-background: #ffffff;
|
||||
--color-foreground: #0a0e1a;
|
||||
--color-primary: #00a5cc;
|
||||
--color-primary-foreground: #ffffff;
|
||||
--color-secondary: #eef2f8;
|
||||
--color-secondary-foreground: #0a0e1a;
|
||||
--color-muted: #eef2f8;
|
||||
--color-muted-foreground: #5a6478;
|
||||
--color-accent: #e3f7fd;
|
||||
--color-accent-foreground: #006f8c;
|
||||
--color-destructive: #e5484d;
|
||||
--color-destructive-foreground: #ffffff;
|
||||
--color-popover: #ffffff;
|
||||
--color-popover-foreground: #0a0e1a;
|
||||
--color-sidebar: #f7f9fc;
|
||||
--color-sidebar-foreground: #0a0e1a;
|
||||
--color-sidebar-border: #dce3ee;
|
||||
--color-sidebar-accent: #eef2f8;
|
||||
--color-sidebar-accent-foreground: #0a0e1a;
|
||||
--color-card: #ffffff;
|
||||
--color-card-foreground: #0a0e1a;
|
||||
--color-success: #16a34a;
|
||||
--color-success-foreground: #ffffff;
|
||||
--color-warning: #ca8a04;
|
||||
--color-warning-foreground: #ffffff;
|
||||
--color-info: #00a5cc;
|
||||
--color-info-foreground: #ffffff;
|
||||
--color-selection: #e3f7fd;
|
||||
--color-selection-foreground: #006f8c;
|
||||
--color-unread: #00a5cc;
|
||||
--color-chart-1: #00a5cc;
|
||||
--color-chart-2: #16a34a;
|
||||
--color-chart-3: #ca8a04;
|
||||
--color-chart-4: #e5484d;
|
||||
--color-chart-5: #6d8bff;
|
||||
}
|
||||
.dark {
|
||||
--color-border: #1e2740;
|
||||
--color-input: #1e2740;
|
||||
--color-ring: #00d4ff;
|
||||
--color-background: #0a0e1a;
|
||||
--color-foreground: #e8ecf4;
|
||||
--color-primary: #00d4ff;
|
||||
--color-primary-foreground: #05121b;
|
||||
--color-secondary: #141b2e;
|
||||
--color-secondary-foreground: #e8ecf4;
|
||||
--color-muted: #141b2e;
|
||||
--color-muted-foreground: #8a97b0;
|
||||
--color-accent: #10263a;
|
||||
--color-accent-foreground: #7fe6ff;
|
||||
--color-destructive: #ff5a5f;
|
||||
--color-destructive-foreground: #ffffff;
|
||||
--color-popover: #111726;
|
||||
--color-popover-foreground: #e8ecf4;
|
||||
--color-sidebar: #080b14;
|
||||
--color-sidebar-foreground: #e8ecf4;
|
||||
--color-sidebar-border: #1e2740;
|
||||
--color-sidebar-accent: #141b2e;
|
||||
--color-sidebar-accent-foreground: #e8ecf4;
|
||||
--color-card: #111726;
|
||||
--color-card-foreground: #e8ecf4;
|
||||
--color-success: #22c55e;
|
||||
--color-success-foreground: #05121b;
|
||||
--color-warning: #f6c544;
|
||||
--color-warning-foreground: #05121b;
|
||||
--color-info: #00d4ff;
|
||||
--color-info-foreground: #05121b;
|
||||
--color-selection: rgba(0, 212, 255, 0.20);
|
||||
--color-selection-foreground: #7fe6ff;
|
||||
--color-unread: #00d4ff;
|
||||
--color-chart-1: #00d4ff;
|
||||
--color-chart-2: #22c55e;
|
||||
--color-chart-3: #f6c544;
|
||||
--color-chart-4: #ff5a5f;
|
||||
--color-chart-5: #6d8bff;
|
||||
}`;
|
||||
|
||||
// Syne display face on headings; DM Sans everywhere else. Scoped to the skin
|
||||
// so it detaches cleanly when the theme is switched off.
|
||||
const vnclagoonSkin = `
|
||||
body[data-theme-skin="builtin-vnclagoon"] {
|
||||
font-family: "DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
}
|
||||
body[data-theme-skin="builtin-vnclagoon"] h1,
|
||||
body[data-theme-skin="builtin-vnclagoon"] h2,
|
||||
body[data-theme-skin="builtin-vnclagoon"] h3 {
|
||||
font-family: "Syne", "DM Sans", sans-serif;
|
||||
letter-spacing: -0.01em;
|
||||
}`;
|
||||
|
||||
export const BUILTIN_THEMES: InstalledTheme[] = [
|
||||
{
|
||||
id: 'builtin-vnclagoon',
|
||||
name: 'VNClagoon',
|
||||
version: '1.0.0',
|
||||
author: 'VNC',
|
||||
description: 'VNClagoon brand theme — deep navy with cyan accent, DM Sans + Syne',
|
||||
css: vnclagoonCSS,
|
||||
skin: vnclagoonSkin,
|
||||
variants: ['light', 'dark'],
|
||||
typography: { fontSans: '"DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif' },
|
||||
enabled: true,
|
||||
builtIn: true,
|
||||
},
|
||||
{
|
||||
id: 'builtin-qui',
|
||||
name: 'Qui',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 130" role="img" aria-label="VNCmail">
|
||||
<!-- Placeholder wordmark — VNClagoon palette. Swap for the official styleguide SVG when available. -->
|
||||
<text x="280" y="98" text-anchor="middle"
|
||||
font-family="'Avenir Next','Futura','Montserrat','Helvetica Neue',Arial,sans-serif"
|
||||
font-weight="800" font-size="96" letter-spacing="-2">
|
||||
<tspan fill="#EEF2FC">VNC</tspan><tspan fill="#00D4FF">mail</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 490 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 130" role="img" aria-label="VNCmail">
|
||||
<!-- Placeholder wordmark — VNClagoon palette (light backgrounds). Swap for the official styleguide SVG when available. -->
|
||||
<text x="280" y="98" text-anchor="middle"
|
||||
font-family="'Avenir Next','Futura','Montserrat','Helvetica Neue',Arial,sans-serif"
|
||||
font-weight="800" font-size="96" letter-spacing="-2">
|
||||
<tspan fill="#0A0E1A">VNC</tspan><tspan fill="#00B4E0">mail</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 510 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -78,8 +78,8 @@ let themeSyncPromise: Promise<void> | null = null;
|
||||
export const useThemeStore = create<ThemeState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
theme: 'system',
|
||||
resolvedTheme: 'light',
|
||||
theme: 'dark',
|
||||
resolvedTheme: 'dark',
|
||||
hydrated: false,
|
||||
installedThemes: [...BUILTIN_THEMES],
|
||||
activeThemeId: null,
|
||||
|
||||
@@ -25,6 +25,11 @@ that merging new upstream releases stays a triage exercise, not an archaeology d
|
||||
|
||||
| 2026-08-03 | `deploy/k8s/` (new) | k8s manifests + runbook for microk8s deploy | Bulwark is stateful → runs as a container w/ persistent volumes, not Vercel serverless |
|
||||
| 2026-08-03 | `.gitignore` | ignore `deploy/k8s/secret.yaml` | keep the real env secret out of git |
|
||||
| 2026-08-03 | `public/branding/*.svg` (new) | VNCmail wordmark (on-dark + on-light) | VNC logo (placeholder — swap official SVG) |
|
||||
| 2026-08-03 | `public/fonts/*.woff2` (new) | DM Sans 400/500/700 + Syne 700/800 (OFL, self-hosted) | VNClagoon typography |
|
||||
| 2026-08-03 | `lib/builtin-themes.ts` | add `builtin-vnclagoon` theme (navy+cyan, DM Sans/Syne, @font-face) | VNClagoon brand theme |
|
||||
| 2026-08-03 | `lib/admin/types.ts` | `DEFAULT_THEME_POLICY.defaultThemeId` → `builtin-vnclagoon` | make VNClagoon the default theme |
|
||||
| 2026-08-03 | `stores/theme-store.ts` | default `theme`/`resolvedTheme` → `dark` | dark-first per VNClagoon styleguide |
|
||||
|
||||
_Note: Vercel was tried and **abandoned** on 2026-08-03. Bulwark writes to a local
|
||||
data dir (`/app/data/*`); Vercel serverless has a read-only filesystem → crash
|
||||
|
||||
Reference in New Issue
Block a user