/* Grain Design System — Light Theme (daylight base)
 * The default light register. Every semantic role points at the --sol-*
 * spine (src/themes/grain-solleva.css) BY ROLE — this file holds no palette
 * values of its own, so grain's BASE renders daylight on every entry point
 * (styles.css, the CDN bundle dist/grain.css, and this theme file), not only
 * where the compat layer is imported. Load the spine alongside this theme.
 */

:root {
  /* ── Surfaces (semantic roles, not colour names) ──────── */
  /* The daylight ground. Surfaces get LIGHTER as they rise
     (surface > ground > sunken > deep) — elevation is daylight, not
     shadow, so the flat field carries no gradient. It is NOT yet free of
     noise: .bg-textured / .bg-atmospheric below still paint the forge-era
     fractal overlay, and src/showcase/showcase.css declares an
     unconditional `body::after` copy of it that vite folds into
     dist/index.css and therefore into the CDN bundle. Named, not claimed
     away — de-noising the field is its own change. Every
     role below points at the --sol-* spine BY ROLE: the spine is the one
     source of truth for the value, and this theme is the light register's
     view of it. A consumer that loads only this theme must also load the
     spine (styles.css and the CDN bundle both do). */
  --background: var(--sol-ground);
  --background-sunken: var(--sol-ground-sunken);
  --background-deep: var(--sol-ground-deep);
  --surface: var(--sol-surface);
  --surface-sunken: var(--sol-ground-sunken);  /* inputs are sunken wells */
  --surface-elevated: var(--sol-surface);


  /* ── Text / Ink ───────────────────────────────────────── */
  --ink: var(--sol-ink);
  --ink-secondary: var(--sol-ink-secondary);
  --ink-tertiary: var(--sol-ink-tertiary);
  --ink-emphasis: var(--sol-ink);
  --text-main: var(--sol-ink);
  --text-muted: var(--sol-ink-secondary);
  /* Deliberately the tertiary ink and not --sol-ink-faint, whose own
     declaration refuses glyphs. That refusal is described here rather than
     quoted beside the declaration below, because the licence parser reads a
     token's OWN same-line comment: quoting another token's refusal there
     hands this token that refusal. */
  --text-faint: var(--sol-ink-tertiary);  /* TEXT. Body-faint ink; IS --sol-ink-tertiary, whose declaration is text-licensed. */

  /* ── Surface Layers ──────────────────────────────────── */
  --surface-overlay: color-mix(in oklab, var(--sol-surface) 55%, transparent);
  /* The atmospheric gradient dies: the daylight ground is flat. Declared as
     a bare colour, NOT linear-gradient(ground, ground) — the two render
     identically, but check-contrast-pairs.mjs skips any element whose
     background chain reaches a gradient function before an opaque colour,
     so the gradient form leaves anything painted with it permanently
     ineligible for measurement. MEASURED, not assumed: swapping the two
     forms moves the probe's population by 0 pairs (430 light / 430 dark
     either way), because no element in the probe fixture paints
     --bg-gradient. This removes a latent exclusion; it does not widen
     today's coverage, and must not be reported as if it did. */
  --bg-gradient: var(--sol-ground);

  /* ── Borders — alpha ink ──────────────────────────────── */
  --border: var(--sol-line);
  --border-light: var(--sol-line-faint);
  --border-hairline: var(--sol-line);
  --border-faint: var(--sol-line-faint);

  /* ── Primary accent (role split) ──────────────────────── */
  /* --on-accent is the ink that rides a FILLED control. It stays a token,
     not a constant, because the pair (fill + ink) is measured in both
     registers by check-contrast-pairs.mjs. As a button fill the old accent
     becomes ink; as a graphic accent it becomes the daylight accent; links
     re-point to --sol-accent-ink in code. */
  --on-accent: var(--sol-surface);
  /* The forge-blue family is DELETED, not aliased. 0.4.6 left the four names
     declared as pure aliases onto the spine; every kit bundle therefore still
     shipped them, and the done-gate measures the TRANSITIVE CLOSURE — so every
     daylight colour these names reached was counted as forge pigment and five
     kits read RED with zero literals and zero call sites between them. The
     roles survive under their own names: fill/ink is --ink, the selected wash
     is --background-sunken, the boundary is --border, the graphic accent is
     --sol-accent. Measured before deleting: 0 var(forge-*) reads in grain's
     shipped src, and 0 in kiln/lore/temper/engagement/grain-kit. */

  /* ── Sage → lifted (success / positive) ───────────────── */
  --sage: var(--sol-lifted);
  --sage-bg: var(--sol-lifted-tint);
  --sage-border: color-mix(in oklab, var(--sol-lifted) 25%, transparent);
  --sage-vivid: var(--sol-lifted);

  /* ── Amber → holding (waiting on you) ─────────────────── */
  --amber: var(--sol-holding);
  --amber-bg: var(--sol-holding-tint);
  --amber-border: color-mix(in oklab, var(--sol-holding) 25%, transparent);
  --amber-vivid: var(--sol-holding);

  /* ── Danger → fault (fail loud) ───────────────────────── */
  --danger-red: var(--sol-fault);
  --danger-red-bg: var(--sol-fault-tint);
  --danger-red-border: color-mix(in oklab, var(--sol-fault) 25%, transparent);

  /* ── Warmth family — RETIRED, and now DELETED. ── */
  /* No ember, no gold, no white-hot: heat is light. These six were kept one
     release longer as neutralizing aliases so consumers could migrate; the
     migration is done (0 reads across every consumer repo), so the names go.
     A glow moment re-points to --sol-accent in code, not to a warmth alias. */

  /* ── Shadows — warm shadow stack → light-from-below lifts ── */
  /* Sunken is a lightness step now, not an inset shadow. */
  --shadow-sunken: none;
  --shadow-elevated: var(--sol-lift-1);
  --shadow-float: var(--sol-lift-2);
  --shadow-subtle: var(--sol-lift-1);

  /* ── Border Radius ────────────────────────────────────── */
  --radius-sm: var(--sol-radius-1);
  --radius-md: var(--sol-radius-1);
  --radius-lg: var(--sol-radius-2);
  --radius-panel: var(--sol-radius-2);
  --radius-card: var(--sol-radius-2);
  --radius-input: var(--sol-radius-1);
  --radius-pill: var(--sol-radius-pill);

  /* ── Typography ───────────────────────────────────────── */
  --font-sans: var(--sol-font-sans);
  --font-serif: var(--sol-font-serif);
  --font-mono: var(--sol-font-code);   /* code-only; metrics use tabular sans */
  --font-size-base: var(--sol-text-2);
  --line-height-base: var(--sol-leading-ui);
  --letter-spacing-base: 0;             /* the UI sans runs untracked at text sizes */

  /* ── Tracking Scale ──────────────────────────────────── */
  --tracking-tight: var(--sol-tracking-display);
  --tracking-normal: 0;
  --tracking-wide: var(--sol-tracking-label);
  --tracking-caps: var(--sol-tracking-label);

  /* ── Weight Scale ────────────────────────────────────── */
  --weight-body: var(--sol-weight-body);
  --weight-label: var(--sol-weight-medium);
  --weight-heading: var(--sol-weight-title);

  /* ── Spacing Scale (4pt base) ───────────────────────── */
  --space-1: var(--sol-space-1);
  --space-2: var(--sol-space-2);
  --space-3: var(--sol-space-3);
  --space-4: var(--sol-space-4);
  --space-6: var(--sol-space-5);
  --space-8: var(--sol-space-6);
  --space-12: var(--sol-space-7);
  --space-16: var(--sol-space-8);
  --space-24: var(--sol-space-9);

  /* ── Elevation — light from below-forward (daylight lift, not shadow) ── */
  /* The Card primitive paints --elevation-2 at rest and --elevation-3 on hover. */
  --elevation-0: var(--sol-lift-0);
  --elevation-1: var(--sol-lift-1);
  --elevation-2: var(--sol-lift-1);
  --elevation-3: var(--sol-lift-2);
  --elevation-4: var(--sol-lift-2);

  /* ── Motion ───────────────────────────────────────────── */
  --duration-fast: var(--sol-t-quick);
  --duration-normal: var(--sol-t-rise);
  --duration-slow: var(--sol-t-settle);
  --ease-default: var(--sol-ease);
  --ease-out: var(--sol-ease);
  --ease-spring: var(--sol-ease);       /* nothing bounces */
  --ease-decelerate: var(--sol-ease);

  /* ── Layout Metrics ───────────────────────────────────── */
  --sidebar-w: var(--sol-rail-w);
  --topbar-h: var(--sol-topbar-h);
  --activity-h: 120px;

  /* ── Breakpoints (reference only — use in media queries) ── */
  /* --bp-mobile: 768px;   @media (min-width: 768px)  */
  /* --bp-desktop: 1280px; @media (min-width: 1280px) */
}

