/**
 * LAYER 1 — the family. Copy this file unchanged into every Lotusion product.
 *
 * It reads brand.css and declares no colour of its own, with exactly two
 * exceptions, both named and explained below. If a product needs to edit this
 * file, either the change belongs in brand.css or it belongs in the designbook
 * for every product at once. It is never a local fix.
 *
 * THREE RULES, and the whole file exists to enforce them.
 *
 * 1. Components name SEMANTIC tokens only. `--lx-text`, never
 *    `--lx-neutral-900`; `--lx-primary`, never `--lx-brand-700`. A component
 *    that reaches past the semantic layer into the ramps has hard-coded the
 *    light theme and will be wrong in the dark one — the expensive kind of
 *    wrong, because nothing about it looks like a defect.
 *
 * 2. Every semantic token is declared ONCE, with both values, via
 *    `light-dark()`. There is no second block to keep in sync, so a token
 *    cannot be added to one theme and forgotten in the other — which is how
 *    every hand-maintained dark mode rots.
 *
 * 3. Motion is springs, not durations. The CSS transitions below are for
 *    state changes only (hover, press, colour, the theme flip). Anything a
 *    finger drags is animated in JS from its CURRENT on-screen value so it can
 *    be caught and reversed mid-flight; a CSS transition restarts from its
 *    target and the seam shows.
 */

/* ═══ Theme switching ═══════════════════════════════════════════════════════
   `color-scheme` is what `light-dark()` reads, and it also fixes the things
   custom properties never reach: native form controls, scrollbars, the flash
   of white before first paint. Setting it is not cosmetic.

   Three states, not two: light, dark, and system (the absence of
   `data-theme`). A settings toggle offers all three; system is the default. */
/* --lx-fallbacks --------------------------------------------------------
   Every semantic token is declared twice: a plain light-theme value, then the
   light-dark() one. A browser without light-dark() — anything before Chrome
   123 — drops the second declaration and keeps the first, so it gets a
   readable light-mode site instead of an unstyled one.

   Found on a Windows 8 laptop where the whole page rendered without colour.
   ---------------------------------------------------------------------- */
:root {
  color-scheme: light dark;

  /**
   * The one hook native controls DO honour.
   *
   * A date picker's calendar is drawn by the browser and its internals cannot
   * be styled — no selector reaches the grid, the month header, or the greyed
   * adjacent days. `accent-color` is the exception: it colours the selected
   * day, checkboxes, radios and range thumbs. Without it the highlighted date
   * is the browser's default blue sitting inside a branded product.
   */
  accent-color: var(--lx-primary);
}
:root[data-theme='light'] {
  color-scheme: light;
}
:root[data-theme='dark'] {
  color-scheme: dark;
}

/* ═══ Layer 2 — semantic ════════════════════════════════════════════════════
   What components are allowed to name. Nothing else. */
