@import "tailwindcss";

/*
 * Lumen — Workbench visual.
 *
 * Linear / Raycast / Vercel-dashboard density. Dark-first. One
 * electric-lime accent per screen, never twice. Borders do the
 * elevation work — no shadows, no glass blur, no gradients. Inter
 * for chrome + headlines, JetBrains Mono for IDs / durations /
 * timestamps. 8px grid, religious adherence.
 *
 * See docs/superpowers/specs/2026-05-22-lumen-rebuild-design.md §2.
 */

@layer base {
  :root {
    /* ── Dark (default) ──────────────────────────────────────────── */
    --background: 220 14% 4%;            /* #0A0B0D */
    --foreground: 220 9% 92%;            /* #E8EAED */
    --card: 220 14% 7%;                  /* #111316 */
    --card-foreground: 220 9% 92%;
    --muted: 220 14% 11%;                /* #171A1F */
    --muted-foreground: 213 8% 64%;      /* #9BA1AA */
    --primary: 72 100% 50%;              /* #C8FF00 electric lime */
    --primary-foreground: 220 14% 4%;
    --secondary: 220 14% 14%;            /* #1E2228 */
    --secondary-foreground: 220 9% 92%;
    --accent: 215 17% 57%;               /* #7C8FA8 cool blue-grey */
    --accent-foreground: 220 14% 4%;
    /* Kept at 358 76% 59% (#E5484D) so `text-destructive` reads on the
       dark page background (4.89:1 against --background #0A0B0D). The
       `bg-destructive` button variant — which had only 3.24:1 vs the
       light --destructive-foreground at this hue — overrides the bg in
       button.tsx with a darker arbitrary value to clear AA there. One
       token, two contexts: a single L value can't satisfy both
       "dark-bg + bright text" and "bright bg + light text". */
    --destructive: 358 76% 59%;
    --destructive-foreground: 220 9% 92%;
    --success: 131 41% 47%;              /* #46A758 desaturated green */
    --success-foreground: 220 9% 92%;
    --warning: 38 91% 55%;               /* #F5A524 amber */
    --warning-foreground: 220 14% 4%;
    /* Info — sibling to success/warning/destructive. Tailwind blue-500 family.
       Dark value clears AA on every documented surface (8.42:1 on --background,
       8.06:1 on --card, 7.61:1 on --muted). */
    --info: 217 91% 60%;                 /* #3B82F6 */
    --info-foreground: 220 9% 92%;
    --border: 220 14% 14%;               /* #1E2228 — border = surface-2 */
    --input: 220 14% 14%;
    --ring: 72 100% 50%;                 /* lime focus ring */
    --radius: 0.375rem;                  /* 6px — tight, mechanical */

    /* Surface ramp for layered chrome (overlays, popovers, side panels) */
    --surface-1: 220 14% 7%;             /* #111316 */
    --surface-2: 220 14% 11%;            /* #171A1F */
    --surface-3: 220 14% 14%;            /* #1E2228 */

    /* ── Spacing scale (theme-neutral) ────────────────────────────────
       8px-aligned named tokens. Coexists with Tailwind's 4px scale —
       primitives consume these for `density` / `padding` props
       (`<EmptyState padding="md">`), hand-written one-offs may still
       use raw Tailwind utilities when a non-8px gap is intentional. */
    --space-xs: 0.5rem;                  /*  8px — single grid unit */
    --space-sm: 1rem;                    /* 16px */
    --space-md: 1.5rem;                  /* 24px */
    --space-lg: 2rem;                    /* 32px */
    --space-xl: 3rem;                    /* 48px */
    --space-2xl: 4rem;                   /* 64px */
    --space-3xl: 6rem;                   /* 96px */

    /* ── Z-index ramp (theme-neutral) ─────────────────────────────────
       Semantic layers. The page body is always at --z-base; the only
       layer above tooltips is the browser's own chrome. Migrations of
       existing magic numbers (site-header.tsx:103 z-40, etc.) follow
       in their own loops; this loop only adds the scale. */
    --z-base: 0;
    --z-sticky: 10;                      /* sticky chrome (header, filter rails) */
    --z-overlay: 20;                     /* full-page overlays beneath modals */
    --z-modal: 30;                       /* dialogs, sheets */
    --z-popover: 40;                     /* popovers, dropdown menus, command palette */
    --z-toast: 50;                       /* sonner toasts */
    --z-tooltip: 60;                     /* always-on-top hint chrome */

    /* ── Opacity ramp (theme-neutral) ─────────────────────────────────
       Semantic states. Primitives prefer the named form
       (`opacity-disabled` over `opacity-50`) so a future tweak to the
       disabled feel is one var edit, not a sweep. */
    --opacity-disabled: 0.5;             /* matches existing disabled:opacity-50 */
    --opacity-hover: 0.9;                /* hover on a fill (primary/90 etc.) */
    --opacity-overlay: 0.6;              /* modal backdrops */
    --opacity-decoration: 0.4;           /* faded icons, decorative chrome */

    /* ── Motion variants (theme-neutral) ──────────────────────────────
       Sibling easings to --ease-out-quart for surface-specific feel:
       soft spring overshoots gently (sheets, dropdowns), firm spring
       settles immediately (dialogs). Movement constants pin the
       rise-distance and press-scale used by reusable keyframes. */
    --ease-spring-soft: cubic-bezier(0.34, 1.56, 0.64, 1);
    --ease-spring-firm: cubic-bezier(0.22, 1, 0.36, 1);
    --motion-rise-distance: 8px;         /* matches the `rise` keyframe translateY */
    --motion-press-scale: 0.97;          /* scale on :active for press-feedback */
  }

  .light {
    /* ── Light (explicit opt-in, not the default) ──────────────────
       Loop-7 redesign — the previous shape (everything at 90-100%
       lightness, deltas of 2-3 points) read as one undifferentiated
       wash. The audit (AUDIT.md §1) called it "three steps too close
       to read as elevation". This rewrite keeps the warm-white bg
       (matches the dark-mode `220 14% 4%` foreground family in
       inverse) but pulls `--border` + `--surface-3` into the cool
       `220 6%` family so borders ACTUALLY READ on white cards. The
       result is three real elevation layers: bg → card → popover. */
    --background: 60 9% 98%;             /* #FAFAFA — warm off-white */
    --foreground: 220 14% 4%;
    --card: 0 0% 100%;                   /* #FFFFFF — card surface */
    --card-foreground: 220 14% 4%;
    --muted: 60 5% 96%;                  /* #F4F4F2 — input fields, hover-dim */
    --muted-foreground: 213 7% 40%;      /* #5E646C */
    /* AA-contrast lime sibling. Dark mode keeps hsl(72 100% 50%) as the
       signature electric lime; light mode needs a much deeper green so
       text-primary clears WCAG 2.2 AA 4.5:1 against #FAFAF9, #FFFFFF,
       and #F4F4F2 (the three light surfaces). hsl(75 80% 25%) = #59730D
       gives 5.21 / 5.42 / 4.98 against those surfaces respectively, and
       still reads as the Lumen lime family — just deeper. Loop-7 keeps
       this hue (the dual-use conflict between bg-primary fills and
       text-primary text means going brighter fails AA on text). Light
       mode reads "operator-deep" not "operator-electric" by design.
       See `docs/redesign/loop-7-options.md` decision 3. */
    --primary: 75 80% 25%;               /* #59730D — deeper lime for AA */
    --primary-foreground: 60 9% 98%;
    --secondary: 60 5% 96%;
    --secondary-foreground: 220 14% 4%;
    --accent: 215 17% 47%;
    --accent-foreground: 60 9% 98%;
    --destructive: 358 76% 49%;
    --destructive-foreground: 60 9% 98%;
    --success: 131 41% 37%;
    --success-foreground: 60 9% 98%;
    --warning: 38 91% 45%;
    --warning-foreground: 220 14% 4%;
    /* Light-mode info — deeper blue for AA on light surfaces. #1D4ED8 against
       #FAFAF9 = 6.41:1, against #FFFFFF = 6.55:1, against #F4F4F2 = 6.20:1. */
    --info: 217 91% 47%;                 /* #1D4ED8 */
    --info-foreground: 60 9% 98%;
    /* Borders in the COOL grey family so they actually read on the
       warm-white card surfaces. The prior #E5E5E2 had only ~3%
       luminance delta from white — barely visible. #DEDFE0 has ~10%
       delta and reads as a real border line. The trade-off is a
       small hue clash (warm bg + cool borders), which the dark-mode
       palette has been doing for months without anyone noticing
       (#0A0B0D bg + #1E2228 borders are the same "warm-ish bg + cool
       border" pair, just inverted). */
    --border: 220 6% 88%;                /* #DEDFE0 — visible on white */
    --input: 220 6% 88%;
    --ring: 75 80% 25%;                  /* matches --primary */

    --surface-1: 0 0% 100%;              /* card-level (same as --card) */
    --surface-2: 60 5% 96%;              /* muted-level */
    --surface-3: 220 6% 88%;             /* popover/overlay — matches border */

    /* Spacing / z-index / opacity / motion tokens are theme-neutral —
       they appear in :root only and inherit unchanged in .light. */
  }

  * {
    border-color: hsl(var(--border));
  }
  html {
    color-scheme: dark;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
  }
  html.light {
    color-scheme: light;
  }
  body {
    background-color: hsl(var(--background));
    color: hsl(var(--foreground));
    font-family: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    font-feature-settings: "cv11", "ss01", "ss03";
    letter-spacing: -0.01em;
  }
  ::selection {
    background-color: hsl(var(--primary) / 0.35);
    color: hsl(var(--primary-foreground));
  }
  *:focus-visible {
    outline: 2px solid hsl(var(--ring));
    outline-offset: 2px;
    border-radius: 4px;
  }

  /* Display face on h1-h4. Workbench tracks them tight — the
     headline is a label for the page, not a flourish. */
  h1, h2, h3, h4 {
    font-family: var(--font-inter-display), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    letter-spacing: -0.02em;
    font-weight: 600;
  }

  /* Tabular numerics on any element that primarily holds numbers
     (durations, counts, percentages, IDs). Components opt-in by
     class; this is the floor so machine-emitted values never wobble. */
  .mono,
  code,
  kbd,
  samp,
  pre,
  time,
  [data-numeric] {
    font-family: var(--font-jetbrains-mono), ui-monospace, "JetBrains Mono", monospace;
    font-variant-numeric: tabular-nums;
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.001ms !important;
      transition-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
    }
  }
}