/* ── Animations ───────────────────────────────────────────── */

@keyframes pulse-ring {
  0% { transform: scale(1); opacity: 1; }
  100% { transform: scale(2.5); opacity: 0; }
}

@keyframes pulse-alert {
  0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.4); }
  50% { box-shadow: 0 0 0 6px rgba(220, 38, 38, 0); }
}

@keyframes dot-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes shimmer-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

@keyframes breathe {
  0%, 100% { box-shadow: var(--shadow-elevated); }
  50% { box-shadow: var(--shadow-float); }
}

/* ── Atmospheric Surface Classes ──────────────────────────── */

.bg-textured {
  background: var(--bg-gradient);
  position: relative;
}
.bg-textured::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.03;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 256px 256px;
}

/* ── Section Label Utility ───────────────────────────────── */

.section-label {
  font-size: 12px;
  font-weight: var(--weight-heading);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--ink-tertiary);
}

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

/* ── Animation Utility Classes ───────────────────────────── */

.fade-in-up {
  animation: fade-in-up var(--duration-normal) var(--ease-out) both;
}
.delay-1 { animation-delay: 0.1s; }
.delay-2 { animation-delay: 0.2s; }
.delay-3 { animation-delay: 0.3s; }
.delay-4 { animation-delay: 0.4s; }