:root {
  /* --- surfaces, back to front ------------------------------------------
     Sunken sits BETWEEN bg and surface in dark, not at the bottom. The
     obvious reasoning — a well is darker than what holds it — is true in
     light, where bg is neutral-50 and there is room below it. In dark there
     is no room below the page: point both at neutral-950 and every well in
     the product becomes invisible. A contrast check will not find that,
     because identical colours fail no minimum. They just disappear. */
  --lx-bg: var(--lx-neutral-50);
  --lx-bg: light-dark(var(--lx-neutral-50), var(--lx-neutral-950));
  --lx-surface: var(--lx-neutral-0);
  --lx-surface: light-dark(var(--lx-neutral-0), var(--lx-neutral-900));
  --lx-surface-sunken: var(--lx-neutral-100);
  --lx-surface-sunken: light-dark(var(--lx-neutral-100), var(--lx-neutral-925));
  --lx-surface-raised: var(--lx-neutral-0);
  --lx-surface-raised: light-dark(var(--lx-neutral-0), var(--lx-neutral-800));

  /* --- two kinds of border, and conflating them is the usual mistake -----
     `--lx-line` separates content: a card edge, a list divider, a table rule.
     It is decorative — remove it and nothing becomes unusable, so it is
     allowed to be quiet, and should be, or the product reads as a
     spreadsheet.

     `--lx-control-line` is the border that is the ONLY thing saying "this is
     a text field". Remove it and the control disappears. WCAG 1.4.11 requires
     3:1 for exactly that case, so it is darker, and it is not optional.

     There is no single value that is right for both. Conflating them either
     makes the UI heavy or makes the forms inaccessible. */
  --lx-line: var(--lx-neutral-200);
  --lx-line: light-dark(var(--lx-neutral-200), var(--lx-neutral-800));
  --lx-line-strong: var(--lx-neutral-300);
  --lx-line-strong: light-dark(var(--lx-neutral-300), var(--lx-neutral-700));
  --lx-control-line: var(--lx-boundary-light);
  --lx-control-line: light-dark(var(--lx-boundary-light), var(--lx-boundary-dark));

  /* --- text — three levels, no more -------------------------------------- */
  --lx-text: var(--lx-neutral-900);
  --lx-text: light-dark(var(--lx-neutral-900), var(--lx-neutral-100));
  --lx-text-soft: var(--lx-neutral-600);
  --lx-text-soft: light-dark(var(--lx-neutral-600), var(--lx-neutral-400));
  --lx-text-faint: var(--lx-faint-light);
  --lx-text-faint: light-dark(var(--lx-faint-light), var(--lx-faint-dark));

  /* --- primary ----------------------------------------------------------
     Dark mode does NOT reuse the brand value. A mark drawn at brand-700 sits
     at roughly 2.4:1 on a dark surface — a brand colour that fails on half
     the product is not a brand colour, it is a light-mode colour. Dark lifts
     to brand-300 and the foreground drops to brand-950.
     Never invert a palette; re-pick it. */
  --lx-primary: var(--lx-brand-700);
  --lx-primary: light-dark(var(--lx-brand-700), var(--lx-brand-300));
  --lx-primary-hover: var(--lx-brand-800);
  --lx-primary-hover: light-dark(var(--lx-brand-800), var(--lx-brand-200));
  --lx-primary-on: var(--lx-neutral-0);
  --lx-primary-on: light-dark(var(--lx-neutral-0), var(--lx-brand-950));
  --lx-primary-soft: var(--lx-brand-50);
  --lx-primary-soft: light-dark( var(--lx-brand-50), color-mix(in oklab, var(--lx-brand-300) 16%, transparent) );
  --lx-primary-quiet: var(--lx-brand-700);
  --lx-primary-quiet: light-dark(var(--lx-brand-700), var(--lx-brand-200));

  /* --- accent — one per screen ------------------------------------------
     If two things on a screen are accent, neither is. */
  --lx-accent: var(--lx-accent-600);
  --lx-accent: light-dark(var(--lx-accent-600), var(--lx-accent-300));
  --lx-accent-on: var(--lx-neutral-0);
  --lx-accent-on: light-dark(var(--lx-neutral-0), var(--lx-accent-900));
  --lx-accent-soft: var(--lx-accent-50);
  --lx-accent-soft: light-dark( var(--lx-accent-50), color-mix(in oklab, var(--lx-accent-300) 16%, transparent) );

  /* --- status ------------------------------------------------------------ */
  --lx-ok: var(--lx-green-600);
  --lx-ok: light-dark(var(--lx-green-600), var(--lx-green-300));
  --lx-ok-soft: var(--lx-green-100);
  --lx-ok-soft: light-dark( var(--lx-green-100), color-mix(in oklab, var(--lx-green-300) 14%, transparent) );
  --lx-warn: var(--lx-amber-600);
  --lx-warn: light-dark(var(--lx-amber-600), var(--lx-amber-300));
  --lx-warn-soft: var(--lx-amber-100);
  --lx-warn-soft: light-dark( var(--lx-amber-100), color-mix(in oklab, var(--lx-amber-300) 14%, transparent) );
  --lx-danger: var(--lx-red-600);
  --lx-danger: light-dark(var(--lx-red-600), var(--lx-red-300));
  --lx-danger-soft: var(--lx-red-100);
  --lx-danger-soft: light-dark( var(--lx-red-100), color-mix(in oklab, var(--lx-red-300) 14%, transparent) );

  /* --- machine-readable ---------------------------------------------------
     The two literals in this file, and the only two allowed.

     A QR code is read by a camera, not by a person, and ISO/IEC 18004 asks
     for dark modules on a light background at the full contrast available.
     Some scanners cope with an inverted code; enough do not that "it worked
     on my phone" is the only test it would ever pass. Theming this would be
     the design system overriding a decision that was never the design
     system's to make — so it is named here, once, with the reason, rather
     than appearing as a bare `#fff` in a component where the next reader
     would rightly delete it. */
  --lx-code-paper: #ffffff;
  --lx-code-ink: #000000;

  /* --- glass -------------------------------------------------------------
     Translucency is how hierarchy is read: chrome floats, content scrolls
     under it. Two hard rules — never stack one glass surface on another
     (legibility collapses), and a bigger surface reads thicker, so blur and
     shadow rise together. */
  --lx-glass: var(--lx-veil-a72);
  --lx-glass: light-dark( color-mix(in srgb, var(--lx-veil-light) 72%, transparent), color-mix(in srgb, var(--lx-veil-dark) 72%, transparent) );
  --lx-glass-heavy: var(--lx-veil-a86);
  --lx-glass-heavy: light-dark( color-mix(in srgb, var(--lx-veil-light) 86%, transparent), color-mix(in srgb, var(--lx-neutral-950) 86%, transparent) );
  --lx-glass-line: var(--lx-veil-a60);
  --lx-glass-line: light-dark( color-mix(in srgb, var(--lx-veil-light) 60%, transparent), color-mix(in srgb, var(--lx-veil-light) 8%, transparent) );
  --lx-blur: blur(20px) saturate(180%);
  --lx-blur-heavy: blur(32px) saturate(180%);

  /* --- elevation ---------------------------------------------------------
     Four shadows. `--lx-shadow-lift` is for WHILE something is held, so a
     drag is felt and not merely watched. */
  --lx-shadow-1: 0 1px 2px var(--lx-shade-a6);
  --lx-shadow-1: 0 1px 2px
    light-dark(
      color-mix(in srgb, var(--lx-shade-light) 6%, transparent),
      color-mix(in srgb, var(--lx-shade-dark) 40%, transparent)
    );
  --lx-shadow-2: 0 2px 4px var(--lx-shade-a6), 0 8px 20px var(--lx-shade-a8);
  --lx-shadow-2:
    0 2px 4px
      light-dark(
        color-mix(in srgb, var(--lx-shade-light) 6%, transparent),
        color-mix(in srgb, var(--lx-shade-dark) 35%, transparent)
      ),
    0 8px 20px
      light-dark(
        color-mix(in srgb, var(--lx-shade-light) 8%, transparent),
        color-mix(in srgb, var(--lx-shade-dark) 45%, transparent)
      );
  --lx-shadow-3: 0 4px 8px var(--lx-shade-a8), 0 20px 48px var(--lx-shade-a12);
  --lx-shadow-3:
    0 4px 8px
      light-dark(
        color-mix(in srgb, var(--lx-shade-light) 8%, transparent),
        color-mix(in srgb, var(--lx-shade-dark) 40%, transparent)
      ),
    0 20px 48px
      light-dark(
        color-mix(in srgb, var(--lx-shade-light) 12%, transparent),
        color-mix(in srgb, var(--lx-shade-dark) 55%, transparent)
      );
  --lx-shadow-lift: 0 8px 16px var(--lx-shade-a10), 0 32px 64px var(--lx-shade-a16);
  --lx-shadow-lift:
    0 8px 16px
      light-dark(
        color-mix(in srgb, var(--lx-shade-light) 10%, transparent),
        color-mix(in srgb, var(--lx-shade-dark) 45%, transparent)
      ),
    0 32px 64px
      light-dark(
        color-mix(in srgb, var(--lx-shade-light) 16%, transparent),
        color-mix(in srgb, var(--lx-shade-dark) 60%, transparent)
      );

  --lx-focus-ring: 0 0 0 3px var(--lx-brand-700-a30);
  --lx-focus-ring: 0 0 0 3px
    light-dark(
      color-mix(in srgb, var(--lx-brand-700) 30%, transparent),
      color-mix(in srgb, var(--lx-brand-300) 35%, transparent)
    );

  /* --- type --------------------------------------------------------------
     Inter, variable, with system-ui as the FALLBACK rather than the default.
     system-ui's proportions change per platform and a wordmark's do not; Inter
     is OFL, so it ships with us. */
  --lx-font: 'Inter var', 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --lx-font-num: var(--lx-font);

  /* Size, leading and tracking travel together as one token. Tracking is
     size-specific — large type set at 0 looks loose, small type set at 0
     closes up — so a fixed letter-spacing is wrong somewhere on every page.
     Note the shape: tracking negative at the top, zero in the middle,
     positive at the bottom; leading runs the other way. */
  --lx-display: 700 clamp(2.25rem, 1.5rem + 3vw, 3.5rem) / 1.05 var(--lx-font);
  --lx-display-track: -0.03em;
  --lx-h1: 700 clamp(1.75rem, 1.4rem + 1.4vw, 2.25rem) / 1.15 var(--lx-font);
  --lx-h1-track: -0.022em;
  --lx-h2: 650 1.5rem / 1.22 var(--lx-font);
  --lx-h2-track: -0.016em;
  --lx-h3: 650 1.25rem / 1.3 var(--lx-font);
  --lx-h3-track: -0.011em;
  --lx-body-lg: 400 1.125rem / 1.55 var(--lx-font);
  --lx-body-lg-track: -0.003em;
  --lx-body: 400 1rem / 1.6 var(--lx-font);
  --lx-body-track: 0em;
  --lx-sm: 400 0.875rem / 1.5 var(--lx-font);
  --lx-sm-track: 0.005em;
  --lx-xs: 500 0.75rem / 1.45 var(--lx-font);
  --lx-xs-track: 0.014em;

  /* The same scale as raw sizes. The `font:` shorthands above are the right
     thing to reach for, because they carry leading and weight with the size
     and cannot be used wrongly. But a shorthand cannot be applied to one
     property — a heading that needs a larger size and the inherited leading,
     an icon sized off the text — so the sizes are nameable on their own.
     Two spellings of ONE scale, not two scales. If these ever disagree with
     the shorthands above, these are wrong. */
  --lx-text-xs: 0.75rem;
  --lx-text-sm: 0.875rem;
  --lx-text-base: 1rem;
  --lx-text-lg: 1.25rem;
  --lx-text-xl: 1.5rem;
  --lx-text-2xl: clamp(1.75rem, 1.4rem + 1.4vw, 2.25rem);
  --lx-text-3xl: clamp(2.25rem, 1.5rem + 3vw, 3.5rem);

  /* --- space — 4px base, nine steps -------------------------------------- */
  --lx-space-1: 4px;
  --lx-space-2: 8px;
  --lx-space-3: 12px;
  --lx-space-4: 16px;
  --lx-space-5: 24px;
  --lx-space-6: 32px;
  --lx-space-7: 48px;
  --lx-space-8: 64px;
  --lx-space-9: 96px;

  /* --- radius ------------------------------------------------------------
     One nesting rule: an inner radius is the outer radius minus the gap
     between them. A 16px card with 8px padding holds an 8px control. Equal
     radii nested look wrong and nobody can ever say why. */
  --lx-radius-xs: 6px;
  --lx-radius-sm: 8px;
  --lx-radius-md: 12px;
  --lx-radius-lg: 16px;
  --lx-radius-xl: 24px;
  --lx-radius-pill: 999px;

  /* --- motion ------------------------------------------------------------
     Spring parameters live here as numbers so JS and CSS agree on the feel.
     `bounce` is Motion's spelling of damping: 0 is critically damped. Bounce
     is reserved for motion the user's own gesture put energy into — a dialog
     that bounces when nobody threw it is a dialog that looks nervous. */
  --lx-spring-ui: 0;
  --lx-spring-ui-dur: 0.35s;
  --lx-spring-throw: 0.2;
  --lx-spring-throw-dur: 0.4s;
  --lx-spring-sheet: 0.15;
  --lx-spring-sheet-dur: 0.3s;

  /* Momentum projection: x + (v/1000)·d/(1−d).
     THIS IS A NUMBER, NOT AN EASING FUNCTION. `transition: transform 600ms
     var(--lx-decel)` is invalid, so the declaration is discarded, the element
     still reaches its final state, and it simply never animates — nothing
     errors and the page looks finished. Found in eight places in one codebase.
     Use --lx-ease below for easing; use this only in JS. */
  --lx-decel: 0.998;

  /* CSS transitions — state changes only. Never gestures. */
  --lx-ease: cubic-bezier(0.2, 0, 0, 1);
  --lx-ease-in: cubic-bezier(0.4, 0, 1, 1);
  --lx-dur-press: 100ms; /* feedback on pointerdown, not on release */
  --lx-dur-fast: 150ms;
  --lx-dur: 220ms;
  --lx-dur-slow: 320ms;

  /* --- layout ------------------------------------------------------------ */
  --lx-container: 1200px;
  --lx-container-narrow: 720px;
  --lx-container-wide: 1440px;
  --lx-header-h: 64px;
  /* The mobile bottom ribbon. 56 fits an icon and a one-word label with
     nothing to spare, so a two-word destination wraps into the border above
     it and the whole row reads as cramped. 64 is the platform norm on both
     phones. */
  --lx-ribbon-h: 64px;
  --lx-tap: 44px; /* nothing interactive is smaller, whatever it looks like */

  --lx-z-base: 0;
  --lx-z-sticky: 10;
  --lx-z-ribbon: 20;
  --lx-z-header: 30;
  --lx-z-sheet: 40;
  --lx-z-modal: 50;
  --lx-z-toast: 60;
}

