Navy theme from the app icon, frosted glass, and the new mark in-app

The icon's artwork is now the source of truth for the palette:

- frost-* is a navy-tinted neutral scale and accent-* is the icon's
  snowflake blue; light page #f4f7fd / sidebar #e9eff9 / border
  #dce4f1, dark page #0e1424 / sidebar #0a0f1c / surfaces #171f33.
- Surfaces are frosted glass: .card, .glass-panel, .list-group and
  .sidebar-surface are translucent with backdrop-blur/saturate, and the
  page wrapper no longer paints an opaque --page, so the fixed navy/blue
  backdrop reads through the sidebar, cards, chrome and sheets.
- The primary CTA is the icon's blue (#1d61ab in light, #59a8f7 in dark
  with ink text).
- The gate stage, fields and glows are navy/blue; viewport theme-color
  and the manifest background/theme colour are #0e1424.
- The old monochrome snowflake tile is gone from the brand: sidebar,
  header, key gate and empty state now show the real app icon (the
  install sheet already did). Decorative snowflakes stay.
- --surface-strong is finally defined (it was referenced but never
  declared, so those inputs had no fill).
- Icon URLs carry ?v=2 so browser favicon caches refetch.

Updated the contrast audit (21 pairs) and the deck-chrome token
assertions to the new theme. Verified: contrast 21/21, deck chrome
15/15, key gate 49/49, install sheet 64/64, final ux 12/12, retry
14/14, zoom 6/6, security 8/8, pwa 16/16, deck fixes 15/15.
This commit is contained in:
Martin Dvořák 2026-10-08 06:15:51 +00:00
commit 979f3ddc39
10 changed files with 181 additions and 158 deletions

View file

