@tailwind base; @tailwind components; @tailwind utilities; /* ─── Theme tokens ───────────────────────────────────────────────────────── Palette is stored as space-separated RGB triplets so Tailwind's `rgb(var(--brand-500) / )` pattern works. The Settings page overrides these at runtime to re-skin the whole app. The remaining tokens (surface / line / text) are also exposed as CSS vars so we can tune the system once and have every surface react. Light and dark are both intentionally near-monochrome — the brand color does the accenting; surfaces stay quiet. */ :root { /* Default brand: refined indigo (#4f46e5 scale). Settings can override. */ --brand-50: 238 242 255; --brand-100: 224 231 255; --brand-200: 199 210 254; --brand-300: 165 180 252; --brand-400: 129 140 248; --brand-500: 99 102 241; --brand-600: 79 70 229; --brand-700: 67 56 202; --brand-800: 55 48 163; --brand-900: 49 46 129; /* Neutral surfaces (light). */ --bg-app: 250 250 252; /* page background */ --bg-surface: 255 255 255; /* cards, sidebar, inputs */ --bg-muted: 246 247 250; /* hover, subtle backgrounds */ --bg-strong: 244 244 248; /* selected rows, kbd */ --line: 226 230 238; /* primary border */ --line-soft: 235 238 245; /* hairline / divider */ --text-strong: 15 23 42; /* headings */ --text: 30 41 59; /* body */ --text-muted: 100 116 139; /* secondary */ --text-faint: 148 163 184; /* tertiary */ } .dark { --bg-app: 9 11 17; --bg-surface: 17 20 27; --bg-muted: 22 26 34; --bg-strong: 28 33 43; --line: 39 45 58; --line-soft: 30 36 48; --text-strong: 248 250 252; --text: 226 232 240; --text-muted: 148 163 184; --text-faint: 100 116 139; } @layer base { html, body, #root { @apply h-full; } html { /* Inter ships with stylistic alternates — `cv11` gives a single-storey `a`, `ss01` evens out punctuation. Both are part of why Linear, Vercel, Stripe, Resend etc. all look the same: it's the Inter feature stack. */ font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11', 'ss01'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; } body { @apply font-sans antialiased; color: rgb(var(--text)); background-color: rgb(var(--bg-app)); } /* Headings get tighter tracking + a hair more weight than the body. */ h1, h2, h3, h4, h5, h6 { @apply font-display tracking-tight; color: rgb(var(--text-strong)); } /* Numbers should align in stat tiles, table cells, durations, etc. */ .tabular, [data-slot='number'] { font-variant-numeric: tabular-nums; } /* Selection uses the brand color at low alpha. */ ::selection { background-color: rgb(var(--brand-500) / 0.18); color: rgb(var(--text-strong)); } /* App scrollbars: thin, near-invisible, lifts on hover. */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-thumb { background-color: rgb(var(--line)); border: 2px solid transparent; background-clip: padding-box; border-radius: 999px; } ::-webkit-scrollbar-thumb:hover { background-color: rgb(var(--text-faint)); background-clip: padding-box; } ::-webkit-scrollbar-track { background: transparent; } } @layer components { /* ─── Surfaces ────────────────────────────────────────────────────────── `.glass` is kept as the primary card surface for backwards compat with every page that already uses the name. The new look is: white surface, 1px hairline, almost-invisible shadow. No glassmorphism — just a quiet, flat card. */ .surface { background-color: rgb(var(--bg-surface)); border: 1px solid rgb(var(--line)); @apply rounded-xl; } .glass { @apply surface shadow-glass; } .glass-strong { @apply surface shadow-glass-lg; } .card { @apply glass p-6; } .card-sm { @apply glass p-4; } .divider { height: 1px; background-color: rgb(var(--line-soft)); } /* ─── Typography helpers ─────────────────────────────────────────────── */ .eyebrow { @apply inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.14em]; color: rgb(var(--text-muted)); } .h-page { @apply font-display text-2xl font-semibold tracking-tight md:text-3xl; color: rgb(var(--text-strong)); } .h-section { @apply font-display text-base font-semibold tracking-tight; color: rgb(var(--text-strong)); } .text-muted { color: rgb(var(--text-muted)); } .text-faint { color: rgb(var(--text-faint)); } /* ─── Buttons ───────────────────────────────────────────────────────── A single `.btn` base, then variants. Every variant has a visible focus ring (a11y), a subtle hover, and an `active:` press state. */ .btn { @apply relative inline-flex select-none items-center justify-center gap-2 rounded-lg px-3.5 py-2 text-sm font-medium leading-none transition duration-150 ease-out disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/60 focus-visible:ring-offset-2 focus-visible:ring-offset-[rgb(var(--bg-app))]; } .btn-primary { @apply btn bg-brand-600 text-white shadow-sm hover:bg-brand-500 active:bg-brand-700; } .btn-secondary { @apply btn text-slate-800 dark:text-slate-100; background-color: rgb(var(--bg-surface)); border: 1px solid rgb(var(--line)); } .btn-secondary:hover { background-color: rgb(var(--bg-muted)); } .btn-secondary:active { background-color: rgb(var(--bg-strong)); } .btn-ghost { @apply btn bg-transparent; color: rgb(var(--text-muted)); } .btn-ghost:hover { color: rgb(var(--text-strong)); background-color: rgb(var(--bg-muted)); } .btn-danger { @apply btn bg-rose-600 text-white shadow-sm hover:bg-rose-500 active:bg-rose-700 focus-visible:ring-rose-400/60; } .btn-sm { @apply px-2.5 py-1.5 text-xs; } .btn-lg { @apply px-5 py-2.5 text-[15px]; } /* ─── Form controls ─────────────────────────────────────────────────── */ .input, .textarea, .select { @apply w-full rounded-lg px-3.5 py-2.5 text-sm leading-snug transition-colors duration-150 placeholder:text-[rgb(var(--text-faint))] focus:outline-none; background-color: rgb(var(--bg-surface)); border: 1px solid rgb(var(--line)); color: rgb(var(--text-strong)); } .input:hover, .textarea:hover, .select:hover { border-color: rgb(var(--text-faint) / 0.7); } .input:focus, .textarea:focus, .select:focus { @apply ring-2 ring-brand-400/40; border-color: rgb(var(--brand-500)); } .label { @apply mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.12em]; color: rgb(var(--text-muted)); } /* ─── Badges / pills ─────────────────────────────────────────────────── */ .badge { @apply inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium leading-none; border: 1px solid rgb(var(--line)); background-color: rgb(var(--bg-muted)); color: rgb(var(--text-muted)); height: 22px; } .badge-success { @apply badge border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300; } .badge-running { @apply badge border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-500/30 dark:bg-sky-500/10 dark:text-sky-300; } .badge-error { @apply badge border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-300; } .badge-warning { @apply badge border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200; } .badge-info { @apply badge border-indigo-200 bg-indigo-50 text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300; } .badge-neutral { @apply badge; } /* ─── Misc ──────────────────────────────────────────────────────────── */ .kbd { @apply inline-flex h-5 items-center rounded border px-1.5 font-mono text-[10px] font-medium; background-color: rgb(var(--bg-strong)); border-color: rgb(var(--line)); color: rgb(var(--text-muted)); } /* Subtle dotted background used behind hero / empty states. */ .dot-grid { background-image: radial-gradient(rgb(var(--line)) 1px, transparent 1px); background-size: 22px 22px; background-position: -1px -1px; } } @layer utilities { /* Centered max-width container used by list / dashboard pages. */ .container-page { @apply mx-auto w-full max-w-6xl; } /* Narrower container for single-task / form-driven pages (wizards, settings, single-form tools). */ .container-form { @apply mx-auto w-full max-w-5xl; } /* Sticky action bar used at the bottom of long forms / wizards. Sits flush with the viewport bottom, gets a hairline top border and a soft surface tint so it reads above the form content. */ .sticky-action-bar { @apply sticky bottom-0 z-10 -mx-4 mt-4 flex flex-wrap items-center gap-3 border-t px-4 py-3 backdrop-blur md:-mx-10 md:px-10; border-color: rgb(var(--line)); background-color: rgb(var(--bg-app) / 0.85); } /* Subtle entrance animation for the main column. */ @keyframes app-fade-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } } .animate-app-fade-in { animation: app-fade-in 240ms ease-out both; } /* Shimmer highlight used by the progress bar while a run is working. */ @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } } }