github-actions
Sync Docker Space
5f3e9f5
@tailwind base;
@tailwind components;
@tailwind utilities;
/* ─── Theme tokens ─────────────────────────────────────────────────────────
Palette is stored as space-separated RGB triplets so Tailwind's
`rgb(var(--brand-500) / <alpha-value>)` 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%); }
}
}