@ -11,7 +11,7 @@ export const metadata = {
export default function CreatePage() {
return (
<ApiKeyGate>
<div className="flex min-h-dvh bg-[var(--page)]">
<div className="flex min-h-dvh">
<Sidebar />
<SidebarOffset />
<MobileSidebar />

View file

@ -4,30 +4,30 @@
@custom-variant dark (&:where(.dark, .dark *));
@theme {
/* Neutral scale (a whisper of cool) — the ChatGPT-ish workhorse. ------- */
--color-frost-50: #f8f8f9;
--color-frost-100: #f1f1f3;
--color-frost-200: #e4e4e7;
--color-frost-300: #cfcfd4;
--color-frost-400: #a3a3aa;
--color-frost-500: #7a7a82;
--color-frost-600: #5d5d64;
--color-frost-700: #47474d;
--color-frost-800: #34343a;
--color-frost-900: #26262a;
--color-frost-950: #171719;
/* Navy-tinted neutral scale — sampled from the app icon. ---------------- */
--color-frost-50: #f5f8fd;
--color-frost-100: #eaf0fa;
--color-frost-200: #d9e3f3;
--color-frost-300: #c2cfe4;
--color-frost-400: #92a3c0;
--color-frost-500: #6a7c9b;
--color-frost-600: #4e5f7d;
--color-frost-700: #3c4b66;
--color-frost-800: #2b3752;
--color-frost-900: #1d2740;
--color-frost-950: #0e1526;
/* Accent: one confident blue for buttons and highlights. -------------- */
--color-accent-50: #eef7fd;
--color-accent-100: #d9edfa;
--color-accent-200: #b6dcf4;
--color-accent-300: #83c3ea;
--color-accent-400: #4aa4dc;
--color-accent-500: #2387c4;
--color-accent-600: #186ca6;
--color-accent-700: #175687;
--color-accent-800: #18496f;
--color-accent-900: #183e5d;
/* Accent: the icon's snowflake blue. ------------------------------------ */
--color-accent-50: #eff7ff;
--color-accent-100: #dbecff;
--color-accent-200: #bcdcff;
--color-accent-300: #8fc6ff;
--color-accent-400: #59a8f7;
--color-accent-500: #3f95ea;
--color-accent-600: #2478d2;
--color-accent-700: #1d61ab;
--color-accent-800: #1c518b;
--color-accent-900: #1b4473;
/* Aurora hues used only for the frosted backdrop wash. ----------------- */
--color-aurora-mint: #9fe3d0;
@ -81,74 +81,76 @@
--------------------------------------------------------------------------- */
:root {
color-scheme: light;
--page: #ffffff;
--sidebar: #f9f9f9;
--page: #f4f7fd;
--sidebar: #e9eff9;
--surface: #ffffff;
--surface-raised: #ffffff;
--card-hover-bg: #fafafa;
--surface-sunken: rgb(13 13 13 / 0.05);
--hairline: #e5e5e5;
--card-border: #e2e2e2;
--sidebar-divider: #e2e2e2;
--hairline-soft: rgb(13 13 13 / 0.07);
--glass-tint: rgb(255 255 255 / 0.6);
--text: #0d0d0d; /* 17.6:1 on #ffffff */
--text-muted: #5d5d5d; /* 7.4:1 */
--text-subtle: #6b6b6b; /* 5.4:1 */
--hover: rgb(13 13 13 / 0.05);
--sidebar-top: #fbfbfc;
--sidebar-bottom: #f6f6f7;
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 18px -12px rgb(13 13 13 / 0.38);
/* API-key gate: near-white stage with soft blank glows, tinted input. */
--gate-base: #f6f6f8;
--gate-glow-a: rgb(84 102 130 / 0.22);
--gate-glow-b: rgb(124 138 160 / 0.18);
--surface-strong: #edf2fa;
--card-hover-bg: #f7faff;
--surface-sunken: rgb(14 21 40 / 0.05);
--hairline: #dce4f1;
--card-border: #d7e0ef;
--sidebar-divider: #d7e0ef;
--hairline-soft: rgb(14 21 40 / 0.07);
--glass-tint: rgb(255 255 255 / 0.55);
--text: #0e1526; /* 16.9:1 on #f4f7fd */
--text-muted: #4d5c78; /* 6.3:1 */
--text-subtle: #5a6a86; /* 5.1:1 */
--hover: rgb(14 21 40 / 0.05);
--sidebar-top: #eef3fb;
--sidebar-bottom: #e5edf9;
--sidebar-edge: 0 1px 0 var(--sidebar-divider), 6px 0 18px -12px rgb(14 21 40 / 0.35);
/* API-key gate: cool near-white stage with soft blue glows, tinted input. */
--gate-base: #eef3fb;
--gate-glow-a: rgb(90 150 240 / 0.25);
--gate-glow-b: rgb(140 150 240 / 0.2);
--gate-card: #ffffff;
--gate-card-border: #e7e7e9;
--gate-callout-bg: #f2f2f5;
--gate-callout-border: #cfcfd6;
--field: #f3f3f4;
--field-border: #e2e2e4;
--shadow-gate: 0 28px 70px -36px rgb(13 13 13 / 0.3), 0 1px 2px rgb(13 13 13 / 0.05);
--gate-card-border: #dde5f2;
--gate-callout-bg: #eef3fa;
--gate-callout-border: #c9d5e8;
--field: #eef2f9;
--field-border: #dbe3f0;
--shadow-gate: 0 28px 70px -36px rgb(14 21 40 / 0.3), 0 1px 2px rgb(14 21 40 / 0.05);
/* Install sheet: light variant (dark is the default, see .dark). */
--install-sheet: #ffffff;
--install-card: #f7f7f8;
--install-chip: #f2f2f2;
--install-border: #e0e0e0;
--shadow-card: 0 1px 2px rgb(13 13 13 / 0.06), 0 1px 3px rgb(13 13 13 / 0.04);
--card-shadow: 0 1px 2px rgb(13 13 13 / 0.05), 0 5px 14px -6px rgb(13 13 13 / 0.12);
--shadow-card-hover: 0 12px 30px -12px rgb(13 13 13 / 0.2), 0 2px 6px rgb(13 13 13 / 0.07);
--shadow-card: 0 1px 2px rgb(14 21 40 / 0.06), 0 1px 3px rgb(14 21 40 / 0.04);
--card-shadow: 0 1px 2px rgb(14 21 40 / 0.05), 0 5px 14px -6px rgb(14 21 40 / 0.12);
--shadow-card-hover: 0 12px 30px -12px rgb(14 21 40 / 0.2), 0 2px 6px rgb(14 21 40 / 0.07);
}
.dark {
color-scheme: dark;
--page: #212121;
--sidebar: #171717;
--surface: #2f2f2f;
--surface-raised: #383838;
--card-hover-bg: #383838;
--surface-sunken: rgb(255 255 255 / 0.08);
--hairline: rgb(255 255 255 / 0.14);
--card-border: var(--hairline);
--sidebar-divider: var(--hairline);
--page: #0e1424;
--sidebar: #0a0f1c;
--surface: #171f33;
--surface-raised: #1e2740;
--surface-strong: #202a42;
--card-hover-bg: #1e2740;
--surface-sunken: rgb(255 255 255 / 0.07);
--hairline: rgb(255 255 255 / 0.12);
--card-border: rgb(255 255 255 / 0.12);
--sidebar-divider: rgb(255 255 255 / 0.1);
--hairline-soft: rgb(255 255 255 / 0.08);
--glass-tint: rgb(255 255 255 / 0.08);
--text: #ececec; /* 12.9:1 on #212121 */
--text-muted: #b4b4b4; /* 7.6:1 */
--text-subtle: #a0a0a0; /* 6.0:1 */
--glass-tint: rgb(255 255 255 / 0.06);
--text: #e9eefb; /* 15.4:1 on #0e1424 */
--text-muted: #b7c3dc; /* 10.4:1 */
--text-subtle: #94a4c2; /* 7.3:1 */
--hover: rgb(255 255 255 / 0.07);
--sidebar-top: #181818;
--sidebar-bottom: #141414;
--sidebar-top: #0b1120;
--sidebar-bottom: #080d18;
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.75);
/* API-key gate: near-black stage, monochrome glows, raised input. */
--gate-base: #0f0f0f;
--gate-glow-a: rgb(255 255 255 / 0.17);
--gate-glow-b: rgb(255 255 255 / 0.12);
--gate-card: #1a1a1a;
--gate-card-border: rgb(255 255 255 / 0.08);
--gate-callout-bg: rgb(255 255 255 / 0.07);
--gate-callout-border: rgb(255 255 255 / 0.22);
--field: #242425;
/* API-key gate: navy stage with the icon's blue glows, raised input. */
--gate-base: #0a0f1c;
--gate-glow-a: rgb(70 140 235 / 0.2);
--gate-glow-b: rgb(120 120 235 / 0.14);
--gate-card: #131a2c;
--gate-card-border: rgb(255 255 255 / 0.1);
--gate-callout-bg: rgb(255 255 255 / 0.06);
--gate-callout-border: rgb(255 255 255 / 0.2);
--field: #1b2438;
--field-border: rgb(255 255 255 / 0.1);
--shadow-gate: 0 30px 80px -40px rgb(0 0 0 / 0.85), 0 1px 2px rgb(0 0 0 / 0.4);
/* Install sheet: layered dark surfaces (default theme for the sheet). */
@ -161,22 +163,22 @@
--shadow-card-hover: 0 10px 28px -16px rgb(0 0 0 / 0.55), 0 1px 2px rgb(0 0 0 / 0.3);
}
/* Backdrop: a whisper of cool over a neutral base (never a coloured surface). */
/* Backdrop: the icon's navy base and blue wash, seen through frosted glass. */
:root {
--bd-sky: rgb(150 200 240 / 0.07);
--bd-lilac: rgb(188 178 236 / 0.05);
--bd-mint: rgb(150 220 202 / 0.05);
--bd-top: #fdfdfd;
--bd-mid: #fafafa;
--bd-bottom: #f7f7f7;
--bd-sky: rgb(110 175 245 / 0.2);
--bd-lilac: rgb(145 145 240 / 0.14);
--bd-mint: rgb(110 210 230 / 0.12);
--bd-top: #f8fafe;
--bd-mid: #f2f6fd;
--bd-bottom: #ecf1fb;
}
.dark {
--bd-sky: rgb(74 164 220 / 0.05);
--bd-lilac: rgb(160 140 235 / 0.04);
--bd-mint: rgb(88 201 173 / 0.04);
--bd-top: #232323;
--bd-mid: #212121;
--bd-bottom: #1e1e1e;
--bd-sky: rgb(60 130 230 / 0.14);
--bd-lilac: rgb(110 115 235 / 0.1);
--bd-mint: rgb(70 180 215 / 0.08);
--bd-top: #111a2e;
--bd-mid: #0e1424;
--bd-bottom: #0a101d;
}
html,
@ -280,14 +282,14 @@ body {
}
.dark .app-backdrop::before,
.dark .app-backdrop::after {
opacity: 0.16;
opacity: 0.3;
}
@layer components {
/* Glass primitives ------------------------------------------------------ */
.glass-panel {
@apply backdrop-blur-xl;
background: var(--surface);
@apply backdrop-blur-xl backdrop-saturate-150;
background: color-mix(in oklab, var(--surface) 82%, transparent);
border: 1px solid var(--hairline);
box-shadow: var(--shadow-card);
}
@ -323,7 +325,8 @@ body {
.card {
border-radius: 1.125rem; /* 18px */
background: var(--surface);
@apply backdrop-blur-xl backdrop-saturate-150;
background: color-mix(in oklab, var(--surface) 78%, transparent);
border: 1px solid var(--card-border);
box-shadow: var(--card-shadow);
}
@ -351,7 +354,7 @@ body {
.list-group {
border-radius: 1rem;
@apply backdrop-blur-lg backdrop-saturate-150;
background: var(--surface);
background: color-mix(in oklab, var(--surface) 82%, transparent);
border: 1px solid var(--hairline);
overflow: hidden;
}
@ -389,7 +392,7 @@ body {
}
.dark .segmented > button[aria-pressed="true"],
.dark .segmented > button[aria-checked="true"] {
background: #383838;
background: var(--surface-raised);
color: #fff;
}
@ -420,9 +423,14 @@ body {
.dark .tint-peach { background: linear-gradient(140deg, rgb(230 160 118 / 0.26), rgb(230 160 118 / 0.1)); color: #fadfc9 }
.dark .tint-rose { background: linear-gradient(140deg, rgb(230 130 150 / 0.26), rgb(230 130 150 / 0.1)); color: #fbd5dd }
/* Sidebar surface: a touch darker than the page, with a real edge. */
/* Sidebar surface: frosted navy glass with a real edge. */
.sidebar-surface {
background: linear-gradient(180deg, var(--sidebar-top), var(--sidebar-bottom));
@apply backdrop-blur-xl backdrop-saturate-150;
background: linear-gradient(
180deg,
color-mix(in oklab, var(--sidebar-top) 84%, transparent),
color-mix(in oklab, var(--sidebar-bottom) 84%, transparent)
);
border-right: 1px solid var(--sidebar-divider);
box-shadow: var(--sidebar-edge);
}
@ -450,27 +458,27 @@ body {
color: var(--text);
}
/* Shared CTA language: near-black pill in light mode, white pill in dark. */
/* Shared CTA language: the icon's blue, with a deeper fill in light mode. */
.btn-primary {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 999px;
background: #0d0d0d;
background: #1d61ab;
color: #ffffff;
font-weight: 500;
box-shadow: 0 1px 2px rgb(13 13 13 / 0.18);
box-shadow: 0 1px 2px rgb(14 21 40 / 0.22);
transition: background-color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.btn-primary:hover { background: #2f2f2f; }
.btn-primary:hover { background: #2478d2; }
.btn-primary:active { transform: scale(0.985); }
.dark .btn-primary {
background: #ffffff;
color: #0d0d0d;
background: #59a8f7;
color: #081020;
box-shadow: none;
}
.dark .btn-primary:hover { background: #e5e5e5; }
.dark .btn-primary:hover { background: #7bbaff; }
.btn-secondary {
display: inline-flex;

View file

@ -20,14 +20,15 @@ export const metadata: Metadata = {
},
icons: {
icon: [
{ url: "/icons/favicon-32.png", sizes: "32x32", type: "image/png" },
{ url: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
{ url: "/icons/icon-512.png", sizes: "512x512", type: "image/png" },
// ?v=2 busts browser favicon caches after the icon redesign.
{ url: "/icons/favicon-32.png?v=2", sizes: "32x32", type: "image/png" },
{ url: "/icons/icon-192.png?v=2", sizes: "192x192", type: "image/png" },
{ url: "/icons/icon-512.png?v=2", sizes: "512x512", type: "image/png" },
],
apple: [
{ url: "/icons/apple-touch-icon.png", sizes: "180x180" },
{ url: "/icons/apple-touch-icon-167.png", sizes: "167x167" },
{ url: "/icons/apple-touch-icon-152.png", sizes: "152x152" },
{ url: "/icons/apple-touch-icon.png?v=2", sizes: "180x180" },
{ url: "/icons/apple-touch-icon-167.png?v=2", sizes: "167x167" },
{ url: "/icons/apple-touch-icon-152.png?v=2", sizes: "152x152" },
],
},
other: {
@ -48,8 +49,8 @@ export const viewport: Viewport = {
userScalable: false,
viewportFit: "cover",
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#f8fafc" },
{ media: "(prefers-color-scheme: dark)", color: "#0a0f13" },
{ media: "(prefers-color-scheme: light)", color: "#f4f7fd" },
{ media: "(prefers-color-scheme: dark)", color: "#0e1424" },
],
};

View file

@ -14,8 +14,8 @@ export default function manifest(): MetadataRoute.Manifest {
scope: "/",
display: "standalone",
orientation: "portrait",
background_color: "#f8fafc",
theme_color: "#f8fafc",
background_color: "#0e1424",
theme_color: "#0e1424",
categories: ["education", "productivity"],
icons: [
{ src: "/icons/icon-192.png", sizes: "192x192", type: "image/png", purpose: "any" },

View file

@ -7,7 +7,7 @@ import { TabBar } from "@/components/TabBar";
export default function HomePage() {
return (
<ApiKeyGate>
<div className="flex min-h-dvh bg-[var(--page)]">
<div className="flex min-h-dvh">
<Sidebar />
<SidebarOffset />
<MobileSidebar />