/* A phone is narrower than the scale assumes. Scaling the four largest steps
   narrows every gap, page margin and panel at once — which is the fix, rather
   than patching whichever header happened to be measured. A second language
   finds these overflows first: French runs about a fifth longer than English. */
@media (max-width: 480px) {
  :root {
    --lx-space-6: 24px;
    --lx-space-7: 32px;
    --lx-space-8: 40px;
    --lx-space-9: 56px;
  }
}

/* ═══ Base ══════════════════════════════════════════════════════════════════ */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font: var(--lx-body);
  letter-spacing: var(--lx-body-track);
  color: var(--lx-text);
  background: var(--lx-bg);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  /* The theme flip is a brightness jump across the whole viewport, which is
     exactly what the accessibility guidance says to ease rather than cut. */
  transition:
    background-color var(--lx-dur) var(--lx-ease),
    color var(--lx-dur) var(--lx-ease);
}

/* A long unbroken string — a reference, a URL, an email in the header — can
   push the document wider than the screen, and a horizontal scrollbar on a
   phone is invisible from the desktop where every screen was designed.

   CLIP, not HIDDEN, and the difference is not cosmetic. `overflow-x: hidden`
   makes the element a scroll CONTAINER — the computed value becomes
   `hidden auto` — and a scroll container is what `position: sticky` measures
   itself against. With it on `body`, every sticky element in the product
   resolves against body's scrollport rather than the viewport and silently
   never engages. `clip` cuts the overflow without creating a scroll container. */
