@import "tailwindcss"; /* Class-based dark mode (toggled on 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; } }