freezmo/app/globals.css
Martin Dvorak fee59b9577 Polish the API-key gate
- Add a theme toggle to the gate (sun/moon ghost button next to the help
  button); it switches instantly and persists to localStorage.
- Strengthen the corner glows so the stage reads atmospheric in both themes
  (bigger, slightly stronger radial gradients behind the card).
- Center the logo, heading, subtitle and footnote inside the card.
- Dark mode: replace the solid white logo tile with a subtle raised tile
  (thin border, light snowflake); make the card only slightly lighter than
  the near-black page with a low-opacity border and soft shadow.
- Disabled Continue keeps the primary fill at ~45% opacity with a crisp
  label instead of a faded muddy one; fully solid once text is entered.
- Bigger 36px corner buttons and a visible "How do I get a key?" text link
  under the input that opens the same modal.
- Shorten and balance the reassurance line.
- Help modal: step circles aligned to the first text line, emphasised
  free-tier callout with a check icon, underlined links with an
  external-link icon, muted medium-weight UI labels, 36px close button
  aligned to the title.
- Gate test extended to 44 checks (theme toggle, text link, modal polish).
2026-10-07 05:34:32 +00:00

529 lines
17 KiB
CSS

@import "tailwindcss";
/* Class-based dark mode (toggled on <html> by the theme script). */
@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;
/* 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;
/* 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;
--color-night-50: #f6f9fc;
--color-night-700: #1d2733;
--color-night-800: #151d27;
--color-night-900: #101720;
--color-night-950: #0a1017;
--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: #ffffff;
--sidebar: #f9f9f9;
--surface: #ffffff;
--surface-raised: #ffffff;
--surface-sunken: rgb(13 13 13 / 0.05);
--hairline: #e5e5e5;
--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 16px -14px rgb(13 13 13 / 0.28);
/* API-key gate: near-white stage with soft blank glows, tinted input. */
--gate-base: #f6f6f8;
--gate-glow-a: rgb(88 106 132 / 0.16);
--gate-glow-b: rgb(126 140 160 / 0.12);
--gate-card: #ffffff;
--gate-card-border: #e7e7e9;
--gate-callout-border: #d9d9dc;
--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);
--shadow-card: 0 1px 2px rgb(13 13 13 / 0.06), 0 1px 3px rgb(13 13 13 / 0.04);
--shadow-card-hover: 0 10px 28px -14px rgb(13 13 13 / 0.22), 0 1px 2px rgb(13 13 13 / 0.06);
}
.dark {
color-scheme: dark;
--page: #212121;
--sidebar: #171717;
--surface: #2f2f2f;
--surface-raised: #383838;
--surface-sunken: rgb(255 255 255 / 0.08);
--hairline: rgb(255 255 255 / 0.14);
--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 */
--hover: rgb(255 255 255 / 0.07);
--sidebar-top: #1a1a1a;
--sidebar-bottom: #151515;
--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: #0c0c0d;
--gate-glow-a: rgb(255 255 255 / 0.14);
--gate-glow-b: rgb(255 255 255 / 0.09);
--gate-card: #171718;
--gate-card-border: rgb(255 255 255 / 0.08);
--gate-callout-border: rgb(255 255 255 / 0.2);
--field: #242425;
--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);
--shadow-card: 0 1px 2px rgb(0 0 0 / 0.25);
--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). */
: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;
}
.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;
}
html,
body {
height: 100%;
}
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: monochrome radial glows in opposite corners over a
near-black (dark) or near-white (light) base. Radial gradients are the
softest "blurred glow" available without extra layers. */
.gate-bg {
background:
radial-gradient(72rem 54rem at 108% -14%, var(--gate-glow-a), transparent 70%),
radial-gradient(64rem 48rem at -14% 112%, var(--gate-glow-b), transparent 72%),
var(--gate-base);
}
.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.16;
}
@layer components {
/* Glass primitives ------------------------------------------------------ */
.glass-panel {
@apply backdrop-blur-xl;
background: var(--surface);
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, white 68%, transparent);
border-bottom: 1px solid var(--hairline);
}
.dark .glass {
background: color-mix(in oklab, #101720 70%, transparent);
}
.card {
border-radius: 1.125rem; /* 18px */
background: var(--surface);
border: 1px solid var(--hairline);
box-shadow: var(--shadow-card);
}
/* Opt-in hover lift for interactive cards (deck cards) */
.card-hover {
transition: box-shadow 180ms ease, border-color 180ms ease, transform 180ms ease;
}
.card-hover:hover {
background: var(--surface-raised);
border-color: color-mix(in oklab, var(--hairline) 55%, var(--text) 18%);
box-shadow: var(--shadow-card-hover);
}
.card-hover:active {
transform: translateY(0);
}
/* Inset grouped list ---------------------------------------------------- */
.list-group {
border-radius: 1rem;
@apply backdrop-blur-lg backdrop-saturate-150;
background: var(--surface);
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: #383838;
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: a touch darker than the page, with a real edge. */
.sidebar-surface {
background: linear-gradient(180deg, var(--sidebar-top), var(--sidebar-bottom));
border-right: 1px solid var(--hairline);
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: near-black pill in light mode, white pill in dark. */
.btn-primary {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 999px;
background: #0d0d0d;
color: #ffffff;
font-weight: 500;
box-shadow: 0 1px 2px rgb(13 13 13 / 0.18);
transition: background-color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.btn-primary:hover { background: #2f2f2f; }
.btn-primary:active { transform: scale(0.985); }
.dark .btn-primary {
background: #ffffff;
color: #0d0d0d;
box-shadow: none;
}
.dark .btn-primary:hover { background: #e5e5e5; }
.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: #8a5e00; }
.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 }
}
}
/* 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;
}
}