.breathe {
  animation: breathe 4s ease-in-out infinite;
}

.shimmer {
  position: relative;
  overflow: hidden;
}
.shimmer::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
  animation: shimmer-sweep 2s ease-in-out infinite;
}

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

/* Touch-aware targets */
@media (pointer: coarse) {
  button, [role="button"], a, input, select, textarea, [role="switch"], [role="tab"] {
    min-height: 44px;
    min-width: 44px;
  }
}

/* Hide hover effects on touch devices */
@media (hover: none) {
  .hover\:shadow-\[var\(--elevation-3\)\]:hover { box-shadow: none; }
  .hover\:-translate-y-px:hover { transform: none; }
}

/* Tablet: reduce workspace padding */
@media (max-width: 1279px) {
  :root {
    --sidebar-w: 48px;
  }
}

/* Mobile: collapse sidebar, stack content */
@media (max-width: 767px) {
  :root {
    --sidebar-w: 0px;
  }
}

/* ── Scroll-Driven Reveals ───────────────────────────────── */
/* The forge-scroll-reveal keyframe is RETIRED: it carried the heat
   register (a brightness() flare on entry). The spine's sol-rise is the
   ruled replacement — everything arrives by rising, nothing flares.
   `.reveal-on-scroll` / `.reveal-stagger` keep their names (no consumer
   markup changes) and now drive the daylight entrance. */

@supports (animation-timeline: view()) {
  .reveal-on-scroll {
    animation: sol-rise linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 30%;
  }
}

/* Staggered reveals for card grids */
@supports (animation-timeline: view()) {
  .reveal-stagger > :nth-child(1) { animation-range: entry 0% entry 25%; }
  .reveal-stagger > :nth-child(2) { animation-range: entry 5% entry 30%; }
  .reveal-stagger > :nth-child(3) { animation-range: entry 10% entry 35%; }
  .reveal-stagger > :nth-child(4) { animation-range: entry 15% entry 40%; }
  .reveal-stagger > * {
    animation: sol-rise linear both;
    animation-timeline: view();
  }
}

/* ── Entry Animations (@starting-style) ──────────────────── */