html,
body {
  max-width: 100%;
  overflow-x: clip;
}

/* `:focus-visible`, not `:focus`, so a mouse click does not leave a ring behind
   on a button somebody already pressed. */
:focus-visible {
  outline: 2px solid var(--lx-primary);
  outline-offset: 2px;
  box-shadow: var(--lx-focus-ring);
  border-radius: var(--lx-radius-sm);
}

/* ═══ Accessibility — three independent signals, three different answers ════
   These are not one switch. Someone can want motion reduced and transparency
   kept, or the reverse. */

@media (prefers-reduced-motion: reduce) {
  :root {
    /* The durations that time MOVEMENT go to zero. --lx-dur does not: it is
       what a cross-fade is timed by, and the theme flip below is still a
       viewport-wide brightness jump that wants easing rather than cutting. */
    --lx-dur-press: 0ms;
    --lx-dur-fast: 0ms;
    --lx-dur-slow: 0ms;
  }
  /* Cross-fade instead of slide. Opacity and colour changes STAY — they carry
     meaning, and removing them removes comprehension, not motion.

     Rewriting the PROPERTY LIST is what makes that true. Cutting
     transition-duration — the usual copy-pasted snippet, and what this file
     did until 2026-09-08 — cuts the cross-fade along with the slide, so the
     rule directly contradicted the comment above it. Naming the properties
     lets colour and opacity through even in code we do not own. */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-property:
      opacity, color, background-color, border-color, box-shadow,
      backdrop-filter, fill, stroke !important;
  }
}

@media (prefers-reduced-transparency: reduce) {
  :root {
    --lx-glass: var(--lx-surface);
    --lx-glass-heavy: var(--lx-surface);
    --lx-glass-line: var(--lx-line);
    --lx-blur: none;
    --lx-blur-heavy: none;
  }
}

@media (prefers-contrast: more) {
  :root {
    --lx-line: var(--lx-neutral-500);
    --lx-line: light-dark(var(--lx-neutral-500), var(--lx-neutral-400));
    --lx-line-strong: var(--lx-neutral-700);
    --lx-line-strong: light-dark(var(--lx-neutral-700), var(--lx-neutral-300));
    /* A control boundary that only just cleared 3:1 is not "more contrast". */
    --lx-control-line: var(--lx-neutral-700);
    --lx-control-line: light-dark(var(--lx-neutral-700), var(--lx-neutral-300));
    --lx-text-soft: var(--lx-neutral-800);
    --lx-text-soft: light-dark(var(--lx-neutral-800), var(--lx-neutral-200));
    --lx-text-faint: var(--lx-neutral-700);
    --lx-text-faint: light-dark(var(--lx-neutral-700), var(--lx-neutral-300));
    --lx-glass: var(--lx-surface);
    --lx-glass-heavy: var(--lx-surface);
    --lx-blur: none;
    --lx-blur-heavy: none;
  }
}