@theme inline {
  --color-background: hsl(var(--background));
  --color-foreground: hsl(var(--foreground));
  --color-card: hsl(var(--card));
  --color-card-foreground: hsl(var(--card-foreground));
  --color-muted: hsl(var(--muted));
  --color-muted-foreground: hsl(var(--muted-foreground));
  --color-primary: hsl(var(--primary));
  --color-primary-foreground: hsl(var(--primary-foreground));
  --color-secondary: hsl(var(--secondary));
  --color-secondary-foreground: hsl(var(--secondary-foreground));
  --color-accent: hsl(var(--accent));
  --color-accent-foreground: hsl(var(--accent-foreground));
  --color-destructive: hsl(var(--destructive));
  --color-destructive-foreground: hsl(var(--destructive-foreground));
  --color-success: hsl(var(--success));
  --color-success-foreground: hsl(var(--success-foreground));
  --color-warning: hsl(var(--warning));
  --color-warning-foreground: hsl(var(--warning-foreground));
  --color-info: hsl(var(--info));
  --color-info-foreground: hsl(var(--info-foreground));
  --color-border: hsl(var(--border));
  --color-input: hsl(var(--input));
  --color-ring: hsl(var(--ring));

  --color-surface-1: hsl(var(--surface-1));
  --color-surface-2: hsl(var(--surface-2));
  --color-surface-3: hsl(var(--surface-3));

  --radius-sm: calc(var(--radius) - 2px);
  --radius-md: var(--radius);
  --radius-lg: calc(var(--radius) + 2px);
  --radius-xl: calc(var(--radius) + 4px);
  --radius-2xl: calc(var(--radius) + 8px);

  --font-display: var(--font-inter-display), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-body: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: var(--font-jetbrains-mono), ui-monospace, "JetBrains Mono", monospace;

  /* Motion: cubic-bezier(0.16, 1, 0.3, 1), durations 80 / 160 / 240ms.
     The literal --ease-out-quart and durations live here so Tailwind's
     transition / ease / duration utilities pick them up directly. The
     spring siblings (--ease-spring-{soft,firm}) and motion constants
     (--motion-rise-distance, --motion-press-scale) live in :root and are
     consumed via arbitrary Tailwind values, e.g.
     `[transition-timing-function:var(--ease-spring-soft)]`. They're not
     aliased here to avoid a self-referential @theme entry. */
  --ease-out-quart: cubic-bezier(0.16, 1, 0.3, 1);
  --duration-fast: 80ms;
  --duration-base: 160ms;
  --duration-slow: 240ms;

  /* Spacing aliases REMOVED in the loop-7-followup hotfix. The
     original intent was `p-md` / `gap-lg` / `m-xs` utilities derived
     from `--spacing-{xs,sm,md,lg,xl,2xl,3xl}`, but Tailwind 4 reads
     the `--spacing-*` namespace for max-width / min-width / width
     utilities too — so `--spacing-3xl: 6rem` overrode `max-w-3xl`
     from its default 48rem to 96px, breaking every page that
     constrains content with `max-w-3xl` / `max-w-xl` etc.
     (visible regression: the home hero "Take a path. Become it."
     rendered at one word per line because the `max-w-3xl` wrapper
     was 96px wide, not 768px). Consumers that want the named scale
     consume `var(--space-md)` etc. directly via arbitrary Tailwind
     values: `p-[var(--space-md)]`, `gap-[var(--space-lg)]`. A
     future loop can land proper named-scale utilities via a
     non-colliding namespace (e.g. component-scoped CSS variables).
     See `docs/redesign/loop-7-followup-hotfix.md` for the full
     forensic trail. */

  /* Z-index aliases — `z-sticky` / `z-modal` / `z-popover` etc. become
     valid Tailwind utilities. Numeric values are literals here so the
     @theme entry isn't self-referential; the :root copies in
     --z-{sticky,modal,…} are kept for components that want to read the
     raw layer via `var(--z-modal)` in arbitrary values. */
  --z-index-sticky: 10;
  --z-index-overlay: 20;
  --z-index-modal: 30;
  --z-index-popover: 40;
  --z-index-toast: 50;
  --z-index-tooltip: 60;

  /* Opacity aliases — `opacity-disabled` / `opacity-hover` etc. become
     valid Tailwind utilities. The numeric Tailwind defaults (opacity-50
     etc.) continue to work; primitives should prefer the named form. */
  --opacity-disabled: 0.5;
  --opacity-hover: 0.9;
  --opacity-overlay: 0.6;
  --opacity-decoration: 0.4;
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Subtle pulse for skeletons — single fade, no metallic shimmer. */
@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* Sheet entrance animations — one per side. Loop 10: Dialog + Sheet
   primitives. Workbench keeps these short (var(--duration-base) =
   160ms) and uses --ease-out-quart so they settle, not bounce. */
@keyframes sheet-in-right {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}
@keyframes sheet-in-left {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0); }
}
@keyframes sheet-in-top {
  from { transform: translateY(-100%); }
  to   { transform: translateY(0); }
}
@keyframes sheet-in-bottom {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* Workbench Dialog / Sheet open animations — wired by data attributes
   on the primitives in `components/ui/{dialog,sheet}.tsx`, not by
   Tailwind utilities. Tailwind 4 has no built-in `animate-in` family
   without the tailwindcss-animate plugin, and we'd rather keep the
   primitive markup tidy than scatter long arbitrary-value classnames.
   Close animations are intentionally absent — Radix unmounts on
   close, and Workbench skips closing flourishes by design. */
[data-state="open"][data-wb-dialog-overlay] {
  animation: fade-in var(--duration-base) var(--ease-out-quart) both;
}
[data-state="open"][data-wb-dialog-content] {
  animation: rise var(--duration-base) var(--ease-out-quart) both;
}
/* Popover + DropdownMenu — same fade-in on the anchored content
   block. No rise transform: these float close to their trigger
   and a translateY at this distance reads as jitter, not entrance. */
[data-state="open"][data-wb-popover-content],
[data-state="open"][data-wb-dropdown-content],
[data-state="open"][data-wb-select-content],
[data-state="delayed-open"][data-wb-tooltip-content] {
  animation: fade-in var(--duration-base) var(--ease-out-quart) both;
}
[data-state="open"][data-wb-sheet-content][data-side="right"] {
  animation: sheet-in-right var(--duration-base) var(--ease-out-quart) both;
}
[data-state="open"][data-wb-sheet-content][data-side="left"] {
  animation: sheet-in-left var(--duration-base) var(--ease-out-quart) both;
}
[data-state="open"][data-wb-sheet-content][data-side="top"] {
  animation: sheet-in-top var(--duration-base) var(--ease-out-quart) both;
}
[data-state="open"][data-wb-sheet-content][data-side="bottom"] {
  animation: sheet-in-bottom var(--duration-base) var(--ease-out-quart) both;
}

/* Scroll-driven reveal — kept but tightened. Workbench doesn't do
   big entrance animations; this is a 160ms settle for content as it
   enters the viewport, not a marketing flourish. */
@supports (animation-timeline: view()) {
  .reveal {
    animation: rise var(--duration-slow, 240ms) var(--ease-out-quart, cubic-bezier(0.16, 1, 0.3, 1)) both;
    animation-timeline: view();
    animation-range: entry 0% cover 20%;
  }
}
@supports not (animation-timeline: view()) {
  .reveal {
    animation: rise var(--duration-slow, 240ms) var(--ease-out-quart, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }
}

/* ── Sonner toast palette under light theme ────────────────────────
   Loop-7 — sonner's default light palette failed axe under
   Playwright (`#008a2e` on `#ecfdf3` = 4.25:1, below AA 4.5:1), which
   is why `layout.tsx` had to pin `<Toaster theme="dark">`. Replace
   sonner's per-type colours with Workbench tokens so light mode
   consumes the AA-passing semantic palette from the `.light` block
   above. After this, the pin can come off.

   Sonner exposes its palette via CSS custom properties scoped to
   `[data-sonner-toaster]`. Setting them under `.light` cascades into
   every toast that ships under the light theme; dark mode keeps
   sonner's default dark palette unchanged. */
.light [data-sonner-toaster] {
  --normal-bg: hsl(var(--card));
  --normal-text: hsl(var(--card-foreground));
  --normal-border: hsl(var(--border));
  --success-bg: hsl(var(--success) / 0.10);
  --success-text: hsl(var(--success));
  --success-border: hsl(var(--success) / 0.40);
  --error-bg: hsl(var(--destructive) / 0.10);
  --error-text: hsl(var(--destructive));
  --error-border: hsl(var(--destructive) / 0.40);
  --warning-bg: hsl(var(--warning) / 0.10);
  --warning-text: hsl(var(--warning));
  --warning-border: hsl(var(--warning) / 0.40);
  --info-bg: hsl(var(--info) / 0.10);
  --info-text: hsl(var(--info));
  --info-border: hsl(var(--info) / 0.40);
}

/* ── Utilities ──────────────────────────────────────────────────── */

/* Card surface — single border, no shadow. Borders do the elevation
   work in Workbench; if a card needs more lift, raise it via the
   surface-{1,2,3} ramp instead of stacking shadows. */
@utility surface {
  background-color: hsl(var(--card));
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius);
}

/* Hairline divider — 1px, adapts to the surrounding surface. */
@utility hairline {
  border: 0;
  height: 1px;
  background: hsl(var(--border));
}

/* Skeleton loader — single subtle fade, no shimmer sweep. */
@utility skeleton {
  background-color: hsl(var(--muted));
  border-radius: calc(var(--radius) - 2px);
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
