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:
parent
586e4b5a40
commit
52cce42cde
20 changed files with 383 additions and 262 deletions
|
|
@ -43,7 +43,7 @@ export default function DeckPage() {
|
|||
🧊
|
||||
</p>
|
||||
<h1 className="text-xl font-extrabold text-frost-950 dark:text-frost-50">Deck not found</h1>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">
|
||||
<p className="text-sm text-frost-600 dark:text-frost-300">
|
||||
This deck isn't in this browser's library. It may have been deleted, or it was
|
||||
created in a different browser profile.
|
||||
</p>
|
||||
|
|
|
|||
486
app/globals.css
486
app/globals.css
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Metadata, Viewport } from "next";
|
||||
|
||||
import { IceBackdrop } from "@/components/IceBackdrop";
|
||||
import { InstallSheet } from "@/components/InstallSheet";
|
||||
import { ServiceWorkerRegistrar } from "@/components/ServiceWorkerRegistrar";
|
||||
import "./globals.css";
|
||||
|
|
@ -74,7 +75,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||
<script dangerouslySetInnerHTML={{ __html: installScript }} />
|
||||
</head>
|
||||
<body className="min-h-full antialiased">
|
||||
<div className="app-backdrop" aria-hidden />
|
||||
<IceBackdrop />
|
||||
{children}
|
||||
<InstallSheet />
|
||||
<ServiceWorkerRegistrar />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue