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.
598 lines
19 KiB
CSS
598 lines
19 KiB
CSS
@import "tailwindcss";
|
|
|
|
/* Class-based dark mode (toggled on <html> by the theme script). */
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
|
|
@theme {
|
|
/* 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: 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;
|
|
--color-aurora-sky: #9ccdf5;
|
|
--color-aurora-lilac: #c3b6f0;
|
|
--color-aurora-peach: #f6cdb4;
|
|
|
|
--font-sans: -apple-system, BlinkMacSystemFont, ui-sans-serif, system-ui,
|
|
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
|
"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
|
|
|
|
--shadow-frost: 0 14px 34px -22px rgb(13 13 13 / 0.28);
|
|
--shadow-lift: 0 4px 14px -8px rgb(13 13 13 / 0.18);
|
|
--shadow-hairline: 0 1px 0 0 rgb(13 13 13 / 0.04);
|
|
|
|
--animate-float: float 11s ease-in-out infinite;
|
|
--animate-pop: pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
--animate-wiggle: wiggle 0.4s ease-in-out;
|
|
--animate-rise: rise 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
--animate-sheet: sheet 0.32s cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
}
|
|
|
|
@keyframes float {
|
|
0%, 100% { transform: translate3d(0, 0, 0) scale(1) }
|
|
50% { transform: translate3d(0, -18px, 0) scale(1.04) }
|
|
}
|
|
@keyframes pop {
|
|
0% { transform: scale(0.92) }
|
|
60% { transform: scale(1.02) }
|
|
100% { transform: scale(1) }
|
|
}
|
|
@keyframes wiggle {
|
|
0%, 100% { transform: translateX(0) }
|
|
25% { transform: translateX(-5px) }
|
|
75% { transform: translateX(5px) }
|
|
}
|
|
@keyframes rise {
|
|
from { opacity: 0; transform: translateY(10px) }
|
|
to { opacity: 1; transform: translateY(0) }
|
|
}
|
|
@keyframes sheet {
|
|
from { opacity: 0; transform: translateY(24px) }
|
|
to { opacity: 1; transform: translateY(0) }
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
Theme tokens.
|
|
Light: ChatGPT-ish neutral (page #ffffff, sidebar #f9f9f9, border #e5e5e5).
|
|
Dark: page #212121, sidebar #171717, surfaces #2f2f2f, borders rgba(255,255,255,.1).
|
|
Text tiers are picked to clear WCAG AA (4.5:1) on their own background.
|
|
--------------------------------------------------------------------------- */
|
|
:root {
|
|
color-scheme: light;
|
|
--page: #f4f7fd;
|
|
--sidebar: #e9eff9;
|
|
--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);
|
|
--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: #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(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: #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.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: #0b1120;
|
|
--sidebar-bottom: #080d18;
|
|
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.75);
|
|
/* 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). */
|
|
--install-sheet: #1c1c1e;
|
|
--install-card: #2c2c2e;
|
|
--install-chip: #3a3a3c;
|
|
--install-border: rgb(255 255 255 / 0.08);
|
|
--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);
|
|
}
|
|
|
|
/* Backdrop: the icon's navy base and blue wash, seen through frosted glass. */
|
|
: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;
|
|
}
|
|
.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;
|
|
}
|
|
|
|
html,
|
|
body {
|
|
height: 100%;
|
|
overscroll-behavior: none;
|
|
}
|
|
|
|
body {
|
|
font-family: var(--font-sans);
|
|
-webkit-tap-highlight-color: transparent;
|
|
-webkit-font-smoothing: antialiased;
|
|
text-rendering: optimizeLegibility;
|
|
/* Matches the backdrop so iOS rubber-banding never shows a seam. */
|
|
background-color: var(--page);
|
|
color: var(--text);
|
|
overscroll-behavior-y: none;
|
|
}
|
|
|
|
.dark body {
|
|
color: var(--color-frost-100);
|
|
}
|
|
|
|
/* The page backdrop now lives in ONE viewport-fixed layer (see .app-backdrop)
|
|
so it can never stretch, scroll or be clipped by a content column.
|
|
`.frost-bg` stays as a transparent wrapper for pages that used it. */
|
|
.frost-bg {
|
|
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);
|
|
}
|
|
|
|
.app-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: -10;
|
|
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%);
|
|
}
|
|
|
|
/* Two slow, very soft blobs for a little life behind the glass. */
|
|
.app-backdrop::before,
|
|
.app-backdrop::after {
|
|
content: "";
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
filter: blur(110px);
|
|
opacity: 0.22;
|
|
}
|
|
.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;
|
|
}
|
|
.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;
|
|
}
|
|
.dark .app-backdrop::before,
|
|
.dark .app-backdrop::after {
|
|
opacity: 0.3;
|
|
}
|
|
|
|
@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);
|
|
}
|
|
|
|
.glass-chip {
|
|
@apply backdrop-blur-md;
|
|
background: var(--surface-sunken);
|
|
border: 1px solid transparent;
|
|
}
|
|
.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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
/* Opt-in hover lift for interactive cards (deck cards). Pointer devices only,
|
|
so touch screens never get sticky hover states. */
|
|
.card-hover {
|
|
transition: background-color 180ms ease, box-shadow 180ms ease, border-color 180ms ease, transform 180ms ease;
|
|
}
|
|
@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%);
|
|
box-shadow: var(--shadow-card-hover);
|
|
}
|
|
:root:not(.dark) .card-hover:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
}
|
|
.card-hover:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Inset grouped list ---------------------------------------------------- */
|
|
.list-group {
|
|
border-radius: 1rem;
|
|
@apply backdrop-blur-lg backdrop-saturate-150;
|
|
background: color-mix(in oklab, var(--surface) 82%, transparent);
|
|
border: 1px solid var(--hairline);
|
|
overflow: hidden;
|
|
}
|
|
.list-group > * + * {
|
|
border-top: 1px solid var(--hairline-soft);
|
|
}
|
|
|
|
/* iOS segmented control ------------------------------------------------- */
|
|
.segmented {
|
|
display: flex;
|
|
gap: 2px;
|
|
padding: 3px;
|
|
border-radius: 0.95rem;
|
|
@apply backdrop-blur-sm;
|
|
background: var(--surface-sunken);
|
|
border: 1px solid var(--hairline);
|
|
}
|
|
.segmented > button {
|
|
flex: 1;
|
|
border-radius: 0.75rem;
|
|
padding: 0.42rem 0.5rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--color-frost-700);
|
|
transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
|
|
}
|
|
.dark .segmented > button {
|
|
color: var(--color-frost-300);
|
|
}
|
|
.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);
|
|
}
|
|
.dark .segmented > button[aria-pressed="true"],
|
|
.dark .segmented > button[aria-checked="true"] {
|
|
background: var(--surface-raised);
|
|
color: #fff;
|
|
}
|
|
|
|
/* Sheet grabber --------------------------------------------------------- */
|
|
.sheet-grabber {
|
|
width: 36px;
|
|
height: 5px;
|
|
border-radius: 999px;
|
|
background: var(--hairline-soft);
|
|
margin: 0 auto 0.75rem;
|
|
}
|
|
|
|
.tabbar {
|
|
@apply backdrop-blur-xl;
|
|
background: color-mix(in oklab, var(--page) 88%, transparent);
|
|
border-top: 1px solid var(--hairline);
|
|
}
|
|
|
|
/* Soft tinted chip for activity types (a hint of colour, not a rainbow) -- */
|
|
.tint-sky { background: linear-gradient(140deg, rgb(156 205 245 / 0.34), rgb(156 205 245 / 0.14)); color: #1c4e72 }
|
|
.tint-mint { background: linear-gradient(140deg, rgb(159 227 208 / 0.4), rgb(159 227 208 / 0.16)); color: #1d5c4c }
|
|
.tint-lilac { background: linear-gradient(140deg, rgb(195 182 240 / 0.4), rgb(195 182 240 / 0.16)); color: #47397f }
|
|
.tint-peach { background: linear-gradient(140deg, rgb(246 205 180 / 0.45), rgb(246 205 180 / 0.18)); color: #7d4522 }
|
|
.tint-rose { background: linear-gradient(140deg, rgb(246 178 190 / 0.4), rgb(246 178 190 / 0.16)); color: #7c2b3d }
|
|
.dark .tint-sky { background: linear-gradient(140deg, rgb(74 164 220 / 0.26), rgb(74 164 220 / 0.1)); color: #bfe2fb }
|
|
.dark .tint-mint { background: linear-gradient(140deg, rgb(88 201 173 / 0.26), rgb(88 201 173 / 0.1)); color: #bdf0e2 }
|
|
.dark .tint-lilac { background: linear-gradient(140deg, rgb(160 140 235 / 0.26), rgb(160 140 235 / 0.1)); color: #ddd5fb }
|
|
.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: frosted navy glass with a real edge. */
|
|
.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);
|
|
}
|
|
|
|
/* Sidebar nav rows */
|
|
.nav-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.625rem;
|
|
width: 100%;
|
|
border-radius: 0.625rem;
|
|
padding: 0.5rem 0.625rem;
|
|
font-size: 13.5px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
transition: background-color 140ms ease, color 140ms ease;
|
|
}
|
|
.nav-row:hover {
|
|
background: var(--hover);
|
|
color: var(--text);
|
|
}
|
|
.nav-row[aria-current="page"],
|
|
.nav-row[aria-pressed="true"] {
|
|
background: var(--surface-sunken);
|
|
color: var(--text);
|
|
}
|
|
|
|
/* 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: #1d61ab;
|
|
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;
|
|
}
|
|
.btn-primary:hover { background: #2478d2; }
|
|
.btn-primary:active { transform: scale(0.985); }
|
|
.dark .btn-primary {
|
|
background: #59a8f7;
|
|
color: #081020;
|
|
box-shadow: none;
|
|
}
|
|
.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;
|
|
color: var(--text);
|
|
border: 1px solid var(--hairline);
|
|
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:active { transform: scale(0.985); }
|
|
|
|
.btn-ghost {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
border-radius: 0.75rem;
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
font-weight: 500;
|
|
transition: background-color 160ms ease, color 160ms ease;
|
|
}
|
|
.btn-ghost:hover { background: var(--hover); color: var(--text); }
|
|
|
|
/* Difficulty: neutral pill, colour lives in the text only. */
|
|
.diff-easy { color: #16714f; }
|
|
.diff-medium { color: #7a5300; }
|
|
.diff-hard { color: #b4342f; }
|
|
.dark .diff-easy { color: #6fd3a8; }
|
|
.dark .diff-medium { color: #e3b341; }
|
|
.dark .diff-hard { color: #f08a84; }
|
|
|
|
/* Loading skeleton */
|
|
.skeleton {
|
|
position: relative;
|
|
overflow: hidden;
|
|
background: var(--surface-sunken);
|
|
border-radius: 0.75rem;
|
|
}
|
|
.skeleton::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
transform: translateX(-100%);
|
|
background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.45), transparent);
|
|
animation: shimmer 1.6s infinite;
|
|
}
|
|
.dark .skeleton::after {
|
|
background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.06), transparent);
|
|
}
|
|
@keyframes shimmer {
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
.text-balance {
|
|
text-wrap: balance;
|
|
}
|
|
|
|
/* Focus rings ------------------------------------------------------------ */
|
|
:where(button, a, input, textarea, [tabindex]):focus-visible {
|
|
outline: 2.5px solid var(--color-accent-500);
|
|
outline-offset: 2px;
|
|
border-radius: 0.75rem;
|
|
}
|
|
|
|
/* Scrollbars ------------------------------------------------------------- */
|
|
.thin-scroll {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: color-mix(in oklab, var(--color-frost-400) 55%, transparent) transparent;
|
|
}
|
|
.thin-scroll::-webkit-scrollbar { width: 8px; height: 8px }
|
|
.thin-scroll::-webkit-scrollbar-thumb {
|
|
background: color-mix(in oklab, var(--color-frost-400) 45%, transparent);
|
|
border-radius: 999px;
|
|
}
|
|
.no-scrollbar::-webkit-scrollbar { display: none }
|
|
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none }
|
|
|
|
/* Mobile safe areas ------------------------------------------------------ */
|
|
.pt-safe { padding-top: max(env(safe-area-inset-top), 0.75rem) }
|
|
.pb-safe { padding-bottom: max(env(safe-area-inset-bottom), 0.75rem) }
|
|
.pb-tabbar { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 5.25rem) }
|
|
.pb-cta { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 9rem) }
|
|
|
|
@media (display-mode: standalone) {
|
|
.standalone-hide { display: none }
|
|
}
|
|
}
|
|
|
|
/* Mobile form fields ----------------------------------------------------- */
|
|
/* iOS Safari zooms the whole page when a focused field's text is under 16px
|
|
(it ignores user-scalable=no for that). Deliberately unlayered so it wins
|
|
over the per-component Tailwind sizes, and only on phone-sized screens. */
|
|
@media (max-width: 639px) {
|
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
|
|
textarea,
|
|
select {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
|
|
/* Reduced motion --------------------------------------------------------- */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.001ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.001ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|