Frosted ice glass restyle

Rebuilds the look around the spec: an ice scene, one shared glass
recipe, icy accents and glass on every large surface. Visual only —
layout, behaviour, copy and functionality are untouched.

Background
- .ice-scene: fixed, z-index -1, pointer-events none. Navy base
  (#070b16 to #0c1426) in dark, pale (#f4f9ff to #e6f1fb) in light,
  four drifting glacier blobs (#38bdf8/#60a5fa/#818cf8, 30-60s
  transform-only loops), five glossy ice orbs (40-220px radial
  gradients with a highlight at 30% 25% and a blue shadow) placed so the
  glass blurs them, and an SVG feTurbulence grain at 4-5%.
- prefers-reduced-motion stops the drift; phones get two blobs and two
  orbs fewer.

Glass
- One shared recipe (.glass-panel, .card, .sidebar-surface,
  .study-chrome, .tabbar, .glass, .gate-card): rgba(255,255,255,.06)
  dark / .55 light, blur(24px) saturate(140%) (16px on phones), a bright
  1px rim, an inset top highlight and a blue-tinted outer shadow.
- Nested glass (chips, inputs, secondary buttons) is more opaque and
  never adds a second blur layer. @supports fallback for no
  backdrop-filter. Radii: 24-28px cards/sheets, 14px inputs/buttons,
  pills for chips. Cards hover brighter + cyan rim + 2px lift, 180ms.

Colour and type
- Accent is glacier cyan (#38bdf8) to ice blue (#0ea5e9); progress bars
  and selected states use the gradient; the active sidebar item is a
  frosted highlight with a cyan rim.
- Primary CTA: icy gradient — white to #bfe3ff with ink text in dark,
  deep blue in light (biased so the label sits on the darker half and
  keeps 5.6:1).
- Leftover slate/black greys replaced with navy-tinted frost tokens.
- Headings are semibold with tighter tracking.

Screens
- Dashboard: the sidebar is one tall inset rounded glass panel; deck
  cards are glass.
- Gate: one centred glass card over the scene, orbs visible behind it.
- Sheets/modals: glass over the dimmed, lightly blurred backdrop.
- Study view: question card more opaque (.glass-solid); top bar and
  pager are slim glass pills.

Perf: measured on a phone viewport with 4x CPU throttling. The animated
blobs' filter: blur() was the whole cost (150ms vs 33ms per frame), so
they are soft radial gradients with no filter now; idle 60fps, ~17ms
median while scrolling.

Tests updated for the new tokens: contrast 18/18 AA, deck chrome 15/15,
key gate 49/49, install sheet 64/64, deck fixes 15/15, retry 14/14,
zoom 6/6, final ux 12/12, security 8/8, pwa 16/16.
This commit is contained in:
Martin Dvořák 2026-10-08 10:11:55 +00:00
commit 52cce42cde
20 changed files with 383 additions and 262 deletions

View file

@ -17,17 +17,17 @@
--color-frost-900: #1d2740;
--color-frost-950: #0e1526;
/* 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;
/* Accent: glacier cyan to ice blue — focus rings, links, highlights. ---- */
--color-accent-50: #ecf9ff;
--color-accent-100: #d6f1ff;
--color-accent-200: #b0e5ff;
--color-accent-300: #7dd4fc;
--color-accent-400: #38bdf8;
--color-accent-500: #0ea5e9;
--color-accent-600: #0284c7;
--color-accent-700: #0369a1;
--color-accent-800: #075985;
--color-accent-900: #0c4a6e;
/* Aurora hues used only for the frosted backdrop wash. ----------------- */
--color-aurora-mint: #9fe3d0;
@ -81,104 +81,110 @@
--------------------------------------------------------------------------- */
:root {
color-scheme: light;
--page: #f4f7fd;
--sidebar: #e9eff9;
--page: #eef5fd;
--sidebar: #ffffff;
--surface: #ffffff;
--surface-raised: #ffffff;
--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);
--surface-strong: rgb(255 255 255 / 0.72); /* nested glass: inputs, chips */
--card-hover-bg: rgb(255 255 255 / 0.72);
--surface-sunken: rgb(12 32 62 / 0.06);
--hairline: #d5e4f4;
--card-border: var(--glass-border);
--sidebar-divider: rgb(255 255 255 / 0.75);
--hairline-soft: rgb(12 32 62 / 0.08);
--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);
--text: #0b1830; /* 16.6:1 on #eef5fd */
--text-muted: #46586f; /* 6.6:1 on light glass */
--text-subtle: #566a84; /* 5.0:1 on light glass */
--hover: rgb(12 32 62 / 0.05);
--focus: #0ea5e9;
/* Frosted ice glass — one shared recipe for every large surface. */
--glass-bg: rgb(255 255 255 / 0.55);
--glass-border: rgb(255 255 255 / 0.8);
--glass-highlight: rgb(255 255 255 / 0.85);
--glass-shadow: 0 20px 50px -20px rgb(30 64 175 / 0.35);
--glass-solid-bg: rgb(255 255 255 / 0.8); /* question card: more opaque */
--glass-fallback: rgb(247 251 255 / 0.97); /* no backdrop-filter */
--nav-active-bg: rgb(255 255 255 / 0.75);
--nav-active-border: rgb(56 189 248 / 0.5);
/* 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: #dde5f2;
--gate-callout-bg: #eef3fa;
--gate-callout-border: #c9d5e8;
--field: #eef2f9;
--field-border: #dbe3f0;
--gate-card: rgb(255 255 255 / 0.62);
--gate-callout-bg: rgb(255 255 255 / 0.55);
--gate-callout-border: rgb(56 189 248 / 0.32);
--field: rgb(255 255 255 / 0.66);
--field-border: rgb(255 255 255 / 0.9);
--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;
--install-sheet: rgb(255 255 255 / 0.6);
--install-card: rgb(255 255 255 / 0.7);
--install-chip: rgb(255 255 255 / 0.55);
--install-border: rgb(255 255 255 / 0.8);
--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);
--shadow-card-hover: 0 24px 60px -24px rgb(30 64 175 / 0.45), 0 2px 6px rgb(30 64 175 / 0.12);
}
.dark {
color-scheme: dark;
--page: #0e1424;
--sidebar: #0a0f1c;
--surface: #171f33;
--surface-raised: #1e2740;
--surface-strong: #202a42;
--card-hover-bg: #1e2740;
--page: #0a1120;
--sidebar: #0b1524;
--surface: #16233a;
--surface-raised: #1d2c47;
--surface-strong: rgb(255 255 255 / 0.08);
--card-hover-bg: rgb(255 255 255 / 0.1);
--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);
--card-border: var(--glass-border);
--sidebar-divider: rgb(255 255 255 / 0.12);
--hairline-soft: rgb(255 255 255 / 0.08);
--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 */
--text: #e8f0ff; /* 16.3:1 on #0a1120 */
--text-muted: #b3c4e0; /* 10.1:1 */
--text-subtle: #90a4c6; /* 6.9:1 */
--hover: rgb(255 255 255 / 0.07);
--sidebar-top: #0b1120;
--sidebar-bottom: #080d18;
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.75);
--focus: #38bdf8;
/* Frosted ice glass — dark: barely-there tint, brighter rim. */
--glass-bg: rgb(255 255 255 / 0.06);
--glass-border: rgb(255 255 255 / 0.12);
--glass-highlight: rgb(255 255 255 / 0.25);
--glass-shadow: 0 20px 50px -20px rgb(30 64 175 / 0.35);
--glass-solid-bg: rgb(255 255 255 / 0.1);
--glass-fallback: rgb(16 26 45 / 0.97);
--nav-active-bg: rgb(56 189 248 / 0.14);
--nav-active-border: rgb(56 189 248 / 0.4);
/* 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-card: rgb(255 255 255 / 0.06);
--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);
--gate-callout-border: rgb(56 189 248 / 0.3);
--field: rgb(255 255 255 / 0.07);
--field-border: rgb(255 255 255 / 0.14);
--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). */
--install-sheet: #1c1c1e;
--install-card: #2c2c2e;
--install-chip: #3a3a3c;
--install-border: rgb(255 255 255 / 0.08);
--install-sheet: rgb(255 255 255 / 0.06);
--install-card: rgb(255 255 255 / 0.07);
--install-chip: rgb(255 255 255 / 0.1);
--install-border: rgb(255 255 255 / 0.14);
--shadow-card: 0 1px 2px rgb(0 0 0 / 0.25);
--card-shadow: var(--shadow-card);
--shadow-card-hover: 0 10px 28px -16px rgb(0 0 0 / 0.55), 0 1px 2px rgb(0 0 0 / 0.3);
--shadow-card-hover: 0 24px 60px -24px rgb(30 64 175 / 0.55), 0 2px 8px rgb(0 0 0 / 0.3);
}
/* Backdrop: the icon's navy base and blue wash, seen through frosted glass. */
/* Ice scene tokens: the fixed layered background behind everything. */
:root {
--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;
--ice-base-top: #f4f9ff;
--ice-base-bottom: #e6f1fb;
--ice-blob-opacity: 0.18;
--ice-orb-opacity: 0.55;
--ice-grain-opacity: 0.05;
}
.dark {
--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;
--ice-base-top: #070b16;
--ice-base-bottom: #0c1426;
--ice-blob-opacity: 0.4;
--ice-orb-opacity: 0.8;
--ice-grain-opacity: 0.045;
}
html,
@ -209,126 +215,196 @@ body {
background: transparent;
}
/* API-key gate stage: two big blurred blobs (top-right + bottom-left) over a
near-black (dark) or near-white (light) base. Real blurred elements read as
atmosphere; a flat radial gradient was too faint. Both blobs are decorative,
non-interactive and sit behind the card (z-index 0 vs the card's z-10). */
.gate-bg {
background: var(--gate-base);
}
.gate-bg::before,
.gate-bg::after {
content: "";
position: absolute;
z-index: 0;
width: 48rem;
height: 48rem;
border-radius: 9999px;
filter: blur(120px);
pointer-events: none;
}
.gate-bg::before {
top: -20rem;
right: -16rem;
background: var(--gate-glow-a);
}
.gate-bg::after {
bottom: -20rem;
left: -16rem;
background: var(--gate-glow-b);
}
/* The key gate rides on the shared ice scene (see .ice-scene). */
.app-backdrop {
/* The fixed ice scene: navy/pale base, drifting glacier blobs, glossy ice
orbs and a whisper of grain. Purely decorative. */
.ice-scene {
position: fixed;
inset: 0;
z-index: -10;
z-index: -1;
overflow: hidden;
pointer-events: none;
background:
radial-gradient(135vw 110vh at 4% -30%, var(--bd-sky) 0%, transparent 72%),
radial-gradient(125vw 105vh at 104% -18%, var(--bd-lilac) 0%, transparent 70%),
radial-gradient(135vw 105vh at 38% 128%, var(--bd-mint) 0%, transparent 72%),
linear-gradient(180deg, var(--bd-top) 0%, var(--bd-mid) 50%, var(--bd-bottom) 100%);
background: linear-gradient(180deg, var(--ice-base-top) 0%, var(--ice-base-bottom) 100%);
}
/* Two slow, very soft blobs for a little life behind the glass. */
.app-backdrop::before,
.app-backdrop::after {
content: "";
.ice-blob {
position: absolute;
border-radius: 50%;
filter: blur(110px);
opacity: 0.22;
opacity: var(--ice-blob-opacity);
will-change: transform;
/* No filter: blur() here on purpose. A radial gradient fading to
transparent is already a soft blob, and blurring a huge layer that is
also being animated forces a re-rasterisation every frame (measured
150ms vs 33ms per frame in a software renderer). The falloff stops below
keep the same softness. */
}
.app-backdrop::before {
width: 52vw;
height: 52vw;
min-width: 24rem;
min-height: 24rem;
top: -26vh;
left: -18vw;
background: radial-gradient(circle, var(--bd-sky), transparent 66%);
animation: float 16s ease-in-out infinite;
.ice-blob-a {
width: 52vw; height: 52vw; min-width: 24rem; min-height: 24rem;
top: -14vh; left: -10vw;
background: radial-gradient(circle, rgb(56 189 248 / 0.9) 0%, rgb(56 189 248 / 0.45) 38%, transparent 72%);
animation: ice-drift-a 44s ease-in-out infinite;
}
.app-backdrop::after {
width: 46vw;
height: 46vw;
min-width: 22rem;
min-height: 22rem;
bottom: -28vh;
right: -14vw;
background: radial-gradient(circle, var(--bd-mint), transparent 66%);
animation: float 20s ease-in-out infinite reverse;
.ice-blob-b {
width: 46vw; height: 46vw; min-width: 22rem; min-height: 22rem;
top: 4vh; right: -12vw;
background: radial-gradient(circle, rgb(96 165 250 / 0.9) 0%, rgb(96 165 250 / 0.45) 38%, transparent 72%);
animation: ice-drift-b 52s ease-in-out infinite;
}
.dark .app-backdrop::before,
.dark .app-backdrop::after {
opacity: 0.3;
.ice-blob-c {
width: 48vw; height: 48vw; min-width: 22rem; min-height: 22rem;
bottom: -16vh; left: 14vw;
background: radial-gradient(circle, rgb(129 140 248 / 0.9) 0%, rgb(129 140 248 / 0.42) 38%, transparent 74%);
animation: ice-drift-c 60s ease-in-out infinite;
}
.ice-blob-d {
width: 34vw; height: 34vw; min-width: 16rem; min-height: 16rem;
bottom: 2vh; right: 6vw;
background: radial-gradient(circle, rgb(56 189 248 / 0.85) 0%, rgb(56 189 248 / 0.4) 38%, transparent 74%);
animation: ice-drift-a 48s ease-in-out infinite reverse;
}
@keyframes ice-drift-a {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(4vw, 3vh, 0) scale(1.08); }
}
@keyframes ice-drift-b {
0%, 100% { transform: translate3d(0, 0, 0) scale(1.05); }
50% { transform: translate3d(-5vw, 4vh, 0) scale(1); }
}
@keyframes ice-drift-c {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(3vw, -4vh, 0) scale(1.1); }
}
/* Glossy ice orbs, placed so the glass panels blur them. */
.ice-orb {
position: absolute;
border-radius: 50%;
opacity: var(--ice-orb-opacity);
background: radial-gradient(
circle at 30% 25%,
rgb(255 255 255 / 0.95) 0%,
rgb(191 227 255 / 0.55) 18%,
rgb(96 165 250 / 0.35) 48%,
rgb(37 99 235 / 0.45) 100%
);
box-shadow: 0 18px 40px -18px rgb(30 64 175 / 0.55), inset 0 -8px 18px -10px rgb(30 64 175 / 0.6);
}
.ice-orb-1 { width: 190px; height: 190px; top: 14%; left: 12%; }
.ice-orb-2 { width: 84px; height: 84px; top: 58%; left: 4%; }
.ice-orb-3 { width: 220px; height: 220px; bottom: -70px; right: 10%; }
.ice-orb-4 { width: 52px; height: 52px; top: 24%; right: 28%; }
.ice-orb-5 { width: 120px; height: 120px; bottom: 16%; left: 38%; }
/* Frosted grain over the whole scene. */
.ice-grain {
position: absolute;
inset: 0;
opacity: var(--ice-grain-opacity);
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce) {
.ice-blob { animation: none; }
}
/* Phones: fewer, softer blobs so the drift animation stays cheap. Unlayered
on purpose — layered rules would lose to the .ice-blob declarations above. */
@media (max-width: 1023px) {
.ice-blob-c,
.ice-blob-d,
.ice-orb-4,
.ice-orb-5 {
display: none;
}
.ice-orb-1 { width: 120px; height: 120px; }
.ice-orb-3 { width: 150px; height: 150px; }
}
@layer components {
/* Glass primitives ------------------------------------------------------ */
.glass-panel {
@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);
/* Frosted ice glass ------------------------------------------------------
One shared recipe for the large surfaces only: translucent tint, 24px
blur, a bright rim, an inner top highlight and a blue-tinted shadow.
Nested glass (chips, inputs) never adds a second blur layer. Tailwind's
backdrop utilities emit the -webkit- prefix too (hand-written
backdrop-filter gets minified to -webkit- only, which paints nothing). */
.glass-panel,
.card,
.sidebar-surface,
.study-chrome,
.tabbar,
.glass,
.gate-card {
@apply backdrop-blur-xl backdrop-saturate-[1.4];
background: var(--glass-bg);
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-highlight);
}
.glass-panel {
border: 1px solid var(--glass-border);
border-radius: 1.5rem;
}
/* Nested glass: slightly more opaque than its parent, no extra blur. */
.glass-chip {
@apply backdrop-blur-md;
background: var(--surface-sunken);
border: 1px solid transparent;
border: 1px solid var(--glass-border);
}
/* More opaque glass for the study question card, where text must stay crisp. */
.glass-solid {
background: var(--glass-solid-bg);
}
.glass-chip:hover {
background: var(--hover);
}
.glass {
/* Tailwind emits both the prefixed and standard properties; hand-written
backdrop-filter gets minified down to -webkit- only, which paints nothing. */
@apply backdrop-blur-xl backdrop-saturate-150;
background: color-mix(in oklab, var(--surface) 70%, transparent);
border-bottom: 1px solid var(--hairline);
border-bottom: 1px solid var(--glass-border);
}
/* Study-feed chrome (progress header + bottom pager): the same neutral
surface as the cards, with a neutral border and shadow. */
.study-chrome {
@apply backdrop-blur-xl backdrop-saturate-150;
background: color-mix(in oklab, var(--surface) 88%, transparent);
border: 1px solid var(--hairline);
box-shadow: var(--shadow-card);
}
.dark .study-chrome {
border-color: var(--hairline-soft);
border: 1px solid var(--glass-border);
}
.card {
border-radius: 1.125rem; /* 18px */
@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);
border-radius: 1.5rem; /* 24px */
border: 1px solid var(--glass-border);
}
.gate-card {
border: 1px solid var(--glass-border);
border-radius: 1.75rem; /* 28px */
}
/* Blur is expensive on phones: halve it, and thin out the ice scene. */
@media (max-width: 1023px) {
.glass-panel,
.card,
.sidebar-surface,
.study-chrome,
.tabbar,
.glass,
.gate-card {
@apply backdrop-blur-lg;
}
}
/* No backdrop-filter support: fall back to a more opaque surface. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.glass-panel,
.card,
.sidebar-surface,
.study-chrome,
.tabbar,
.glass,
.gate-card {
background: var(--glass-fallback);
}
}
/* Opt-in hover lift for interactive cards (deck cards). Pointer devices only,
@ -339,10 +415,8 @@ body {
@media (hover: hover) and (pointer: fine) {
.card-hover:hover {
background: var(--card-hover-bg);
border-color: color-mix(in oklab, var(--hairline) 45%, var(--text) 20%);
border-color: var(--nav-active-border);
box-shadow: var(--shadow-card-hover);
}
:root:not(.dark) .card-hover:hover {
transform: translateY(-2px);
}
}
@ -386,14 +460,19 @@ body {
}
.segmented > button[aria-pressed="true"],
.segmented > button[aria-checked="true"] {
background: var(--surface-strong);
color: var(--text);
box-shadow: 0 1px 2px rgb(13 13 13 / 0.12);
background-image: linear-gradient(135deg, #38bdf8 0%, #60a5fa 100%);
color: #06111f;
box-shadow: 0 6px 16px -8px rgb(56 189 248 / 0.7);
}
.dark .segmented > button[aria-pressed="true"],
.dark .segmented > button[aria-checked="true"] {
background: var(--surface-raised);
color: #fff;
background-image: linear-gradient(135deg, #38bdf8 0%, #60a5fa 100%);
color: #06111f;
}
/* Ice gradient for progress bars and selected states. */
.grad-ice {
background-image: linear-gradient(90deg, #38bdf8 0%, #60a5fa 100%);
}
/* Sheet grabber --------------------------------------------------------- */
@ -406,9 +485,7 @@ body {
}
.tabbar {
@apply backdrop-blur-xl;
background: color-mix(in oklab, var(--page) 88%, transparent);
border-top: 1px solid var(--hairline);
border-top: 1px solid var(--glass-border);
}
/* Soft tinted chip for activity types (a hint of colour, not a rainbow) -- */
@ -424,15 +501,10 @@ body {
.dark .tint-rose { background: linear-gradient(140deg, rgb(230 130 150 / 0.26), rgb(230 130 150 / 0.1)); color: #fbd5dd }
/* Sidebar surface: frosted navy glass with a real edge. */
/* Sidebar surface: one tall inset glass panel. */
.sidebar-surface {
@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);
border: 1px solid var(--glass-border);
border-radius: 1.5rem;
}
/* Sidebar nav rows */
@ -454,8 +526,9 @@ body {
}
.nav-row[aria-current="page"],
.nav-row[aria-pressed="true"] {
background: var(--surface-sunken);
background: var(--nav-active-bg);
color: var(--text);
box-shadow: inset 0 0 0 1px var(--nav-active-border), inset 0 1px 0 var(--glass-highlight);
}
/* Shared CTA language: the icon's blue, with a deeper fill in light mode. */
@ -464,35 +537,44 @@ body {
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 999px;
background: #1d61ab;
border-radius: 14px;
background-image: linear-gradient(135deg, #1d4ed8 0%, #1a5fd0 55%, #0ea5e9 100%);
color: #ffffff;
font-weight: 500;
box-shadow: 0 1px 2px rgb(14 21 40 / 0.22);
transition: background-color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
box-shadow: 0 10px 24px -12px rgb(14 165 233 / 0.7);
transition: box-shadow 180ms ease, filter 180ms ease, transform 160ms ease;
}
.btn-primary:hover {
filter: brightness(1.07);
box-shadow: 0 14px 32px -12px rgb(56 189 248 / 0.85);
}
.btn-primary:hover { background: #2478d2; }
.btn-primary:active { transform: scale(0.985); }
.dark .btn-primary {
background: #59a8f7;
color: #081020;
box-shadow: none;
background-image: linear-gradient(135deg, #ffffff 0%, #bfe3ff 100%);
color: #0b1220;
box-shadow: 0 10px 26px -14px rgb(56 189 248 / 0.6);
}
.dark .btn-primary:hover {
filter: brightness(1.03);
box-shadow: 0 14px 34px -14px rgb(56 189 248 / 0.95);
}
.dark .btn-primary:hover { background: #7bbaff; }
.btn-secondary {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 999px;
background: transparent;
border-radius: 14px;
background: var(--surface-strong);
color: var(--text);
border: 1px solid var(--hairline);
border: 1px solid var(--glass-border);
font-weight: 500;
transition: background-color 160ms ease, border-color 160ms ease, transform 160ms ease;
}
.btn-secondary:hover { background: var(--hover); border-color: color-mix(in oklab, var(--hairline) 70%, var(--text) 12%); }
.btn-secondary:hover {
background: var(--hover);
border-color: var(--nav-active-border);
}
.btn-secondary:active { transform: scale(0.985); }
.btn-ghost {
@ -500,7 +582,7 @@ body {
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 0.75rem;
border-radius: 14px;
background: transparent;
color: var(--text-muted);
font-weight: 500;