@supports (selector(:popover-open)) {
  [data-state="open"] {
    @starting-style {
      opacity: 0;
      transform: scale(0.96) translateY(4px);
    }
    transition: opacity var(--duration-normal) var(--ease-out),
                transform var(--duration-normal) var(--ease-decelerate);
  }
}

/* ── Completion Animations ───────────────────────────────── */

/* The seal stroke draws itself on. The gold/amber drop-shadow flares it
   carried are RETIRED — they painted #FFD700 and amber directly, so the
   mark glowed forge even after the palette moved. The draw is geometry,
   not pigment: the stroke takes its colour from the element. */
@keyframes molten-seal {
  0% { stroke-dashoffset: 24; opacity: 0; }
  20% { opacity: 1; }
  60% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 0; }
}

/* forge-temper is RETIRED — a scale celebration with an amber box-shadow
   flare. The spine's ruled replacement is sol-lift-settle: the finished
   thing rises and the daylight appears beneath it. Nothing outside grain
   referenced the `.forge-temper` class (measured across every consumer
   repo), so the alias is deleted rather than left beside its successor. */

/* ── Idle Breathe (for status indicators) ────────────────── */

@keyframes idle-breathe {
  0%, 100% { opacity: 0.7; }
  50% { opacity: 1; }
}

.idle-breathe {
  animation: idle-breathe 4s ease-in-out infinite;
}

/* ── The warm status halo — RETIRED (DEPRECATED ALIAS ONLY) ──
   The keyframe this class used to name painted a box-shadow halo out of an
   amber LITERAL. No token controlled that pigment, so the 0.4.5 palette
   repoint — which moved every other colour in this file onto the daylight
   spine — could not reach it, and every consumer's running status dot kept
   glowing forge amber under a daylight theme. Deleting the keyframe is what
   removes the pigment, and the literal is not restated here: a comment that
   quotes a retired hex is still that hex shipping in dist/grain.css, which is
   how the last one survived a repoint. The SELECTOR survives, deprecated,
   because consumer markup outside grain still carries the class name and a
   missing animation is a silent no-op rather than a fail-loud.

   The replacement is the spine's own live-indicator beat. grain-solleva.css §4
   states the rule this violated — "Live indicators (opacity only — glow is
   reserved for the accent)" — so a running dot breathes in OPACITY and takes
   its colour from --status-running, a token, in whichever register is active.
   Amplitude and cadence are unchanged, so the alias is a like-for-like swap for
   anyone still on the old name. Grain's own StatusDot uses .sol-live-pulse. */
.ember-breathe {
  animation: sol-pulse 3s var(--sol-ease) infinite;
}

/* ── Molten Fill (progress bar leading edge) ─────────────── */

@keyframes molten-edge {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

@keyframes molten-shimmer {
  0% { transform: translateX(-200%); }
  100% { transform: translateX(200%); }
}

/* ── Heat Line — RETIRED, and DELETED (0.4.7) ─────────────
   The keyframe block that used to sit here was a dead orphan: it animated
   background-position only (it painted NO colour), no rule ever named it in an
   `animation:`, and no class, component or consumer referenced it — measured
   across the repo. It survived every pigment sweep precisely BECAUSE it had no
   pigment, while still putting the retired heat register's name into the
   shipped bundle for a name-matching predicate to trip over. Its own name is
   not restated in this note for exactly that reason. The spine's motion set
   (sol-rise, sol-lift-settle, sol-pulse, sol-idle) is the replacement. */

/* ── Entry animation ─────────────────────────────────────── */
/* forge-rise is RETIRED — it rose with a brightness() flare, and it hard-
   coded its own easing curve instead of taking the register's. The spine's
   .sol-rise (+ .delay-1..4) is the ruled replacement and lives in
   grain-solleva.css. Nothing outside grain referenced `.forge-rise`
   (measured across every consumer repo), so it is deleted, not aliased. */

/* ── Switch Bounce ───────────────────────────────────────── */

@keyframes switch-bounce {
  0% { transform: translateX(0); }
  40% { transform: translateX(2px); }
  100% { transform: translateX(0); }
}

/* ── Noise Overlay Utility ───────────────────────────────── */

.bg-atmospheric {
  background: var(--bg-gradient);
  position: relative;
}
.bg-atmospheric::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.03;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 256px 256px;
}
