/* ===========================================================================
   vedika-ds.css — COMPONENTS
   ---------------------------------------------------------------------------
   Consumes css/vedika-tokens.css and nothing else. There is not one literal
   colour, radius, shadow, font-family or duration in this file, and
   `node tools/check-tokens.mjs` fails the build if one appears. That rule is
   what makes a retheme of 29,861 pages an edit to a single token file.

   LAYERS
   ------
   Everything here is in `@layer vds.base`, `@layer vds.components` or
   `@layer vds.utilities`. css/vedika-theme.css declares the order and pulls
   the legacy stylesheets into `@layer legacy` beneath them, so these rules win
   over Tailwind utilities and legacy page CSS on specificity-free grounds. No
   `!important` appears in this file, and none is needed.

   THE RESPONSIVE RULE
   -------------------
   THERE IS NO MOBILE STYLESHEET. If you find yourself writing one, the
   component is wrong. Components size themselves from their CONTAINER, grids
   reflow intrinsically, and the type scale is fluid. Viewport @media is used
   for exactly two things in this file: page chrome that has no container (the
   nav), and prefers-reduced-motion.
   =========================================================================== */


/* ===========================================================================
   BASE — the opt-in reset
   ---------------------------------------------------------------------------
   Scoped to `.vds`, never to bare elements, so loading this file changes
   nothing until a page or a section opts in. Put `.vds` on <body> to convert a
   whole page, or on a <section> to convert one band at a time.
   =========================================================================== */

@layer vds.base {

  .vds,
  .vds *,
  .vds *::before,
  .vds *::after { box-sizing: border-box; }

  .vds {
    font-family: var(--vds-font-sans);
    font-size: var(--vds-text-body);
    line-height: var(--vds-leading-body);
    letter-spacing: var(--vds-tracking-body);
    color: var(--vds-text-primary);
    background-color: var(--vds-surface-canvas);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    /* Fragment Mono ships one weight. Without this, any font-weight:600 on a
       mono element gets a smeared synthetic bold that is obvious at 11px
       uppercase. Turning synthesis off makes the mistake visible instead. */
    font-synthesis-weight: none;
  }

  /* --- OVERFLOW MADE IMPOSSIBLE ------------------------------------------
     Four rules that between them prevent almost every "text overflowing the
     grid on mobile" report. They are in the base layer deliberately: a
     component author should not have to remember any of them.

     1. min-width:0 on flex and grid children. A flex/grid item's default
        min-width is `auto`, which means it refuses to shrink below its
        content. One long unbroken string — an API path, a key, a German
        compound — then pushes the whole track wider than the viewport. This
        single missing declaration is the most common cause of horizontal
        scroll on a mobile page, and it is fixed here once.
     2. Media never exceeds its box.
     3. Long unbroken strings wrap rather than extend.
     4. The page itself may not scroll sideways. */
  .vds :where(.vds-grid, .vds-row, .vds-stack, .vds-cluster) > * { min-width: 0; }
  .vds :where(img, svg, video, canvas, iframe, picture) { max-width: 100%; height: auto; }
  .vds :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, td, th, figcaption) {
    overflow-wrap: break-word;
  }
  .vds :where(pre, code, kbd, samp, .vds-mono) { overflow-wrap: break-word; }
  .vds { overflow-x: clip; }

  /* --- TAP TARGET FLOOR --------------------------------------------------
     WCAG 2.2 AA 2.5.8. Applied structurally so no component can ship below it,
     including ones written later by someone who has not read this file.
     :where() keeps specificity at zero so a component can still set a larger
     size without a fight. */
  .vds :where(a[href], button, [role="button"], input, select, textarea, summary) {
    min-height: var(--vds-tap-min);
  }

  /* --- one focus ring for the whole system ------------------------------- */
  .vds :focus-visible {
    outline: var(--vds-focus-width) solid var(--vds-accent-primary);
    outline-offset: var(--vds-focus-offset);
  }
  .vds :focus:not(:focus-visible) { outline: none; }

  /* forms inherit the system's type instead of the UA's */
  .vds :where(input, select, textarea, button) { font: inherit; color: inherit; }
}


/* ===========================================================================
   THE DUST BLOOM — a component-level material
   ---------------------------------------------------------------------------
   ONE implementation, opted into by any component: a button, a card, a grid
   cell, a section header. It is the same construction as
   css/vedika-sheet-css.css, so a bloom inside a capsule and a bloom on a
   full-bleed poster are visibly the same material.

     * GAUSSIAN FALLOFF, NOT A CONE. Eight stops approximating exp(-3.4t^2).
       A two-stop radial-gradient is a linear ramp and the eye reads the kink
       in its first derivative as a hard disc. This is the whole difference
       between "airbrush" and "CSS gradient".

     * SUBTRACTIVE ON LIGHT, ADDITIVE ON DARK. On white the blooms are inks
       and composite with multiply, so magenta over gold really becomes red
       rather than going grey. On near-black they are light and composite with
       screen. --vds-bloom-mode carries this and lives in the token file.

     * THE CLEARING IS THE BLEND IDENTITY. Layer 0 of the stack is a wash in
       --vds-bloom-null — white under multiply, black under screen, i.e.
       exactly the identity element of the active blend mode. Where that wash
       is opaque the ink is ERASED and raw surface shows through. That is what
       holds label text off the bloom, and it is why the measured contrast
       numbers survive. It is not decoration. Do not remove it.

     * GRAIN IS A DITHER, NOT A TEXTURE. Zero-mean feTurbulence composited with
       overlay, whose identity IS mid-grey. It breaks the 8-bit quantiser
       without moving the average pixel, and it is what makes the bloom read as
       granular dust rather than a smooth ramp. It is masked to the bloom's own
       region so a flat surface never picks up noise.

   COST — read this before adding a bloom to a repeated element.
   The bloom is TWO extra paint layers with a blend mode. Static, that is
   cheap: it rasterises once and is then just a composited texture. ANIMATED,
   it invalidates and repaints every frame, and background-position/size
   animation cannot run on the compositor.

   So the default is STATIC and the animation is opt-in per element via
   `.vds-bloom--drift`. Never put --drift on anything that repeats. The budget
   is stated in DESIGN-SYSTEM.md and measured in tools/bloom-perf.mjs.
   =========================================================================== */

@layer vds.components {

  .vds-bloom-host {
    /* geometry, as fractions of the element. Defaults put the bloom in the
       right third and bleed it leftward — the reference component. */
    /* Three SEPARATE fronds in the right third, not one merged wash. The
       radii are deliberately small and the centres are spread across x and y
       so the blooms overlap only partially -- that partial overlap is what
       produces the feathered, dispersing edge instead of a left-to-right
       ramp. Wide radii merge the three into a single smooth gradient, which
       is the exact thing this is not supposed to look like. */
    --vds-b1-x: 84%;  --vds-b1-y: 58%;  --vds-b1-rx: 30%;  --vds-b1-ry: 116%;
    --vds-b2-x: 99%;  --vds-b2-y: 96%;  --vds-b2-rx: 27%;  --vds-b2-ry: 104%;
    --vds-b3-x: 72%;  --vds-b3-y: 2%;   --vds-b3-rx: 27%;  --vds-b3-ry: 122%;
    --vds-bloom-alpha: 0.62;
    --vds-bloom-clear: 1;
    --vds-bloom-clear-width: 58%;
    /* the dither carries the "dust" reading; too low and the bloom collapses
       into a plain CSS gradient */
    --vds-bloom-grain: 0.5;

    position: relative;
    /* REQUIRED. Without it the multiply reaches through to the page behind
       instead of stopping at this element's own background. */
    isolation: isolate;
  }

  /* --- the ink layer ------------------------------------------------------
     z-index:-1 places it above the element's own background and below its
     inline content, so no wrapper element is needed and text is never blended
     into the bloom. */
  .vds-bloom-host.vds-bloom::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    pointer-events: none;
    mix-blend-mode: var(--vds-bloom-mode);
    /* layer 0 (the clearing) composites normally ON TOP of the three inks,
       which multiply among themselves. */
    background-blend-mode: normal, multiply, multiply;
    background-repeat: no-repeat;
    background-size: 132% 132%;
    background-position: 50% 50%;
    background-image:
      linear-gradient(to right in oklab,
        rgb(var(--vds-bloom-null) / var(--vds-bloom-clear))                          0%,
        rgb(var(--vds-bloom-null) / calc(var(--vds-bloom-clear) * var(--vds-k1)))    calc(var(--vds-bloom-clear-width) * 0.14),
        rgb(var(--vds-bloom-null) / calc(var(--vds-bloom-clear) * var(--vds-k2)))    calc(var(--vds-bloom-clear-width) * 0.28),
        rgb(var(--vds-bloom-null) / calc(var(--vds-bloom-clear) * var(--vds-k3)))    calc(var(--vds-bloom-clear-width) * 0.43),
        rgb(var(--vds-bloom-null) / calc(var(--vds-bloom-clear) * var(--vds-k4)))    calc(var(--vds-bloom-clear-width) * 0.58),
        rgb(var(--vds-bloom-null) / calc(var(--vds-bloom-clear) * var(--vds-k5)))    calc(var(--vds-bloom-clear-width) * 0.72),
        rgb(var(--vds-bloom-null) / calc(var(--vds-bloom-clear) * var(--vds-k6)))    calc(var(--vds-bloom-clear-width) * 0.86),
        rgb(var(--vds-bloom-null) / 0)                                               var(--vds-bloom-clear-width)),
      radial-gradient(var(--vds-b1-rx) var(--vds-b1-ry) at var(--vds-b1-x) var(--vds-b1-y) in oklab,
        color-mix(in oklab, var(--vds-bloom-ink-1) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k0) * 100%), transparent) var(--vds-s0),
        color-mix(in oklab, var(--vds-bloom-ink-1) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k1) * 100%), transparent) var(--vds-s1),
        color-mix(in oklab, var(--vds-bloom-ink-1) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k2) * 100%), transparent) var(--vds-s2),
        color-mix(in oklab, var(--vds-bloom-ink-1) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k3) * 100%), transparent) var(--vds-s3),
        color-mix(in oklab, var(--vds-bloom-ink-1) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k4) * 100%), transparent) var(--vds-s4),
        color-mix(in oklab, var(--vds-bloom-ink-1) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k5) * 100%), transparent) var(--vds-s5),
        color-mix(in oklab, var(--vds-bloom-ink-1) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k6) * 100%), transparent) var(--vds-s6),
        transparent 100%),
      radial-gradient(var(--vds-b2-rx) var(--vds-b2-ry) at var(--vds-b2-x) var(--vds-b2-y) in oklab,
        color-mix(in oklab, var(--vds-bloom-ink-2) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k0) * 100%), transparent) var(--vds-s0),
        color-mix(in oklab, var(--vds-bloom-ink-2) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k1) * 100%), transparent) var(--vds-s1),
        color-mix(in oklab, var(--vds-bloom-ink-2) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k2) * 100%), transparent) var(--vds-s2),
        color-mix(in oklab, var(--vds-bloom-ink-2) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k3) * 100%), transparent) var(--vds-s3),
        color-mix(in oklab, var(--vds-bloom-ink-2) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k4) * 100%), transparent) var(--vds-s4),
        color-mix(in oklab, var(--vds-bloom-ink-2) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k5) * 100%), transparent) var(--vds-s5),
        color-mix(in oklab, var(--vds-bloom-ink-2) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k6) * 100%), transparent) var(--vds-s6),
        transparent 100%),
      radial-gradient(var(--vds-b3-rx) var(--vds-b3-ry) at var(--vds-b3-x) var(--vds-b3-y) in oklab,
        color-mix(in oklab, var(--vds-bloom-ink-3) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k0) * 100%), transparent) var(--vds-s0),
        color-mix(in oklab, var(--vds-bloom-ink-3) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k1) * 100%), transparent) var(--vds-s1),
        color-mix(in oklab, var(--vds-bloom-ink-3) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k2) * 100%), transparent) var(--vds-s2),
        color-mix(in oklab, var(--vds-bloom-ink-3) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k3) * 100%), transparent) var(--vds-s3),
        color-mix(in oklab, var(--vds-bloom-ink-3) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k4) * 100%), transparent) var(--vds-s4),
        color-mix(in oklab, var(--vds-bloom-ink-3) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k5) * 100%), transparent) var(--vds-s5),
        color-mix(in oklab, var(--vds-bloom-ink-3) calc(var(--vds-bloom-alpha) * var(--vds-bloom-strength) * var(--vds-k6) * 100%), transparent) var(--vds-s6),
        transparent 100%);
  }

  /* --- the dither --------------------------------------------------------- */
  .vds-bloom-host.vds-bloom::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    pointer-events: none;
    mix-blend-mode: overlay;
    opacity: var(--vds-bloom-grain);
    background-repeat: repeat;
    background-size: var(--vds-grain-tile) var(--vds-grain-tile);
    background-image: var(--vds-grain-image);
    -webkit-mask-image: linear-gradient(to right,
      transparent 0%,
      rgb(var(--vds-rgb-black) / 0.25) calc(var(--vds-bloom-clear-width) * 0.55),
      rgb(var(--vds-rgb-black) / 0.8) calc(var(--vds-bloom-clear-width) * 0.95),
      rgb(var(--vds-rgb-black) / 1) 100%);
    mask-image: linear-gradient(to right,
      transparent 0%,
      rgb(var(--vds-rgb-black) / 0.25) calc(var(--vds-bloom-clear-width) * 0.55),
      rgb(var(--vds-rgb-black) / 0.8) calc(var(--vds-bloom-clear-width) * 0.95),
      rgb(var(--vds-rgb-black) / 1) 100%);
  }

  /* --- drift: OPT-IN, hero moments only ----------------------------------
     Two irrational periods on background-position and background-size. The
     layer is oversized to 132% so its own edges are never exposed and it never
     needs translating. NEVER apply this to a repeated element — see COST. */
  /* Travel widened 2026-07-29. The old cycle moved 9 percentage points of
     position and 14 of size over 26s — roughly 0.4%/s, which is under the
     threshold at which the eye registers movement at all, so the surface read
     as a static gradient. Three keyframes also meant the motion retraced its
     own path exactly, which reads as a pulse rather than a drift.

     Four asymmetric stops on a non-repeating loop, with the two axes moving
     out of phase, is what makes it read as pigment in water rather than a
     panning gradient. Still `background-position`/`-size` rather than a
     transform: a transform on the ::before would move the CLEARING as well as
     the bloom, and that clearing is what holds the label's contrast. */
  @keyframes vds-drift {
    0%   { background-position: 42% 56%; background-size: 130% 134%; }
    27%  { background-position: 58% 43%; background-size: 152% 140%; }
    58%  { background-position: 47% 61%; background-size: 138% 156%; }
    81%  { background-position: 61% 49%; background-size: 148% 132%; }
    100% { background-position: 42% 56%; background-size: 130% 134%; }
  }
  .vds-bloom-host.vds-bloom.vds-bloom--drift::before {
    animation: vds-drift var(--vds-duration-bloom) var(--vds-ease-soft) infinite;
  }

  /* --- geometry presets, so nobody hand-writes six percentages ------------ */
  .vds-bloom-at--right  { --vds-b1-x: 84%; --vds-b2-x: 99%; --vds-b3-x: 72%;
                          --vds-bloom-clear-width: 58%; }
  .vds-bloom-at--corner { --vds-b1-x: 94%;  --vds-b1-y: 4%;   --vds-b1-rx: 40%; --vds-b1-ry: 74%;
                          --vds-b2-x: 108%; --vds-b2-y: 26%;  --vds-b2-rx: 34%; --vds-b2-ry: 66%;
                          --vds-b3-x: 80%;  --vds-b3-y: -20%; --vds-b3-rx: 34%; --vds-b3-ry: 78%;
                          --vds-bloom-clear-width: 76%; }
  .vds-bloom-at--base   { --vds-b1-x: 50%; --vds-b1-y: 118%; --vds-b1-rx: 78%; --vds-b1-ry: 90%;
                          --vds-b2-x: 78%; --vds-b2-y: 128%; --vds-b2-rx: 64%; --vds-b2-ry: 80%;
                          --vds-b3-x: 24%; --vds-b3-y: 124%; --vds-b3-rx: 70%; --vds-b3-ry: 86%;
                          --vds-bloom-clear: 0; }
  /* strength presets — the taste dial */
  .vds-bloom--faint { --vds-bloom-alpha: 0.22; --vds-bloom-grain: 0.26; }
  .vds-bloom--full  { --vds-bloom-alpha: 0.62; }


/* ===========================================================================
   TYPE
   ---------------------------------------------------------------------------
   This is the ONLY type authority in the system. No page may set its own
   font-family or a font-size outside this scale.
   =========================================================================== */

  .vds-display { font-size: var(--vds-text-display); line-height: var(--vds-leading-tight);
                 letter-spacing: var(--vds-tracking-display);
                 font-weight: var(--vds-weight-black); margin: 0; text-wrap: balance; }
  .vds-h1 { font-size: var(--vds-text-h1); line-height: var(--vds-leading-head);
            letter-spacing: var(--vds-tracking-head);
            font-weight: var(--vds-weight-bold); margin: 0; text-wrap: balance; }
  .vds-h2 { font-size: var(--vds-text-h2); line-height: var(--vds-leading-head);
            letter-spacing: var(--vds-tracking-head);
            font-weight: var(--vds-weight-bold); margin: 0; text-wrap: balance; }
  .vds-h3 { font-size: var(--vds-text-h3); line-height: var(--vds-leading-snug);
            font-weight: var(--vds-weight-semi); margin: 0; }
  .vds-h4 { font-size: var(--vds-text-h4); line-height: var(--vds-leading-snug);
            font-weight: var(--vds-weight-semi); margin: 0; }

  .vds-lede  { font-size: var(--vds-text-h4); line-height: var(--vds-leading-body);
               color: var(--vds-text-secondary); margin: 0; max-width: var(--vds-measure); }
  .vds-body  { font-size: var(--vds-text-body); line-height: var(--vds-leading-body);
               color: var(--vds-text-secondary); margin: 0; max-width: var(--vds-measure); }
  .vds-small { font-size: var(--vds-text-sm); line-height: var(--vds-leading-snug);
               color: var(--vds-text-secondary); margin: 0; }

  /* --- the mono voice. Weight stays 400; SIZE + TRACKING do the work. ----- */
  .vds-eyebrow { font-family: var(--vds-font-mono); font-weight: var(--vds-weight-regular);
                 font-size: var(--vds-text-micro); line-height: var(--vds-leading-snug);
                 letter-spacing: var(--vds-tracking-caps-xs); text-transform: uppercase;
                 color: var(--vds-text-tertiary); margin: 0;
                 inline-size: fit-content; max-inline-size: 100%; }
  .vds-label   { font-family: var(--vds-font-mono); font-weight: var(--vds-weight-regular);
                 font-size: var(--vds-text-xs); line-height: var(--vds-leading-snug);
                 letter-spacing: var(--vds-tracking-caps); text-transform: uppercase;
                 color: var(--vds-text-primary); margin: 0;
                 inline-size: fit-content; max-inline-size: 100%; }
  .vds-caps    { font-family: var(--vds-font-mono); font-weight: var(--vds-weight-regular);
                 font-size: var(--vds-text-micro); line-height: var(--vds-leading-snug);
                 letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
                 color: var(--vds-text-tertiary); margin: 0;
                 inline-size: fit-content; max-inline-size: 100%; }
  .vds-mono    { font-family: var(--vds-font-mono); font-size: 0.9em; letter-spacing: 0; }
  .vds-accent-text { color: var(--vds-accent-text); }

  .vds-code { font-family: var(--vds-font-mono); font-size: var(--vds-text-xs);
              line-height: var(--vds-leading-snug); letter-spacing: 0;
              background: var(--vds-surface-well); color: var(--vds-text-primary);
              border-radius: var(--vds-radius-xs); padding: 0.15em 0.42em; }
  pre.vds-code { display: block; padding: var(--vds-space-4); overflow-x: auto;
                 border: var(--vds-hairline) solid var(--vds-border-subtle);
                 border-radius: var(--vds-radius-sm); background: var(--vds-surface-sunken); }


/* ===========================================================================
   THE PILL — the reference component
   ---------------------------------------------------------------------------
     <a class="vds-pill vds-pill--primary vds-bloom-host vds-bloom vds-bloom--ember" href="#">
       <span class="vds-pill__label">GET API KEYS</span>
       <span class="vds-pill__sub">NO CARD REQUIRED</span>
     </a>

   `vds-bloom-host` is REQUIRED alongside `vds-bloom` and is easy to miss: the
   ink layer is selected as `.vds-bloom-host.vds-bloom::before`, and the bloom
   GEOMETRY (--vds-b1-x .. --vds-b3-ry) is declared on `.vds-bloom-host` only.
   Omit it and the pill still renders, correctly sized and coloured, with no
   bloom and no error anywhere — a silent failure. This example was missing the
   class until 2026-07-29; anyone who copied it shipped a flat capsule.

   Both spans are optional; a pill with bare text works and sets it as the
   label. Two spans give the stacked label+subtitle of the reference.

   The bloom is a MODIFIER (`.vds-bloom`), never baked in. A page where every
   button blooms is worse than one where a single one does.
   =========================================================================== */

  .vds-pill {
    --vds-pill-bg: var(--vds-surface);
    --vds-pill-fg: var(--vds-text-primary);
    --vds-pill-bd: var(--vds-border);
    --vds-pill-sub: var(--vds-text-tertiary);
    --vds-pill-shadow: var(--vds-shadow-float);

    position: relative;
    isolation: isolate;
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: var(--vds-space-1);
    min-height: var(--vds-tap-comfortable);
    padding-block: var(--vds-space-2);
    padding-inline: var(--vds-space-5);
    border: var(--vds-hairline) solid var(--vds-pill-bd);
    border-radius: var(--vds-radius-pill);
    background-color: var(--vds-pill-bg);
    color: var(--vds-pill-fg);
    box-shadow: var(--vds-pill-shadow), var(--vds-shadow-edge);
    font-family: var(--vds-font-mono-display);
    font-weight: var(--vds-weight-regular);
    font-size: var(--vds-text-xs);
    line-height: var(--vds-leading-snug);
    letter-spacing: var(--vds-tracking-caps);
    text-transform: uppercase;
    text-decoration: none;
    text-align: left;
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    /* a long label wraps inside the capsule instead of widening past its
       container — the capsule is never the thing that causes a sideways
       scroll on a 390px screen */
    max-width: 100%;
    transition:
      transform var(--vds-duration-2) var(--vds-ease),
      box-shadow var(--vds-duration-2) var(--vds-ease),
      background-color var(--vds-duration-1) linear,
      border-color var(--vds-duration-1) linear,
      color var(--vds-duration-1) linear;
  }

  .vds-pill__label { display: block; font: inherit; color: inherit;
                     inline-size: fit-content; max-inline-size: 100%; }
  .vds-pill__sub {
    display: block;
    /* the box hugs the glyphs, so it can never extend into the bloom */
    inline-size: fit-content;
    max-inline-size: 100%;
    font-family: var(--vds-font-mono);
    font-weight: var(--vds-weight-regular);
    font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm);
    line-height: var(--vds-leading-snug);
    text-transform: uppercase;
    color: var(--vds-pill-sub);
  }
  .vds-pill__row { display: flex; align-items: center; gap: var(--vds-space-2); }
  .vds-pill :where(svg) { flex: none; inline-size: 1em; block-size: 1em; }

  /* --- variants ----------------------------------------------------------- */
  .vds-pill--primary {
    --vds-pill-bg: var(--vds-surface-inverse);
    --vds-pill-fg: var(--vds-text-inverse);
    --vds-pill-bd: transparent;
    --vds-pill-sub: color-mix(in oklab, var(--vds-text-inverse) 78%, transparent);
    /* The capsule's own surface is the INVERSE of the page, so the bloom's
       blend identity must invert with it — the ink lands on the capsule, not
       on the page. This is the one place these two tokens are legitimately set
       outside the token file, and they are still set together. */
    --vds-bloom-mode: screen;
    --vds-bloom-null: var(--vds-rgb-black);
    --vds-bloom-alpha: 0.58;
  }
  :where([data-vds-theme="dark"], .vds-dark) .vds-pill--primary {
    --vds-bloom-mode: multiply;
    --vds-bloom-null: var(--vds-rgb-white);
    --vds-bloom-alpha: 0.5;
  }

  .vds-pill--secondary {
    --vds-pill-bg: var(--vds-surface);
    --vds-pill-fg: var(--vds-text-primary);
    --vds-pill-bd: var(--vds-border);
    /* --text-secondary, not --text-tertiary: a bloom LIFTS the backdrop under
       the subtitle, and tertiary measured 3.91:1 against a solar bloom on a
       dark capsule. One step up holds AA across every colourway. */
    --vds-pill-sub: var(--vds-text-secondary);
  }

  .vds-pill--ghost {
    --vds-pill-bg: transparent;
    --vds-pill-fg: var(--vds-text-secondary);
    --vds-pill-bd: var(--vds-border);
    --vds-pill-shadow: var(--vds-shadow-none);
    box-shadow: var(--vds-shadow-none);
  }

  /* --- sizes -------------------------------------------------------------- */
  .vds-pill--sm { min-height: var(--vds-tap-comfortable);
                  padding-inline: var(--vds-space-4);
                  font-size: var(--vds-text-micro);
                  letter-spacing: var(--vds-tracking-caps-sm); }
  .vds-pill--lg { min-height: calc(var(--vds-tap-comfortable) + var(--vds-space-5));
                  padding-inline: var(--vds-space-6);
                  font-size: var(--vds-text-sm); }
  .vds-pill--block { display: flex; inline-size: 100%; align-items: flex-start;
                     /* A full-width capsule holds ~3x the text of an inline
                        one, so its label sits deep into the gaussian clearing
                        where only ~40% of the ink has been erased. Measured
                        3.75:1 there at the inline defaults. Lower ink is the
                        right lever - the bloom still reads at the right edge,
                        which is where there is no text. */
                     --vds-bloom-clear-width: 74%;
                     --vds-bloom-alpha: 0.5; }

  /* --- states ------------------------------------------------------------- */
  .vds-pill:hover {
    transform: translateY(-1px);
    box-shadow: var(--vds-shadow-lift), var(--vds-shadow-edge);
  }
  .vds-pill--secondary:hover { --vds-pill-bd: var(--vds-border-strong); }
  .vds-pill--ghost:hover {
    --vds-pill-bg: var(--vds-accent-soft);
    --vds-pill-bd: var(--vds-accent-border);
    --vds-pill-fg: var(--vds-text-primary);
    transform: none;
    box-shadow: var(--vds-shadow-none);
  }
  .vds-pill:active {
    transform: translateY(0) scale(0.985);
    box-shadow: var(--vds-shadow-1), var(--vds-shadow-edge);
    transition-duration: var(--vds-duration-1);
  }
  .vds-pill:focus-visible {
    outline: var(--vds-focus-width) solid var(--vds-accent-primary);
    outline-offset: var(--vds-focus-offset);
  }
  .vds-pill:disabled,
  .vds-pill[aria-disabled="true"] {
    cursor: not-allowed;
    transform: none;
    /* never fade the label out of AA — recolour instead of reducing opacity */
    opacity: 1;
    --vds-pill-bg: var(--vds-surface-well);
    --vds-pill-fg: var(--vds-text-tertiary);
    --vds-pill-bd: var(--vds-border-subtle);
    --vds-pill-sub: var(--vds-text-tertiary);
    box-shadow: var(--vds-shadow-none);
  }
  .vds-pill:disabled::before, .vds-pill:disabled::after,
  .vds-pill[aria-disabled="true"]::before,
  .vds-pill[aria-disabled="true"]::after { opacity: 0; }

  /* a two-line capsule is taller, so the ink drops slightly to hold the same
     measured contrast as the one-line case */
  .vds-pill.vds-bloom:has(.vds-pill__sub) { --vds-bloom-alpha: 0.52; }


/* ===========================================================================
   PANEL — the sheet
   ---------------------------------------------------------------------------
   `container-type: inline-size` is what makes a panel correct in a sidebar, a
   grid cell and a full-bleed section with no breakpoint anywhere.
   =========================================================================== */

  .vds-panel {
    container-type: inline-size;
    container-name: vds-panel;
    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    gap: var(--vds-space-3);
    padding: var(--vds-space-5);
    border: var(--vds-hairline) solid var(--vds-border-subtle);
    border-radius: var(--vds-radius-lg);
    background-color: var(--vds-surface);
    color: var(--vds-text-primary);
    box-shadow: var(--vds-shadow-1);
    min-width: 0;
    transition: border-color var(--vds-duration-2) var(--vds-ease),
                box-shadow var(--vds-duration-2) var(--vds-ease),
                transform var(--vds-duration-2) var(--vds-ease);
  }
  .vds-panel--raised { box-shadow: var(--vds-shadow-3); border-color: transparent; }
  .vds-panel--inset  { background-color: var(--vds-surface-sunken);
                       box-shadow: var(--vds-shadow-none); }
  .vds-panel--flush  { padding: 0; overflow: hidden; }
  .vds-panel--tight  { padding: var(--vds-space-4); }
  .vds-panel--roomy  { padding: var(--vds-space-6); }
  .vds-panel--featured { border-color: var(--vds-accent-border);
                         box-shadow: var(--vds-shadow-3); }

  /* a panel in a narrow container tightens its own padding. No @media, no
     mobile file — it is correct wherever it is placed. */
  @container vds-panel (max-width: 24rem) {
    .vds-panel { padding: var(--vds-space-4); }
  }

  /* interactive panels only — a static card must not move under the cursor */
  :where(a, button).vds-panel:hover,
  .vds-panel--interactive:hover {
    border-color: var(--vds-accent-border);
    box-shadow: var(--vds-shadow-2);
    transform: translateY(-2px);
  }
  :where(a, button).vds-panel:focus-visible,
  .vds-panel--interactive:focus-visible {
    outline: var(--vds-focus-width) solid var(--vds-accent-primary);
    outline-offset: var(--vds-focus-offset);
  }

  /* the panel that carries colour. Much lower ink than a pill: this sits
     BEHIND body copy, and the ink is a wash, not the subject. */
  .vds-panel.vds-bloom {
    --vds-bloom-alpha: 0.20;
    --vds-bloom-clear: 1;
    --vds-bloom-clear-width: 82%;
    --vds-bloom-grain: 0.28;
  }

  .vds-panel__flag {
    position: absolute; top: 0; left: var(--vds-space-5);
    transform: translateY(-50%);
    font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
    padding: var(--vds-space-1) var(--vds-space-3);
    border-radius: var(--vds-radius-pill);
    background: var(--vds-surface-inverse); color: var(--vds-text-inverse);
  }


/* ===========================================================================
   LATTICE — the grid / structural background
   ---------------------------------------------------------------------------
   Two hairlines on a 32px pitch, masked with a gaussian so it has no edge. It
   is structure, not decoration: a floor for the eye under a hero or a pricing
   grid. Never a full-page tile.
   =========================================================================== */

  .vds-lattice { position: relative; isolation: isolate; --vds-lattice-pitch: 32px; }
  .vds-lattice > * { position: relative; z-index: 1; }
  .vds-lattice::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
      linear-gradient(to bottom, var(--vds-lattice-line) var(--vds-hairline), transparent var(--vds-hairline)),
      linear-gradient(to right,  var(--vds-lattice-line) var(--vds-hairline), transparent var(--vds-hairline));
    background-size: var(--vds-lattice-pitch) var(--vds-lattice-pitch);
    background-position: center center;
    -webkit-mask-image: radial-gradient(72% 62% at 50% 42%,
      rgb(var(--vds-rgb-black) / 1) 0%, rgb(var(--vds-rgb-black) / 0.55) 46%,
      rgb(var(--vds-rgb-black) / 0.16) 74%, transparent 100%);
    mask-image: radial-gradient(72% 62% at 50% 42%,
      rgb(var(--vds-rgb-black) / 1) 0%, rgb(var(--vds-rgb-black) / 0.55) 46%,
      rgb(var(--vds-rgb-black) / 0.16) 74%, transparent 100%);
  }
  .vds-lattice--fine { --vds-lattice-pitch: 20px; }
  .vds-lattice--wide { --vds-lattice-pitch: 56px; }
  .vds-lattice--top::before {
    -webkit-mask-image: linear-gradient(to bottom, rgb(var(--vds-rgb-black) / 1) 0%,
      rgb(var(--vds-rgb-black) / 0.4) 52%, transparent 100%);
    mask-image: linear-gradient(to bottom, rgb(var(--vds-rgb-black) / 1) 0%,
      rgb(var(--vds-rgb-black) / 0.4) 52%, transparent 100%);
  }


/* ===========================================================================
   SECTION HEADER · STAT · BADGE · CHIP · TABLE · FIELD
   =========================================================================== */

  .vds-sec { display: flex; flex-direction: column; gap: var(--vds-space-3);
             margin-block-end: var(--vds-space-6); }
  .vds-sec__eyebrow { font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
                      letter-spacing: var(--vds-tracking-caps-xs); text-transform: uppercase;
                      color: var(--vds-accent-text); margin: 0;
                      display: flex; align-items: center; gap: var(--vds-space-2); }
  .vds-sec__eyebrow::before { content: ""; inline-size: var(--vds-space-5);
                              block-size: var(--vds-hairline); flex: none;
                              background: var(--vds-accent-border); }
  .vds-sec__title { font-size: var(--vds-text-h2); line-height: var(--vds-leading-head);
                    letter-spacing: var(--vds-tracking-head);
                    font-weight: var(--vds-weight-bold); margin: 0; text-wrap: balance; }
  .vds-sec__lede { font-size: var(--vds-text-h4); line-height: var(--vds-leading-body);
                   color: var(--vds-text-secondary); margin: 0; max-width: var(--vds-measure); }
  .vds-sec--center { align-items: center; text-align: center; }
  .vds-sec--center .vds-sec__lede { margin-inline: auto; }

  .vds-stat { display: flex; flex-direction: column; gap: var(--vds-space-1); min-width: 0; }
  .vds-stat__value { font-family: var(--vds-font-mono-display);
                     font-weight: var(--vds-weight-regular);
                     font-size: var(--vds-text-stat); line-height: var(--vds-leading-tight);
                     letter-spacing: var(--vds-tracking-head);
                     color: var(--vds-text-primary); font-variant-numeric: tabular-nums; }
  .vds-stat__label { font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
                     letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
                     color: var(--vds-text-tertiary);
                     inline-size: fit-content; max-inline-size: 100%; }
  .vds-stat__delta { font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
                     letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase; }
  .vds-stat__delta--up   { color: var(--vds-status-ok); }
  .vds-stat__delta--down { color: var(--vds-status-error); }

  .vds-badge {
    display: inline-flex; align-items: center; gap: var(--vds-space-1);
    padding: var(--vds-space-1) var(--vds-space-3);
    border-radius: var(--vds-radius-pill);
    border: var(--vds-hairline) solid var(--vds-border);
    background: var(--vds-surface-sunken); color: var(--vds-text-secondary);
    font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
    line-height: var(--vds-leading-snug); white-space: nowrap;
  }
  .vds-badge--accent { background: var(--vds-accent-soft);
                       border-color: var(--vds-accent-border); color: var(--vds-accent-text); }
  .vds-badge--ok    { background: var(--vds-status-ok-bg);    border-color: transparent;
                      color: var(--vds-status-ok); }
  .vds-badge--warn  { background: var(--vds-status-warn-bg);  border-color: transparent;
                      color: var(--vds-status-warn); }
  .vds-badge--error { background: var(--vds-status-error-bg); border-color: transparent;
                      color: var(--vds-status-error); }
  .vds-badge--info  { background: var(--vds-status-info-bg);  border-color: transparent;
                      color: var(--vds-status-info); }
  .vds-badge--solid { background: var(--vds-surface-inverse); border-color: transparent;
                      color: var(--vds-text-inverse); }
  .vds-badge__dot { inline-size: var(--vds-space-2); block-size: var(--vds-space-2);
                    border-radius: var(--vds-radius-circle);
                    background: currentColor; flex: none; }

  .vds-chip {
    display: inline-flex; align-items: center; justify-content: center;
    gap: var(--vds-space-1);
    min-height: var(--vds-tap-comfortable);
    padding: var(--vds-space-2) var(--vds-space-4);
    border-radius: var(--vds-radius-pill);
    border: var(--vds-hairline) solid var(--vds-border);
    background: transparent; color: var(--vds-text-secondary); cursor: pointer;
    font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
    line-height: var(--vds-leading-snug); white-space: nowrap;
    transition: background-color var(--vds-duration-1) linear,
                border-color var(--vds-duration-1) linear,
                color var(--vds-duration-1) linear;
  }
  .vds-chip:hover { border-color: var(--vds-border-strong); color: var(--vds-text-primary); }
  .vds-chip:focus-visible { outline: var(--vds-focus-width) solid var(--vds-accent-primary);
                            outline-offset: var(--vds-focus-offset); }
  .vds-chip[aria-pressed="true"], .vds-chip[aria-selected="true"] {
    background: var(--vds-surface-inverse); border-color: transparent;
    color: var(--vds-text-inverse);
  }
  .vds-chip:disabled { cursor: not-allowed; color: var(--vds-text-tertiary);
                       border-color: var(--vds-border-subtle);
                       background: var(--vds-surface-well); }

  /* the wrapper is what stops a wide table from scrolling the whole page */
  .vds-table-wrap { inline-size: 100%; max-inline-size: 100%; overflow-x: auto;
                    border: var(--vds-hairline) solid var(--vds-border-subtle);
                    border-radius: var(--vds-radius-md);
                    background: var(--vds-surface);
                    -webkit-overflow-scrolling: touch; }
  .vds-table { inline-size: 100%; border-collapse: collapse;
               font-size: var(--vds-text-sm); color: var(--vds-text-primary); }
  .vds-table caption { text-align: left;
                       padding: var(--vds-space-4) var(--vds-space-4) 0;
                       font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
                       letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
                       color: var(--vds-text-tertiary); }
  .vds-table th {
    text-align: left; font-weight: var(--vds-weight-regular);
    font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
    color: var(--vds-text-tertiary);
    padding: var(--vds-space-3) var(--vds-space-4);
    border-bottom: var(--vds-hairline) solid var(--vds-border);
    white-space: nowrap; background: var(--vds-surface-sunken);
  }
  .vds-table td { padding: var(--vds-space-3) var(--vds-space-4);
                  border-bottom: var(--vds-hairline) solid var(--vds-border-subtle);
                  vertical-align: top; }
  .vds-table tbody tr:last-child td { border-bottom: 0; }
  .vds-table--hover tbody tr { transition: background-color var(--vds-duration-1) linear; }
  .vds-table--hover tbody tr:hover { background: var(--vds-surface-sunken); }
  .vds-table__num { font-family: var(--vds-font-mono); font-variant-numeric: tabular-nums; }

  .vds-field { display: flex; flex-direction: column; gap: var(--vds-space-2); min-width: 0; }
  .vds-field__label { font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
                      letter-spacing: var(--vds-tracking-caps-sm); text-transform: uppercase;
                      color: var(--vds-text-tertiary); }
  .vds-field__req { color: var(--vds-status-error); }
  .vds-input, .vds-select, .vds-textarea {
    inline-size: 100%; max-inline-size: 100%;
    min-height: var(--vds-tap-comfortable);
    font-family: var(--vds-font-sans); font-size: var(--vds-text-body);
    line-height: var(--vds-leading-snug); color: var(--vds-text-primary);
    background: var(--vds-surface);
    border: var(--vds-hairline) solid var(--vds-border);
    border-radius: var(--vds-radius-sm);
    padding: var(--vds-space-3) var(--vds-space-4);
    transition: border-color var(--vds-duration-1) linear,
                box-shadow var(--vds-duration-1) linear;
    -webkit-appearance: none; appearance: none;
  }
  .vds-textarea { min-height: calc(var(--vds-space-9) + var(--vds-space-2)); resize: vertical; }
  .vds-select { padding-inline-end: var(--vds-space-8);
                background-image:
                  linear-gradient(45deg, transparent 50%, currentColor 50%),
                  linear-gradient(135deg, currentColor 50%, transparent 50%);
                background-position: calc(100% - 19px) center, calc(100% - 14px) center;
                background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
  .vds-input::placeholder, .vds-textarea::placeholder {
    color: var(--vds-text-tertiary); opacity: 1; }
  .vds-input:hover, .vds-select:hover, .vds-textarea:hover {
    border-color: var(--vds-border-strong); }
  .vds-input:focus-visible, .vds-select:focus-visible, .vds-textarea:focus-visible {
    outline: none; border-color: var(--vds-accent-primary);
    box-shadow: 0 0 0 3px var(--vds-accent-soft);
  }
  .vds-input:disabled, .vds-select:disabled, .vds-textarea:disabled {
    background: var(--vds-surface-well); color: var(--vds-text-tertiary);
    cursor: not-allowed; border-color: var(--vds-border-subtle);
  }
  .vds-input[aria-invalid="true"], .vds-textarea[aria-invalid="true"],
  .vds-select[aria-invalid="true"] { border-color: var(--vds-status-error); }
  .vds-field__hint  { font-size: var(--vds-text-xs); color: var(--vds-text-tertiary); }
  .vds-field__error { font-size: var(--vds-text-xs); color: var(--vds-status-error);
                      display: flex; align-items: center; gap: var(--vds-space-1); }
  .vds-field__mono  { font-family: var(--vds-font-mono); letter-spacing: 0; }


/* ===========================================================================
   NAV + MOBILE DRAWER
   ---------------------------------------------------------------------------
   One component, one behaviour, everywhere. See DESIGN-SYSTEM.md: the nav is
   duplicated with drift across the site (65 distinct structural shapes in a
   754-page sample), so styling alone cannot converge it — it must be RENDERED
   from one place. These are the styles that single renderer targets.

   THE STUCK-DRAWER FIX IS STRUCTURAL.
   The reported "hamburger always remains" is a stuck open-state that survives
   a resize: state lives in a class on an injected node and nothing clears it
   when the viewport grows. Rather than adding another JS listener that can
   also fail, the desktop rule below FORCES the drawer closed and the scrim
   away above the nav breakpoint, whatever the state attribute says. CSS wins,
   so a stuck attribute cannot produce a stuck overlay. Regression is
   impossible rather than merely discouraged.

   This and prefers-reduced-motion are the ONLY viewport @media in the system.
   The nav is page chrome; it has no container to query.
   =========================================================================== */

  .vds-nav { display: flex; align-items: center; gap: var(--vds-space-1); flex-wrap: wrap; }
  .vds-nav__item {
    position: relative; display: inline-flex; align-items: center;
    gap: var(--vds-space-2);
    min-height: var(--vds-tap-comfortable);
    padding: var(--vds-space-2) var(--vds-space-3);
    border-radius: var(--vds-radius-sm);
    color: var(--vds-text-secondary); text-decoration: none;
    font-family: var(--vds-font-sans); font-size: var(--vds-text-sm);
    font-weight: var(--vds-weight-medium); letter-spacing: var(--vds-tracking-body);
    background: transparent; border: 0; cursor: pointer;
    transition: color var(--vds-duration-1) linear,
                background-color var(--vds-duration-1) linear;
  }
  .vds-nav__item:hover { color: var(--vds-text-primary); background: var(--vds-surface-sunken); }
  .vds-nav__item:focus-visible { outline: var(--vds-focus-width) solid var(--vds-accent-primary);
                                 outline-offset: var(--vds-focus-offset); }
  .vds-nav__item[aria-current="page"] { color: var(--vds-text-primary);
                                        background: var(--vds-surface-sunken); }
  .vds-nav__item[aria-current="page"]::after {
    content: ""; position: absolute;
    inset-inline: var(--vds-space-3); inset-block-end: var(--vds-space-1);
    block-size: 2px; border-radius: var(--vds-radius-xs);
    background: var(--vds-accent-primary);
  }

  .vds-nav--tabs { gap: 0; border-bottom: var(--vds-hairline) solid var(--vds-border-subtle);
                   overflow-x: auto; flex-wrap: nowrap; }
  .vds-nav--tabs .vds-nav__item {
    border-radius: 0; padding: var(--vds-space-3) var(--vds-space-4);
    font-family: var(--vds-font-mono); font-size: var(--vds-text-micro);
    font-weight: var(--vds-weight-regular); letter-spacing: var(--vds-tracking-caps-sm);
    text-transform: uppercase; white-space: nowrap;
  }
  .vds-nav--tabs .vds-nav__item:hover { background: transparent; }
  .vds-nav--tabs .vds-nav__item::after { display: none; }
  .vds-nav--tabs .vds-nav__item[aria-selected="true"],
  .vds-nav--tabs .vds-nav__item[aria-current="page"] {
    background: transparent; color: var(--vds-text-primary);
    box-shadow: inset 0 -2px 0 var(--vds-accent-primary);
  }

  /* --- the bar ------------------------------------------------------------ */
  .vds-topnav {
    position: sticky; top: 0; z-index: 50;
    display: flex; align-items: center; gap: var(--vds-space-4);
    min-height: calc(var(--vds-tap-comfortable) + var(--vds-space-4));
    padding-inline: var(--vds-space-gutter);
    background: color-mix(in oklab, var(--vds-surface) 88%, transparent);
    -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    border-bottom: var(--vds-hairline) solid var(--vds-border-subtle);
  }
  .vds-topnav__brand { display: inline-flex; align-items: center; gap: var(--vds-space-2);
                       font-family: var(--vds-font-mono); font-size: var(--vds-text-xs);
                       letter-spacing: var(--vds-tracking-caps); text-transform: uppercase;
                       color: var(--vds-text-primary); text-decoration: none; flex: none; }
  .vds-topnav__spacer { flex: 1 1 auto; min-width: 0; }
  .vds-topnav__desktop { display: none; }
  .vds-topnav__actions { display: flex; align-items: center; gap: var(--vds-space-2); }

  /* --- the hamburger ------------------------------------------------------ */
  .vds-burger {
    display: inline-flex; flex-direction: column; justify-content: center;
    align-items: center; gap: 4px;
    inline-size: var(--vds-tap-comfortable);
    block-size: var(--vds-tap-comfortable);
    flex: none;
    padding: 0; border: var(--vds-hairline) solid var(--vds-border);
    border-radius: var(--vds-radius-sm);
    background: transparent; color: var(--vds-text-primary); cursor: pointer;
  }
  .vds-burger__bar { display: block; inline-size: 16px; block-size: 1.5px;
                     border-radius: var(--vds-radius-xs); background: currentColor;
                     transition: transform var(--vds-duration-2) var(--vds-ease),
                                 opacity var(--vds-duration-1) linear; }
  .vds-burger:focus-visible { outline: var(--vds-focus-width) solid var(--vds-accent-primary);
                              outline-offset: var(--vds-focus-offset); }
  [data-vds-nav="open"] .vds-burger__bar:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
  [data-vds-nav="open"] .vds-burger__bar:nth-child(2) { opacity: 0; }
  [data-vds-nav="open"] .vds-burger__bar:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

  /* --- the drawer ---------------------------------------------------------
     `visibility: hidden` while closed is REQUIRED, not a content-hiding hack:
     without it the off-screen links stay in the tab order and are announced by
     a screen reader while invisible. It is paired with `inert` on the element
     in the renderer. Content is never display:none. */
  .vds-drawer {
    position: fixed; inset-block: 0; inset-inline-start: 0; z-index: 60;
    inline-size: min(20rem, 86vw);
    max-inline-size: 100%;
    display: flex; flex-direction: column; gap: var(--vds-space-4);
    padding: var(--vds-space-5) var(--vds-space-4);
    background: var(--vds-surface);
    border-inline-end: var(--vds-hairline) solid var(--vds-border-subtle);
    box-shadow: var(--vds-shadow-3);
    overflow-y: auto; overscroll-behavior: contain;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--vds-duration-3) var(--vds-ease),
                visibility 0s linear var(--vds-duration-3);
  }
  .vds-drawer__scrim {
    position: fixed; inset: 0; z-index: 55;
    background: rgb(var(--vds-rgb-black) / 0.45);
    opacity: 0; visibility: hidden;
    transition: opacity var(--vds-duration-3) var(--vds-ease),
                visibility 0s linear var(--vds-duration-3);
  }
  [data-vds-nav="open"] .vds-drawer {
    transform: translateX(0); visibility: visible;
    transition: transform var(--vds-duration-3) var(--vds-ease-out), visibility 0s;
  }
  [data-vds-nav="open"] .vds-drawer__scrim {
    opacity: 1; visibility: visible;
    transition: opacity var(--vds-duration-3) var(--vds-ease-out), visibility 0s;
  }
  .vds-drawer__head { display: flex; align-items: center; justify-content: space-between;
                      gap: var(--vds-space-3); }
  .vds-drawer__nav { display: flex; flex-direction: column; gap: var(--vds-space-1); }
  .vds-drawer__nav .vds-nav__item { inline-size: 100%; }

  /* THE STRUCTURAL FIX. Above the nav breakpoint the drawer and scrim are
     forced away and the desktop nav is forced on, whatever [data-vds-nav]
     says. A stuck attribute therefore cannot produce a stuck overlay. */
  @media (min-width: 60rem) {
    .vds-topnav__desktop { display: flex; align-items: center; gap: var(--vds-space-1); }
    .vds-burger { display: none; }
    .vds-drawer,
    [data-vds-nav="open"] .vds-drawer {
      transform: translateX(-100%); visibility: hidden; transition: none;
    }
    .vds-drawer__scrim,
    [data-vds-nav="open"] .vds-drawer__scrim {
      opacity: 0; visibility: hidden; transition: none;
    }
  }
}

/* -----------------------------------------------------------------------
   UNLAYERED OVERRIDE — restores .vds-pill's own padding on <button> pills.
   This page loads Tailwind's Preflight reset (css/tailwind.min.css) UNLAYERED
   in <head>. Unlayered CSS always wins over @layer'd CSS regardless of
   selector specificity, so Tailwind's
   `button,input,optgroup,select,textarea{...padding:0}` strips the
   `padding-block`/`padding-inline` declared on `.vds-pill` above whenever the
   pill IS a <button> element, even though that rule is more specific. This is
   the same "unlayered beats layered" bug class the page's own inline <style>
   already works around for `.vds-pill--primary` text colour, and that this
   file documents again further down for <figure> margins ("unlayered
   Tailwind beats layered vds.components"). Left unfixed, a
   <button class="vds-pill"> sits flush against its own 999px-radius stadium
   border, so both text lines poke past the rounded left edge instead of
   sitting inset with even padding.
   ----------------------------------------------------------------------- */
button.vds-pill {
  padding-block: var(--vds-space-2);
  padding-inline: var(--vds-space-5);
}

/* -----------------------------------------------------------------------
   UNLAYERED OVERRIDE — restores .vds-pill's own colour on <a> pills.
   Exactly the bug above, one property across: Preflight's unlayered
   `a{color:inherit;text-decoration:inherit}` beats the layered
   `.vds-pill{color:var(--vds-pill-fg)}` at line 392, so an anchor pill takes
   its PARENT's ink instead of its variant's. The block above already names
   this as "the same bug class the page's own inline <style> already works
   around for .vds-pill--primary text colour" - that inline patch was never
   promoted to a shared rule, which is why it only ever protected one page.

   Measured across the 86 pages that load the unlayered reset: the Dashboard
   CTA on 404.html, 500.html and api-reference.html renders rgb(22,17,42) on
   rgb(11,7,22) - 1.09:1 - while its own --vds-pill-fg resolves correctly to
   #fbfafd. An invisible primary call to action on the site's API reference.

   Reading the custom property rather than a literal keeps every variant
   right: each of --vds-pill--primary/-ghost/-quiet/-subtle sets its own
   --vds-pill-fg, so this single rule follows all of them, and follows the
   theme too. Anchors only; <button> pills were never affected, since
   Preflight's `a` rule does not select them.
   ----------------------------------------------------------------------- */
a.vds-pill {
  color: var(--vds-pill-fg);
}


/* ===========================================================================
   UTILITIES — intrinsic layout
   ---------------------------------------------------------------------------
   Every grid here reflows by construction. None has a mobile variant, because
   none needs one.
   =========================================================================== */

@layer vds.utilities {

  .vds-container { inline-size: 100%; max-inline-size: var(--vds-container);
                   margin-inline: auto; padding-inline: var(--vds-space-gutter); }
  .vds-container--wide { max-inline-size: var(--vds-container-wide); }
  .vds-measure { max-inline-size: var(--vds-measure); }
  .vds-measure--narrow { max-inline-size: var(--vds-measure-narrow); }
  .vds-section { padding-block: var(--vds-space-section); }

  /* THE grid. minmax(min(100%, ...)) is what makes it impossible to overflow:
     in a container narrower than the track floor, the track collapses to the
     container instead of overhanging it. Without the min(100%, ...) wrapper
     this is the single most common source of horizontal scroll on mobile. */
  .vds-grid {
    display: grid;
    gap: var(--vds-space-gap);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--vds-col-min)), 1fr));
  }
  .vds-grid--sm { --vds-col-min: var(--vds-col-min-sm); }
  .vds-grid--lg { --vds-col-min: var(--vds-col-min-lg); }
  /* a grid that must not go below two columns even when narrow */
  .vds-grid--pair {
    grid-template-columns: repeat(auto-fit, minmax(min(50% - var(--vds-space-gap), var(--vds-col-min-sm)), 1fr));
  }

  .vds-stack   { display: flex; flex-direction: column; gap: var(--vds-space-4); }
  .vds-row     { display: flex; flex-wrap: wrap; align-items: center; gap: var(--vds-space-3); }
  .vds-cluster { display: flex; flex-wrap: wrap; gap: var(--vds-space-2); }
  .vds-rule    { block-size: var(--vds-hairline); border: 0;
                 background: var(--vds-border-subtle); margin: 0; }

  /* the label a screen reader needs and the eye does not. NEVER display:none
     on content — this keeps it in the accessibility tree. */
  .vds-sr {
    position: absolute; inline-size: 1px; block-size: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
  }
}


/* ===========================================================================
   MOTION PREFERENCES
   ---------------------------------------------------------------------------
   The bloom STOPS. It does not slow down and it does not fade — a reduced
   motion user gets one static frame, which is the composition the thing was
   designed around anyway. Transitions collapse to near-zero rather than zero
   so a :hover state still fires its transitionend where something listens.
   =========================================================================== */

@layer vds.motion {
  @media (prefers-reduced-motion: reduce) {
    .vds *,
    .vds *::before,
    .vds *::after,
    .vds-drawer, .vds-drawer__scrim {
      animation-duration: 1ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 1ms !important;
      scroll-behavior: auto !important;
    }
    .vds-bloom::before { animation: none !important; }
    .vds-pill:hover, .vds-panel--interactive:hover,
    :where(a, button).vds-panel:hover { transform: none; }
  }
}


/* ===========================================================================
   FALLBACK — engines without color-mix()
   ---------------------------------------------------------------------------
   Pre-2023 Chrome/Safari/Firefox. The bloom degrades to an sRGB alpha stack:
   same geometry, same blend modes, interpolated in sRGB. Visibly a touch
   muddier through the middle of each ramp; the grain covers most of it.
   Everything else in this file is already universal.
   =========================================================================== */

@layer vds.components {
  @supports not (color: color-mix(in oklab, red, blue)) {
    .vds-bloom-host.vds-bloom::before {
      background-image:
        linear-gradient(to right,
          rgb(var(--vds-bloom-null) / var(--vds-bloom-clear)) 0%,
          rgb(var(--vds-bloom-null) / 0) var(--vds-bloom-clear-width)),
        radial-gradient(var(--vds-b1-rx) var(--vds-b1-ry) at var(--vds-b1-x) var(--vds-b1-y),
          rgb(var(--vds-bloom-srgb-1) / calc(var(--vds-bloom-alpha) * var(--vds-k0))) var(--vds-s0),
          rgb(var(--vds-bloom-srgb-1) / calc(var(--vds-bloom-alpha) * var(--vds-k2))) var(--vds-s2),
          rgb(var(--vds-bloom-srgb-1) / calc(var(--vds-bloom-alpha) * var(--vds-k4))) var(--vds-s4),
          rgb(var(--vds-bloom-srgb-1) / calc(var(--vds-bloom-alpha) * var(--vds-k6))) var(--vds-s6),
          rgb(var(--vds-bloom-srgb-1) / 0) 100%),
        radial-gradient(var(--vds-b2-rx) var(--vds-b2-ry) at var(--vds-b2-x) var(--vds-b2-y),
          rgb(var(--vds-bloom-srgb-2) / calc(var(--vds-bloom-alpha) * var(--vds-k0))) var(--vds-s0),
          rgb(var(--vds-bloom-srgb-2) / calc(var(--vds-bloom-alpha) * var(--vds-k2))) var(--vds-s2),
          rgb(var(--vds-bloom-srgb-2) / calc(var(--vds-bloom-alpha) * var(--vds-k4))) var(--vds-s4),
          rgb(var(--vds-bloom-srgb-2) / calc(var(--vds-bloom-alpha) * var(--vds-k6))) var(--vds-s6),
          rgb(var(--vds-bloom-srgb-2) / 0) 100%),
        radial-gradient(var(--vds-b3-rx) var(--vds-b3-ry) at var(--vds-b3-x) var(--vds-b3-y),
          rgb(var(--vds-bloom-srgb-3) / calc(var(--vds-bloom-alpha) * var(--vds-k0))) var(--vds-s0),
          rgb(var(--vds-bloom-srgb-3) / calc(var(--vds-bloom-alpha) * var(--vds-k2))) var(--vds-s2),
          rgb(var(--vds-bloom-srgb-3) / calc(var(--vds-bloom-alpha) * var(--vds-k4))) var(--vds-s4),
          rgb(var(--vds-bloom-srgb-3) / calc(var(--vds-bloom-alpha) * var(--vds-k6))) var(--vds-s6),
          rgb(var(--vds-bloom-srgb-3) / 0) 100%);
    }
  }

  /* Without mask-image the grain would cover a whole flat surface. Cheaper to
     drop it than to ship a dirty panel. */
  /* Colour-free feature probe. @supports validates the property/value pair, so
     `mask-image: none` fails on an engine that does not implement the property
     at all — which is exactly what we are testing. Using a literal colour in
     the probe would be a token-conformance violation for no benefit. */
  @supports not ((mask-image: none) or (-webkit-mask-image: none)) {
    .vds-bloom-host.vds-bloom::after { opacity: 0; }
  }
}

/* ===========================================================================
   CARDGRID — the row grid a row of cards SHARES
   ---------------------------------------------------------------------------
   THE PROBLEM. Four cards side by side are four independent block boxes. Each
   one stacks its own eyebrow, title, price, rule, list and CTA at whatever
   height its own content produces, so nothing lines up across the row: the
   titles sit at three different baselines, the rules are a few pixels apart,
   and the CTA of the card with the shortest list floats 30-50px above the
   others. `align-items: stretch` and `height: 100%` do not fix this. They make
   the BOXES the same height; the contents inside them are still four
   unrelated stacks.

   THE FIX. The row is one grid, and every card is a SUBGRID of it. The card
   stops defining rows and instead adopts the row grid's, so row 3 is the same
   3rd row in all four cards — the tallest title in the row sets the title row
   for everybody, and the CTA row starts at one y across the whole row. It is
   structural: nothing is measured in JS and no height is hand-tuned, so it
   stays correct when the copy changes, when the currency toggle rewrites the
   prices, and when one card grows a fifth bullet.

     <div class="vds-cardgrid" style="--vds-cardgrid-rows: 6">
       <article class="vds-cardgrid__card">
         <p>eyebrow</p> <h3>title</h3> <p>price</p>
         <p>meta</p> <ul>features</ul> <a>CTA</a>
       </article>
       ... three more, same six children in the same order ...
     </div>

   THE ONE RULE OF ADOPTION: --vds-cardgrid-rows is the number of DIRECT
   children of the card, counting the longest card. Children auto-place in
   source order, one per row, so the Nth child of every card lands in row N.
   A card with fewer children simply leaves the trailing rows empty — which is
   exactly how a card with an extra link under its CTA keeps its BUTTON
   aligned with the other three instead of dragging it up. Anything that must
   not take a row (a "POPULAR" badge, a corner flag) is positioned absolutely
   and is therefore not a grid item at all.

   WRAPPING IS FREE. The card is placed with `grid-row: span N` rather than
   `1 / -1`, so the tracks are implicit and a row that wraps to two-up simply
   starts a second band of N rows. Cards 1-2 align with each other, cards 3-4
   align with each other, and no breakpoint was written to arrange it.

   COLUMNS. The default is intrinsic — repeat(auto-fit, minmax(min(MIN,100%),
   1fr)) — so the count follows the container and there is no mobile variant to
   forget. `min()` against 100% is what stops a 17rem minimum from overflowing
   a 320px screen. When a fixed split is required (four price tiers want 4/2/1
   and never 3), --vds-cardgrid-cols takes a whole grid-template-columns value
   and overrides it.

   GUTTERS. A subgrid inherits its parent's gaps, so --vds-cardgrid-gap is both
   the space BETWEEN cards and the rhythm BETWEEN rows inside every card. Where
   a specific row needs more air than that, put a margin on the row: a margin
   grows the SHARED track, so it stays aligned across the row by construction.

   FALLBACK. Subgrid is Chrome 117+, Safari 16+, Firefox 71+. Below that the
   card degrades to a flex column with the footer pushed down by
   `margin-block-start: auto`, which preserves the one alignment that carries
   the most meaning — the CTAs share a bottom edge — and loses the per-row
   alignment above it. Mark the row that should absorb the slack with
   .vds-cardgrid__grow and the footer with .vds-cardgrid__foot; both are inert
   wherever subgrid is supported.

   COST. Zero. This is layout, not paint: no extra element, no observer, no
   animation, and no second pass.
   =========================================================================== */

@layer vds.components {

  .vds-cardgrid {
    /* number of rows one card contributes — SET THIS PER USE */
    --vds-cardgrid-rows: 6;
    /* narrowest a card may get before the row drops a column */
    --vds-cardgrid-min: 17rem;
    --vds-cardgrid-gap: var(--vds-space-4);
    /* the whole grid-template-columns value. The default is intrinsic and is
       right for a row of N unknown cards; override it with a fixed split when
       the count matters (four price tiers must go 4 / 2 / 1, never 3). */
    --vds-cardgrid-cols: repeat(auto-fit, minmax(min(var(--vds-cardgrid-min), 100%), 1fr));

    display: grid;
    grid-template-columns: var(--vds-cardgrid-cols);
    gap: var(--vds-cardgrid-gap);
    /* the card fills its band; the subgrid then divides that band */
    align-items: stretch;
  }

  .vds-cardgrid__card {
    /* a card is a plane, not an object: square by default, because the
       component that needed this most is a ruled grid of panels. */
    --vds-cardgrid-radius: var(--vds-radius-none);

    grid-row: span var(--vds-cardgrid-rows);
    display: grid;
    grid-template-rows: subgrid;
    border-radius: var(--vds-cardgrid-radius);
    /* nothing inside a card may widen the column it lives in */
    min-inline-size: 0;
  }

  /* A direct child is a row. It must not create its own scroll or push the
     column wider than its track — the one failure mode of putting arbitrary
     content into a shared grid. */
  .vds-cardgrid__card > * { min-inline-size: 0; }

  /* --- no subgrid: keep the CTA line, lose the rest ------------------------ */
  @supports not (grid-template-rows: subgrid) {
    .vds-cardgrid__card {
      grid-row: auto;
      display: flex;
      flex-direction: column;
      gap: var(--vds-cardgrid-gap);
    }
    .vds-cardgrid__card > .vds-cardgrid__grow { flex: 1 1 auto; }
    .vds-cardgrid__card > .vds-cardgrid__foot { margin-block-start: auto; }
  }
}


/* ===========================================================================
   CARDGRID — RAISE, LIFT, TRAY, FIELD AND MOTIF
   (appended 2026-07-29, pricing lane; written to be reused by the dashboard)
   ---------------------------------------------------------------------------
   Five additions, all opt-in, none of which touch the rules above. They exist
   because a row of price tiers needs four things the bare cardgrid does not
   give it, and every one of them was about to be written as page-local CSS on
   pricing.html — which is exactly how a design system stops being one.

   WHY THESE ARE COMPONENTS AND NOT PAGE CSS. The same row is going into the
   user dashboard next. If the tray and the lift live in
   css/vedika-pricing-reframe.css they get copied, the copy drifts, and the two
   surfaces stop agreeing within a sprint. Everything here is therefore
   expressed against .vds-cardgrid* and the token layer only, with no
   page selector anywhere in it.

     .vds-cardgrid__card--lift    hover/focus elevation
     .vds-cardgrid__card--tray    the pale frame that marks the featured card
     .vds-cardgrid__trayflag      a badge that rides the tray's top edge
     .vds-cardgrid__ornament      the element a decorative mark is drawn on
     .vds-field                   a pigment layer that fades in and out
     .vds-motif--*                a faint generated line-art mark

   ---------------------------------------------------------------------------
   THE PSEUDO-ELEMENT BUDGET, WHICH IS THE CONSTRAINT THAT SHAPED ALL OF THIS

   The first version of this block put the lift shadow on `::after`, the tray
   on `::before` and the motif on `::before` — and every one of them was wrong
   for the same reason. `.vds-bloom-host.vds-bloom` ALREADY OWNS BOTH
   PSEUDO-ELEMENTS of the element it is on: ::before is the ink layer and
   ::after is the grain. A price card is a bloom host, so all three components
   landed on top of the bloom.

   The failure was not a clean override either, which is what made it worth
   recording. `.vds-bloom-host.vds-bloom::before` is specificity (0,2,1) and
   `[class*="vds-motif--"]::before` is (0,1,1), so the bloom kept its
   background-image and LOST its geometry: the motif's `inline-size`,
   `block-size` and `translate` applied to the ink layer, which shrank the
   bloom to a 190px square and threw it 34% outside the card. Measured
   consequence: saturation p95 on the featured card fell from 0.629 to 0.424
   and coloured rectangles appeared floating off three card corners.

   So the rule for this component family is: NEVER TAKE A PSEUDO-ELEMENT ON A
   NODE A CONSUMER MIGHT ALSO MAKE A BLOOM HOST. Decoration goes on a real
   child (.vds-cardgrid__ornament) and elevation is expressed with properties
   on the card itself.

   ---------------------------------------------------------------------------
   1. THE LIFT — TRANSFORM IS THE ONLY THING THAT ANIMATES

   The measured constraint on this codebase is blunt: a background-position
   animation ran at 9fps / 119ms, and a drifting CSS bloom costs ~12.9ms for
   one instance. Anything that is not compositor-only is off the table for a
   hover that can fire on four large elements.

   `transform` is therefore the only property in the transition list. The
   shadow still STEPS on hover — a lift with no shadow change reads as a slide
   rather than a rise — but it is not transitioned, so it never costs a frame.
   The step is invisible in use because it happens under 240ms of motion.

   TOUCH. Hover is not an affordance on a touch device, so nothing may be
   revealed by it. This component only ELEVATES: every piece of information is
   already visible at rest, and the featured tier is marked by the tray below,
   which is permanent and needs no pointer. :focus-within is included so a
   keyboard reaches the same state a mouse does.
   =========================================================================== */

@layer vds.components {

  .vds-cardgrid__card--lift {
    --vds-lift-y: -6px;
    --vds-lift-duration: var(--vds-duration-2, .24s);
    position: relative;
    transition: transform var(--vds-lift-duration) var(--vds-ease-out, cubic-bezier(.22,1,.36,1));
  }
  .vds-cardgrid__card--lift:hover,
  .vds-cardgrid__card--lift:focus-within {
    transform: translateY(var(--vds-lift-y));
    box-shadow: var(--vds-cardgrid-lift-shadow, var(--vds-shadow-lift));
  }

  /* ---------------------------------------------------------------------------
     2. THE TRAY — how a featured card is elevated WITHOUT breaking the subgrid

     The obvious way to mark the recommended tier is to make its card taller.
     That is also the one way guaranteed to break this component: every card in
     a .vds-cardgrid shares one set of row tracks, so a taller card either
     stretches the tracks for all of them or drops out of the subgrid.

     The tray is the non-destructive form, and it is drawn with two spread
     shadows rather than a box. A spread shadow paints STRICTLY OUTSIDE the
     border box, so the ring appears proud of the card on all four sides
     without tinting the card's interior and without adding an element — and,
     since it is not in flow, it cannot move a row. The measured 0.00px
     alignment survives it by construction.

     The reach is clamped against the grid gap: a ring wider than half the seam
     collides with the neighbouring card. --vds-tray-w defaults to 5px, which
     is exactly half of the 10px seam this was tuned against.
     ------------------------------------------------------------------------- */

  .vds-cardgrid__card--tray {
    --vds-tray-w: 5px;
    --vds-tray-bg: var(--vds-accent-soft);
    --vds-tray-bd: var(--vds-accent-border);
    box-shadow:
      0 0 0 var(--vds-tray-w) var(--vds-tray-bg),
      0 0 0 calc(var(--vds-tray-w) + 1px) var(--vds-tray-bd),
      var(--vds-cardgrid-card-shadow, var(--vds-shadow-float));
  }
  .vds-cardgrid__card--tray:hover,
  .vds-cardgrid__card--tray:focus-within {
    box-shadow:
      0 0 0 var(--vds-tray-w) var(--vds-tray-bg),
      0 0 0 calc(var(--vds-tray-w) + 1px) var(--vds-tray-bd),
      var(--vds-cardgrid-lift-shadow, var(--vds-shadow-lift));
  }

  /* A flag that rides the TOP EDGE OF THE TRAY rather than floating over the
     card. position:absolute keeps it out of the row map, exactly like the ring. */
  .vds-cardgrid__trayflag {
    position: absolute;
    z-index: 3;
    inset-block-start: calc(-1 * var(--vds-tray-w));
    inset-inline-end: calc(-1 * var(--vds-tray-w));
    transform: translateY(-50%);
  }

  /* ---------------------------------------------------------------------------
     2b. THE ORNAMENT — the safe place to draw on a card

     An absolutely-positioned child, so it takes no subgrid row, and a real
     element, so its ::before is genuinely free even when the card itself is a
     bloom host. Everything decorative hangs off this and nothing decorative
     touches the card's own pseudo-elements.

     It is positioned against the card's PADDING box (the default containing
     block for an abspos child of a grid container with no grid placement),
     which is what the motif wants anyway — the mark should sit inside the
     padding, not bleed to the border.
     ------------------------------------------------------------------------- */
  .vds-cardgrid__ornament {
    position: absolute;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
    border-radius: inherit;
  }

  /* ---------------------------------------------------------------------------
     3. THE FIELD — pigment that has no edge

     A band of colour behind content is normally a panel, and a panel is a
     rectangle the reader can see. This is the same thing with its edges
     removed: a layer that fades IN at the top and OUT at the bottom, so
     content can cross its boundary without crossing a visible line.

     That is what lets a card straddle two grounds. It is also why this is a
     layer and not a background on a wrapper — a wrapper would put another
     rectangle into the hierarchy, which is the failure it exists to avoid.

     It is `inert` by construction: z-index below its parent's content,
     pointer-events none, aria-hidden expected on the element itself.
     ------------------------------------------------------------------------- */

  .vds-field {
    --vds-field-in: 18%;      /* fully present by here */
    --vds-field-hold: 58%;    /* still fully present at here */
    position: absolute;
    z-index: -1;
    pointer-events: none;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to bottom,
        transparent 0, #000 var(--vds-field-in),
        #000 var(--vds-field-hold), transparent 100%);
    mask-image: linear-gradient(to bottom,
        transparent 0, #000 var(--vds-field-in),
        #000 var(--vds-field-hold), transparent 100%);
  }
  .vds-field > canvas { display: block; width: 100%; height: 100%; }

  /* ---------------------------------------------------------------------------
     4. THE MOTIF — per-card identity that is not a fifth background colour

     Four cards each carrying a different saturated ground is the "multi theme
     competing" failure. A faint generated line mark gives each card its own
     identity at a fraction of the visual weight, and because these are CSS
     gradients they are generated in-repo: no asset is fetched, nothing is
     traced from a reference image, and there is no request to fail.

     ALPHA IS THE WHOLE POINT. --vds-motif-a defaults to 0.05 and the mark is
     parked in a corner the type does not reach. A motif that shows up in a
     contrast measurement is too strong; that is the acceptance test, not taste.
     ------------------------------------------------------------------------- */

  [class*="vds-motif--"] {
    --vds-motif-a: 0.05;
    --vds-motif-size: 190px;
  }
  /* :where() CONTRIBUTES ZERO SPECIFICITY, AND THAT IS THE POINT.

     A motif needs a positioned host so its mark has something to be absolute
     against, but the host is USUALLY .vds-cardgrid__ornament, which is
     position:absolute. Written as a plain `[class*="vds-motif--"]` selector
     this is (0,1,0) — identical to `.vds-cardgrid__ornament` — so the two tie
     and source order decides. It tied and won, the ornament computed
     `relative` instead of `absolute`, collapsed to zero height, and every card
     gained an eighth in-flow child: the subgrid spans seven rows, so the
     Enterprise aside was pushed into an implicit eighth row and rendered on
     top of its own CTA.

     At zero specificity this is a floor rather than a competitor: any host
     that declares its own position wins automatically. */
  :where([class*="vds-motif--"]) { position: relative; }
  [class*="vds-motif--"]::before {
    content: "";
    position: absolute;
    z-index: -1;
    inline-size: var(--vds-motif-size);
    block-size: var(--vds-motif-size);
    inset-block-start: 0;
    inset-inline-end: 0;
    translate: 34% -34%;
    pointer-events: none;
    background-repeat: no-repeat;
    background-position: center;
  }

  /* concentric rings */
  .vds-motif--rings::before {
    background-image: repeating-radial-gradient(circle at 50% 50%,
        transparent 0 13px,
        rgb(var(--vds-motif-rgb, var(--vds-rgb-accent, 108 118 250)) / var(--vds-motif-a)) 13px 14px);
  }
  /* a starburst of spokes */
  .vds-motif--burst::before {
    background-image: repeating-conic-gradient(from 0deg at 50% 50%,
        rgb(var(--vds-motif-rgb, var(--vds-rgb-accent, 108 118 250)) / var(--vds-motif-a)) 0deg 0.7deg,
        transparent 0.7deg 15deg);
    -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 34%, transparent 72%);
    mask-image: radial-gradient(circle at 50% 50%, #000 34%, transparent 72%);
  }
  /* a globe: meridians over parallels */
  .vds-motif--globe::before {
    background-image:
      repeating-linear-gradient(to bottom,
        rgb(var(--vds-motif-rgb, var(--vds-rgb-accent, 108 118 250)) / var(--vds-motif-a)) 0 1px,
        transparent 1px 17px),
      repeating-linear-gradient(to right,
        rgb(var(--vds-motif-rgb, var(--vds-rgb-accent, 108 118 250)) / var(--vds-motif-a)) 0 1px,
        transparent 1px 17px);
    -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 40%, transparent 70%);
    mask-image: radial-gradient(circle at 50% 50%, #000 40%, transparent 70%);
  }
  /* a corner lattice, for the most sober tier */
  .vds-motif--lattice::before {
    background-image: repeating-linear-gradient(135deg,
        rgb(var(--vds-motif-rgb, var(--vds-rgb-accent, 108 118 250)) / var(--vds-motif-a)) 0 1px,
        transparent 1px 13px);
    -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 38%, transparent 74%);
    mask-image: radial-gradient(circle at 50% 50%, #000 38%, transparent 74%);
  }
}

/* Reduced motion removes the TRANSFORM only. The shadow cross-fade is an
   opacity change on a stationary element, which is not motion, and removing
   the elevation entirely would leave a hover with no feedback at all. */
@media (prefers-reduced-motion: reduce) {
  @layer vds.components {
    .vds-cardgrid__card--lift,
    .vds-cardgrid__card--lift:hover,
    .vds-cardgrid__card--lift:focus-within { transform: none; }
  }
}


/* ===========================================================================
   THE REVIEW CARD, THE STAGGERED SET, AND THE SHEET TILE
   (appended 2026-07-29, reviews / use-cases / case-studies lane)
   ---------------------------------------------------------------------------
   Three components and one correction. All opt-in; nothing above is renamed,
   re-valued or overridden except the one bug fixed in section 0.

     0. .vds-bloom-at--base grain axis      (correction)
     1. .vds-quote                          a review card
     2. .vds-quoteset                       the staggered set it lives in
     3. .vds-cardgrid__card--sheet          a white plane in a shared grid

   ---------------------------------------------------------------------------
   0. THE GRAIN FOLLOWED THE WRONG AXIS

   The dither on .vds-bloom-host.vds-bloom::after is masked left-to-right,
   because the reference geometry (--right, --corner) puts its ink on the
   right. .vds-bloom-at--base moves the ink to the BOTTOM and does not move
   the mask with it, so on a base-lit card the grain concentrated along the
   right edge — nowhere near the pigment it is supposed to be dithering, and
   visible as a dirty vertical band on an otherwise white card.

   The fix is one rule with the axis rotated. It cannot regress the other
   presets because it is selected on .vds-bloom-at--base.
   =========================================================================== */

@layer vds.components {

  .vds-bloom-host.vds-bloom.vds-bloom-at--base::after {
    -webkit-mask-image: linear-gradient(to top,
      rgb(var(--vds-rgb-black) / 1) 0%,
      rgb(var(--vds-rgb-black) / 0.8) 18%,
      rgb(var(--vds-rgb-black) / 0.25) 46%,
      transparent 82%);
    mask-image: linear-gradient(to top,
      rgb(var(--vds-rgb-black) / 1) 0%,
      rgb(var(--vds-rgb-black) / 0.8) 18%,
      rgb(var(--vds-rgb-black) / 0.25) 46%,
      transparent 82%);
  }


/* ---------------------------------------------------------------------------
   1. THE REVIEW CARD

     <figure class="vds-quote vds-bloom-host vds-bloom vds-bloom-at--base
                    vds-bloom--rose">
       <blockquote class="vds-quote__text"><p>...</p></blockquote>
       <figcaption class="vds-quote__by">
         <p class="vds-quote__role">CTO</p>
         <p class="vds-quote__org">Astrology Mobile App</p>
       </figcaption>
     </figure>

   FOUR CLASSES ON PURPOSE. `vds-bloom-host` is REQUIRED alongside `vds-bloom`
   — see THE PILL. `vds-bloom-at--base` is the geometry and `--rose` is the
   colourway; both are chosen per card, which is the entire point of the set.

   THE TINT RISES FROM THE BOTTOM EDGE AND FADES UPWARD INTO WHITE. That is a
   different geometry from the corner-pooled treatment on pricing, and it is
   not a new one: .vds-bloom-at--base already parks all three fronds below the
   element (y 118-128%) with radii wide enough to bleed up into it. The only
   thing tuned here is INTENSITY.

   IT IS STATIC, AND THAT IS A BUDGET DECISION, NOT A TASTE ONE. A static
   bloom rasterises once and is then a composited texture: measured at zero
   cost for 200 instances. A drifting one costs ~12.9ms EACH. A set of review
   cards is by definition a repeated element, so `.vds-bloom--drift` must
   never appear on one. Where a page needs live motion, it gets ONE WebGL
   sheet behind the whole section instead — see js/vedika-sheet-gl.js.

   THERE IS NO AVATAR SLOT, AND THAT IS DELIBERATE. These quotes are
   attributed by ROLE ONLY, because that is the attribution the business
   actually holds. A circular headshot and a full name would be an identity
   invented to fill a rectangle, which is not a design decision — it is a
   false claim about a customer relationship. So the vertical rhythm the
   avatar would have occupied is solved typographically instead: an oversized
   serif quotation mark as a masthead, a hairline, and the role set in the
   mono voice at label size where the reference would have set a name.

   CONTRAST IS MEASURED OVER THE INK, NOT BESIDE IT. The attribution sits at
   the BOTTOM of the card, which is exactly where a base-rising tint is
   densest — so it is the attribution, not the quote, that governs the alpha
   ceiling. --vds-bloom-alpha is held at 0.28 for that reason: at that value
   the densest composited pixel under --vds-text-secondary still clears AA by
   a wide margin on all ten registers, and raising it is a contrast change
   dressed up as a colour change.
   --------------------------------------------------------------------------- */

  .vds-quote {
    container-type: inline-size;
    container-name: vds-quote;

    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--vds-space-5);
    padding: var(--vds-space-6) var(--vds-space-5) var(--vds-space-5);
    /* generous, per the reference — this is an object on a plane, not a
       division of one, so it takes the large radius rather than --none */
    border-radius: var(--vds-radius-lg);
    border: var(--vds-hairline) solid var(--vds-border-subtle);
    background-color: var(--vds-surface);
    color: var(--vds-text-primary);
    /* soft and wide rather than tight and dark: the card should read as
       lifted off the page, not as an outlined box with a drop shadow */
    box-shadow: var(--vds-shadow-float);
    min-inline-size: 0;
    margin: 0;
  }

  /* the ink. Low, because it sits behind reading matter and under the
     attribution — see the contrast note above. */
  .vds-quote.vds-bloom {
    --vds-bloom-alpha: 0.28;
    --vds-bloom-grain: 0.34;
    /* pull the fronds down and flatten them, so the pigment is a rising tide
       across the bottom third rather than a disc centred under the card */
    --vds-b1-x: 50%;  --vds-b1-y: 126%;  --vds-b1-rx: 82%;  --vds-b1-ry: 78%;
    --vds-b2-x: 84%;  --vds-b2-y: 134%;  --vds-b2-rx: 66%;  --vds-b2-ry: 70%;
    --vds-b3-x: 18%;  --vds-b3-y: 131%;  --vds-b3-rx: 72%;  --vds-b3-ry: 74%;
  }

  /* THE MASTHEAD. This is the avatar's replacement and it is type, not a
     picture: the display serif's own open-quote at a size no reader mistakes
     for content. aria-hidden in the markup; it is punctuation the blockquote
     already implies. */
  .vds-quote__mark {
    font-family: var(--vds-font-display);
    font-size: 3.25rem;
    line-height: 0.62;
    font-weight: var(--vds-weight-regular);
    color: var(--vds-border-strong);
    margin: 0;
    block-size: 0.62em;
    overflow: visible;
    user-select: none;
  }

  /* THE QUOTE. Display serif at reading size with a comfortable measure —
     narrower than the page measure, because a quotation is read as a unit and
     a 68ch line inside a card is a paragraph. */
  .vds-quote__text {
    margin: 0;
    font-family: var(--vds-font-display);
    font-size: var(--vds-text-h3);
    line-height: var(--vds-leading-snug);
    letter-spacing: var(--vds-tracking-body);
    color: var(--vds-text-primary);
    max-inline-size: 34ch;
    text-wrap: pretty;
  }
  .vds-quote__text > p { margin: 0; }

  /* THE ATTRIBUTION. `margin-block-start: auto` is what makes a row of cards
     of different quote lengths put their attributions on a shared bottom
     edge without any grid involvement. */
  .vds-quote__by {
    margin: 0;
    margin-block-start: auto;
    padding-block-start: var(--vds-space-4);
    border-block-start: var(--vds-hairline) solid var(--vds-border);
    display: flex;
    flex-direction: column;
    gap: var(--vds-space-1);
  }

  /* the mono voice, at the size and tracking the reference gives a NAME.
     Weight stays 400 — Fragment Mono has no other. */
  .vds-quote__role {
    margin: 0;
    font-family: var(--vds-font-mono);
    font-weight: var(--vds-weight-regular);
    font-size: var(--vds-text-xs);
    line-height: var(--vds-leading-snug);
    letter-spacing: var(--vds-tracking-caps);
    text-transform: uppercase;
    color: var(--vds-text-primary);
  }
  .vds-quote__org {
    margin: 0;
    font-family: var(--vds-font-sans);
    font-size: var(--vds-text-sm);
    line-height: var(--vds-leading-snug);
    color: var(--vds-text-secondary);
  }

  /* an optional rating row. Icons only, never a number the page cannot
     source; sized as an icon so --vds-text-muted is legal for it. */
  .vds-quote__rating {
    display: flex;
    align-items: center;
    gap: var(--vds-space-1);
    color: var(--vds-text-tertiary);
    font-size: var(--vds-text-sm);
  }

  @container vds-quote (max-width: 20rem) {
    .vds-quote { padding: var(--vds-space-5) var(--vds-space-4); }
    .vds-quote__mark { font-size: 2.5rem; }
  }


/* ---------------------------------------------------------------------------
   2. THE STAGGERED SET

   The reference does not put its cards on one baseline. It drops every other
   one, which is what stops three white rectangles from reading as a table
   row. Two things make that safe here rather than fragile:

     * `align-items: start`, so a card is its own height and a long quote
       never stretches its neighbours. The set is deliberately NOT a
       .vds-cardgrid: subgrid exists to make a row of cards agree, and this
       component wants the opposite.

     * the offset is applied inside a @container query, so it exists only
       while the set is actually multi-column. At one column a stagger is
       just an inconsistent gap, and this collapses it to zero with no
       breakpoint and no mobile stylesheet.

   THE OFFSET IS A TRANSFORM, NOT A MARGIN, AND THAT IS NOT A PREFERENCE.
   `margin-block-start` was the obvious implementation and it silently did
   nothing: the review card is a <figure>, and Tailwind's preflight — which is
   loaded UNLAYERED on most pages in this repo — zeroes the margin of figure,
   blockquote, p and the headings. An unlayered rule beats a layered one
   whatever the specificity, so every margin css/vedika-ds.css sets on those
   elements loses on any page that also loads Tailwind. Nothing errors and
   nothing warns; the cards just line up.

   `transform` has no reset anywhere in that preflight, is compositor-only,
   and cannot be clobbered the same way. The set pays for the translated
   overhang with its own padding-block-end so the last card never escapes the
   section — the one thing a transform does not do for you.
   --------------------------------------------------------------------------- */

  .vds-quoteset {
    /* THE FLOOR IS IN rem, NOT px, AND THAT IS THE ONE INTERESTING LINE HERE.
       --vds-col-min* are px because a 260px card is 260px whatever the page
       does. But several pages in this repo shrink the ROOT font size to get a
       "70% zoom" desktop look (css/desktop-zoom-70.css sets html to 11.2px),
       which shrinks every rem-based container on the page — including
       Tailwind's max-w-7xl — while leaving a px track floor at full size. The
       result is a set that silently drops from three columns to two on
       exactly the pages that wanted more content on screen, not less.

       A rem floor tracks the page's own type scale, so the card stays the
       same number of CHARACTERS wide either way and the column count is
       preserved. 21rem is 336px at a 16px root, i.e. the same card the px
       floor was aiming at. */
    --vds-quoteset-min: 21rem;
    --vds-quoteset-step: var(--vds-space-7);
    container-type: inline-size;
    container-name: vds-quoteset;
    display: grid;
    gap: var(--vds-space-5);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--vds-quoteset-min)), 1fr));
    align-items: start;
  }

  /* 44rem is where the floor above admits a second column with its gutter
     (21 + 1.5 + 21). Below it the set is one column, and a stagger on one
     column is just an inconsistent gap — so it collapses to zero here, with
     no breakpoint and no mobile stylesheet. */
  @container vds-quoteset (min-width: 44rem) {
    /* an up-down-up rhythm rather than a descending staircase: the middle of
       a three-up dropping furthest reads as a sag, and a staircase reads as a
       mistake in one direction. */
    .vds-quoteset > :nth-child(3n + 2) {
      transform: translateY(var(--vds-quoteset-step));
    }
    .vds-quoteset > :nth-child(3n + 3) {
      transform: translateY(calc(var(--vds-quoteset-step) * 0.38));
    }
    /* reclaim the overhang the transform does not add to the flow */
    .vds-quoteset { padding-block-end: var(--vds-quoteset-step); }
  }


/* ---------------------------------------------------------------------------
   3. THE SHEET TILE

   .vds-cardgrid gives a row of cards a SHARED set of rows. It deliberately
   ships with no skin at all — `--vds-cardgrid-radius: none` and no background
   — because the component that needed it first was a ruled grid of panels.

   A grid of use cases or case-study records wants the same alignment with a
   skin: a white plane, hairline, and the same bottom-rising tint as the
   review card, so that a page carrying both reads as one material.

   This is a MODIFIER on the existing card, not a new layout. It adds paint
   and padding and touches nothing structural, so subgrid alignment across the
   row stays exact — every card in a row carries the same padding, so the
   shared tracks are still shared.

   The tint is one third the review card's, because a tile is smaller, there
   are more of them on screen at once, and a grid of twenty full-strength
   washes is the "multi theme competing" failure.
   --------------------------------------------------------------------------- */

  .vds-cardgrid__card--sheet {
    --vds-cardgrid-radius: var(--vds-radius-md);
    padding: var(--vds-space-5);
    border: var(--vds-hairline) solid var(--vds-border-subtle);
    background-color: var(--vds-surface);
    color: var(--vds-text-primary);
    box-shadow: var(--vds-shadow-1);
    text-decoration: none;
  }
  .vds-cardgrid__card--sheet.vds-bloom {
    --vds-bloom-alpha: 0.20;
    --vds-bloom-grain: 0.26;
  }
  /* a tile that is itself the link. The lift is on the existing --lift
     modifier; this only removes the underline and keeps the focus ring. */
  a.vds-cardgrid__card--sheet:focus-visible {
    outline: var(--vds-focus-width) solid var(--vds-accent-primary);
    outline-offset: var(--vds-focus-offset);
  }
  .vds-cardgrid__card--sheet .vds-cardgrid__title {
    font-family: var(--vds-font-display);
    font-size: var(--vds-text-h3);
    line-height: var(--vds-leading-snug);
    font-weight: var(--vds-weight-regular);
    color: var(--vds-text-primary);
    margin: 0;
    text-wrap: balance;
  }
  .vds-cardgrid__card--sheet .vds-cardgrid__body {
    font-size: var(--vds-text-sm);
    line-height: var(--vds-leading-body);
    color: var(--vds-text-secondary);
    margin: 0;
  }
  /* the mono cue that names the register a tile belongs to */
  .vds-cardgrid__card--sheet .vds-cardgrid__key {
    font-family: var(--vds-font-mono);
    font-weight: var(--vds-weight-regular);
    font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm);
    text-transform: uppercase;
    color: var(--vds-text-tertiary);
    margin: 0;
  }
}


/* ---------------------------------------------------------------------------
   PAGE FURNITURE THESE THREE NEED, AND NOTHING MORE
   ---------------------------------------------------------------------------
   A plain light ground for a set of white cards to sit on. The reference is
   explicit that nothing competes with the cards, so this is a flat surface
   token, not a gradient and not an image.
   --------------------------------------------------------------------------- */

@layer vds.utilities {
  .vds-ground        { background-color: var(--vds-surface-sunken); }
  .vds-ground--paper { background-color: var(--vds-surface); }
  .vds-ground--well  { background-color: var(--vds-surface-well); }

  /* the display face, applied to a heading that is not a .vds-* type class */
  .vds-serif { font-family: var(--vds-font-display); font-weight: var(--vds-weight-regular); }
}


/* ===========================================================================
   THE COMB — a generated field of bars
   (appended 2026-07-29, chatbot-API lane)
   ---------------------------------------------------------------------------
   A dense field of thin vertical bars whose heights trace a smooth envelope
   and whose tips dissolve. It is the poster counterpart to the dust bloom:
   where the bloom is diffuse pigment, the comb is DISCRETE and STRUCTURED, and
   the two are not interchangeable. Reach for the comb when the surface should
   read as a printed exhibition sheet; reach for the bloom when it should read
   as dispersing ink.

   GENERATED, NOT FETCHED. There is no image here and no canvas and no script.
   The whole graphic is two gradients and two masks, which means it retints
   from a token, resizes with its box, prints, renders with JavaScript
   disabled, and costs one composited layer.

   THE CONSTRUCTION, which is the part worth reading
   -------------------------------------------------
   Getting bars whose HEIGHTS follow a curve, out of a language with no
   per-bar addressing, is a two-mask problem and the order matters.

     ENVELOPE  on the element itself. A radial-gradient with a HARD stop
               (#000 0 99.5%, transparent 100%) is not a fade — it is a
               SILHOUETTE, an ellipse anchored below the bottom edge. Masking
               with it clips everything outside the curve. That single hard
               stop is what makes the bar TOPS crisp; an early attempt used a
               soft radial falloff here and the result faded at the left and
               right EDGES of the field instead of at the bar tips, which is a
               different picture entirely and the wrong one.

     COMB      on ::before. A repeating-linear-gradient at 90deg with hard
               stops, so it is stripes rather than a ramp. Masking the ink with
               it cuts the silhouette into discrete bars.

     DISSOLVE  the ::before's own background: a vertical gradient, opaque at
               the baseline and gone at the top. Because the envelope has
               already clipped each column to its own height, this reads as
               each bar fading toward ITS tip — the form dissolving rather
               than ending. Four stops, not two: a two-stop ramp has a visible
               kink in its first derivative exactly where the eye is looking.

   Two SINGLE-LAYER masks on two elements, deliberately, rather than one
   two-layer mask with `mask-composite: intersect`. Intersect is Safari 15.4+
   and Chrome 120+, and where it is missing the layers UNION instead — which
   does not degrade, it inverts, painting full-height stripes across the whole
   box. Single-layer masking is supported everywhere the rest of this file is.

   ::before IS OWNED. `.vds-comb` takes its own ::before, so — per the rule
   recorded in the cardgrid block above — a comb must NEVER also be a bloom
   host. It has no reason to be: they are two answers to the same question.

   COST. Static. No animation, no observer, no second pass. The element is
   inert to the pointer and carries no text, so it takes `aria-hidden="true"`
   in markup and contributes nothing to the accessibility tree.
   =========================================================================== */

@layer vds.components {

  .vds-comb {
    /* the single ink. ONE hue per comb — that is the component's whole thesis */
    --vds-comb-ink: var(--vds-ink-terracotta);

    /* the field. --pitch is bar-to-bar; --bar is how much of it is ink. */
    --vds-comb-pitch: 9px;
    --vds-comb-bar: 2.5px;

    /* the envelope ellipse, as fractions of the element. The centre sits just
       BELOW the bottom edge so the curve meets the baseline steeply and the
       outermost bars are not sliced to points by an oblique tangent. */
    --vds-comb-x:  40%;
    --vds-comb-y:  104%;
    --vds-comb-rx: 46%;
    --vds-comb-ry: 106%;

    /* the dissolve: fully opaque up to --hold, gone by --dissolve */
    --vds-comb-hold: 32%;
    --vds-comb-dissolve: 100%;

    position: relative;
    display: block;
    min-block-size: 0;
    pointer-events: none;

    -webkit-mask-image: radial-gradient(var(--vds-comb-rx) var(--vds-comb-ry)
                        at var(--vds-comb-x) var(--vds-comb-y),
                        rgb(var(--vds-rgb-black)) 0 99.5%, transparent 100%);
            mask-image: radial-gradient(var(--vds-comb-rx) var(--vds-comb-ry)
                        at var(--vds-comb-x) var(--vds-comb-y),
                        rgb(var(--vds-rgb-black)) 0 99.5%, transparent 100%);
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
  }

  .vds-comb::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: linear-gradient(to top in oklab,
      var(--vds-comb-ink) 0%,
      var(--vds-comb-ink) var(--vds-comb-hold),
      color-mix(in oklab, var(--vds-comb-ink) 55%, transparent)
        calc(var(--vds-comb-hold) + (var(--vds-comb-dissolve) - var(--vds-comb-hold)) * 0.42),
      color-mix(in oklab, var(--vds-comb-ink) 18%, transparent)
        calc(var(--vds-comb-hold) + (var(--vds-comb-dissolve) - var(--vds-comb-hold)) * 0.74),
      transparent var(--vds-comb-dissolve));
    -webkit-mask-image: repeating-linear-gradient(90deg,
      rgb(var(--vds-rgb-black)) 0, rgb(var(--vds-rgb-black)) var(--vds-comb-bar),
      transparent var(--vds-comb-bar), transparent var(--vds-comb-pitch));
            mask-image: repeating-linear-gradient(90deg,
      rgb(var(--vds-rgb-black)) 0, rgb(var(--vds-rgb-black)) var(--vds-comb-bar),
      transparent var(--vds-comb-bar), transparent var(--vds-comb-pitch));
  }

  /* --- envelope presets, so nobody hand-writes four percentages ----------- */
  /* rises from the left, peaks left-of-centre, falls away to the right */
  .vds-comb--rise { --vds-comb-x: 40%;  --vds-comb-rx:  46%; --vds-comb-ry: 106%; }
  /* a symmetric bell */
  .vds-comb--bell { --vds-comb-x: 50%;  --vds-comb-rx:  45%; --vds-comb-ry:  98%; }
  /* a monotonic ramp up to the right — a quarter ellipse anchored off-corner */
  .vds-comb--ramp { --vds-comb-x: 100%; --vds-comb-rx: 100%; --vds-comb-ry: 100%; }

  /* --- density ----------------------------------------------------------- */
  .vds-comb--fine  { --vds-comb-pitch: 6px;  --vds-comb-bar: 1.5px; }
  .vds-comb--coarse{ --vds-comb-pitch: 14px; --vds-comb-bar: 4px; }

  /* --- colourways. ONE hue each, by construction. ------------------------ */
  .vds-comb--terracotta { --vds-comb-ink: var(--vds-ink-terracotta); }
  .vds-comb--saffron    { --vds-comb-ink: var(--vds-ink-saffron); }
  .vds-comb--teal       { --vds-comb-ink: var(--vds-ink-teal); }
}


/* ===========================================================================
   THE SHELF — sheets that are offset, that overlap, and that make room
   (appended 2026-07-29, chatbot-API lane)
   ---------------------------------------------------------------------------
   A row of sheets pinned at different heights and overlapping slightly, in
   which hovering one brings it forward AND the others move out of its way.
   The rearrangement is the component; a shadow that gets bigger is not what
   this is.

   TRANSFORM, AND ONLY TRANSFORM. Measured on this codebase, animating
   `background-position` runs at 9fps / 119ms per frame. Every resting offset
   and every response here is a single `translate3d` + `scale` on one composited
   layer, so the whole row rearranges without a layout pass or a repaint.

   DIRECTION WITHOUT JAVASCRIPT, AND WITHOUT NUMBERING THE SHEETS.
   The row has to open AROUND the hovered sheet: everything to its left slides
   further left, everything to its right slides further right. Both halves fall
   out of two selectors and neither needs to know the index or the count:

     .vds-shelf__sheet:has(~ .vds-shelf__sheet:hover)   -> I am to the LEFT
     .vds-shelf__sheet:hover ~ .vds-shelf__sheet        -> I am to the RIGHT

   Add or remove a sheet and it still works. `:focus-within` is carried
   alongside `:hover` in every rule so a keyboard user gets the same
   rearrangement when they tab into a sheet.

   HOVER REVEALS NOTHING, WHICH IS THE POINT.
   Hover is not available on touch, so anything it uncovered would be
   unreachable for a large share of visitors. This component therefore only
   ever MOVES things: every word on every sheet is on screen at rest. There is
   nothing to fall back to because nothing was hidden.

   Belt and braces, the hover rules are additionally gated behind
   `@media (hover: hover) and (pointer: fine)`. iOS synthesises a :hover on tap
   and then LEAVES IT APPLIED until the next tap elsewhere, so an ungated rule
   does not degrade to "no effect" on a phone — it degrades to one sheet stuck
   out of position. Gating is what makes the touch experience the resting one.

   REDUCED MOTION removes the transition and zeroes every delta, so the row is
   static and the hovered sheet is distinguished by elevation alone.

   WRAPPING. The resting offsets and the overlap only make sense while the row
   is a row. A container query zeroes them the moment the grid drops to a
   single column, so the stacked layout is plain and nothing hangs off the
   side of a 320px screen. No viewport breakpoint is involved.
   =========================================================================== */

@layer vds.components {

  .vds-shelf {
    --vds-shelf-min: 15rem;
    --vds-shelf-gap: var(--vds-space-3);
    /* how far the hovered sheet rises */
    --vds-shelf-lift: 14px;
    /* how far its neighbours move aside */
    --vds-shelf-yield: 18px;

    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--vds-shelf-min), 100%), 1fr));
    gap: var(--vds-shelf-gap);
    align-items: start;
    container-type: inline-size;
    container-name: vds-shelf;
    /* the sheets translate outside their tracks by design */
    padding-block: var(--vds-space-5);
  }

  .vds-shelf__sheet {
    /* the RESTING pin, set per sheet by the page: this is the stagger */
    --vds-sheet-x: 0px;
    --vds-sheet-y: 0px;
    /* the RESPONSE, driven entirely by the state selectors below */
    --vds-sheet-dx: 0px;
    --vds-sheet-dy: 0px;
    --vds-sheet-scale: 1;

    position: relative;
    z-index: 1;
    min-inline-size: 0;
    transform:
      translate3d(calc(var(--vds-sheet-x) + var(--vds-sheet-dx)),
                  calc(var(--vds-sheet-y) + var(--vds-sheet-dy)), 0)
      scale(var(--vds-sheet-scale));
    transition:
      transform var(--vds-duration-3) var(--vds-ease-out),
      box-shadow var(--vds-duration-3) var(--vds-ease-out);
  }

  @media (hover: hover) and (pointer: fine) {
    /* the sheet under the pointer comes forward */
    .vds-shelf__sheet:hover {
      --vds-sheet-dy: calc(var(--vds-shelf-lift) * -1);
      --vds-sheet-scale: 1.025;
      z-index: 3;
    }
    /* everything to its LEFT slides further left */
    .vds-shelf__sheet:has(~ .vds-shelf__sheet:hover) {
      --vds-sheet-dx: calc(var(--vds-shelf-yield) * -1);
      --vds-sheet-dy: calc(var(--vds-shelf-lift) * 0.4);
      --vds-sheet-scale: 0.985;
    }
    /* everything to its RIGHT slides further right */
    .vds-shelf__sheet:hover ~ .vds-shelf__sheet {
      --vds-sheet-dx: var(--vds-shelf-yield);
      --vds-sheet-dy: calc(var(--vds-shelf-lift) * 0.4);
      --vds-sheet-scale: 0.985;
    }
  }

  /* keyboard parity, ungated: a tab stop must behave the same on every device */
  .vds-shelf__sheet:focus-within {
    --vds-sheet-dy: calc(var(--vds-shelf-lift) * -1);
    --vds-sheet-scale: 1.025;
    z-index: 3;
  }
  .vds-shelf__sheet:has(~ .vds-shelf__sheet:focus-within) {
    --vds-sheet-dx: calc(var(--vds-shelf-yield) * -1);
    --vds-sheet-dy: calc(var(--vds-shelf-lift) * 0.4);
    --vds-sheet-scale: 0.985;
  }
  .vds-shelf__sheet:focus-within ~ .vds-shelf__sheet {
    --vds-sheet-dx: var(--vds-shelf-yield);
    --vds-sheet-dy: calc(var(--vds-shelf-lift) * 0.4);
    --vds-sheet-scale: 0.985;
  }

  /* --- one column: no stagger, no overlap, nothing hanging off the edge --- */
  @container vds-shelf (max-width: 32rem) {
    .vds-shelf__sheet {
      --vds-sheet-x: 0px;
      --vds-sheet-y: 0px;
    }
  }

  /* --- the sheet itself: cream stock, square corners, printed edge -------- */
  .vds-shelf__paper {
    display: flex;
    flex-direction: column;
    block-size: 100%;
    background-color: var(--vds-surface-paper);
    border-radius: var(--vds-radius-none);
    box-shadow: var(--vds-shadow-2);
    padding: var(--vds-space-6) var(--vds-space-5) var(--vds-space-5);
  }

  @media (prefers-reduced-motion: reduce) {
    .vds-shelf__sheet {
      transition: none;
      --vds-sheet-dx: 0px !important;
      --vds-sheet-dy: 0px !important;
      --vds-sheet-scale: 1 !important;
    }
  }
}


/* ===========================================================================
   COMB COLOURWAYS — the ten-hue ladder
   (appended 2026-07-29, chatbot-API lane — surface correction pass)
   ---------------------------------------------------------------------------
   Six lines of nothing, on purpose: a comb colourway is ONE token, because the
   component's whole thesis is one hue per comb. The inks and the reasoning are
   in css/vedika-tokens.css TIER 2g; the names match the TIER 2f bloom
   registers exactly, so `.vds-bloom--iris` and `.vds-comb--iris` on the same
   card are the same hue, and the subject mapping documented there carries over
   without a second table to keep in sync.

   The original three (--terracotta / --saffron / --teal) are above and are
   unchanged.
   =========================================================================== */

@layer vds.components {
  .vds-comb--rose     { --vds-comb-ink: var(--vds-ink-comb-rose); }
  .vds-comb--clay     { --vds-comb-ink: var(--vds-ink-comb-clay); }
  .vds-comb--sand     { --vds-comb-ink: var(--vds-ink-comb-sand); }
  .vds-comb--leaf     { --vds-comb-ink: var(--vds-ink-comb-leaf); }
  .vds-comb--jade     { --vds-comb-ink: var(--vds-ink-comb-jade); }
  .vds-comb--lagoon   { --vds-comb-ink: var(--vds-ink-comb-lagoon); }
  .vds-comb--cerulean { --vds-comb-ink: var(--vds-ink-comb-cerulean); }
  .vds-comb--iris     { --vds-comb-ink: var(--vds-ink-comb-iris); }
  .vds-comb--orchid   { --vds-comb-ink: var(--vds-ink-comb-orchid); }
  .vds-comb--plum     { --vds-comb-ink: var(--vds-ink-comb-plum); }

  /* --- a comb that is a RULE rather than a field -------------------------
     The comb was built at 10rem+ as a poster graphic. Used as the top edge of
     a card in a dense grid it wants to be a band — 44-64px — and at that
     height the default 32% hold leaves almost no solid ink before the
     dissolve starts. Shortening the hold and tightening the pitch keeps the
     bars reading as bars instead of as a haze. One class, no new geometry. */
  .vds-comb--band {
    --vds-comb-hold: 14%;
    --vds-comb-pitch: 7px;
    --vds-comb-bar: 2px;
    block-size: 3rem;
    flex: none;
  }
}


/* ===========================================================================
   DEVICE FRAMES — a phone and a laptop, drawn in CSS
   (appended 2026-07-29, chatbot-API lane)
   ---------------------------------------------------------------------------
   WHY NOT A PNG. A raster mockup is a fixed width, a fixed theme, a network
   request, and a lie the moment the content inside it changes. These are two
   boxes and a border-radius, so they retheme from tokens, scale with their
   container, print, survive JavaScript being off, and cost nothing.

   THE FRAME IS SUBORDINATE. A heavy bezel is decoration and decoration is the
   thing this project keeps rejecting, so the bezel here is a hairline and a
   shadow — just enough to say "this is a screen" and then get out of the way.
   The content inside is at full contrast; the frame is at --border-subtle.

   NOT INTERACTIVE, NOT A LAYOUT SYSTEM. The frame sets a viewport-ish aspect
   and clips; whatever goes inside is ordinary flow content and scrolls in its
   own box if it is too tall. There is no JS and no measurement.
   =========================================================================== */

@layer vds.components {

  .vds-device {
    --vds-device-bezel: var(--vds-space-2);
    --vds-device-radius: var(--vds-radius-lg);
    position: relative;
    display: flex;
    flex-direction: column;
    background-color: var(--vds-surface);
    border: var(--vds-hairline) solid var(--vds-border);
    border-radius: var(--vds-device-radius);
    padding: var(--vds-device-bezel);
    box-shadow: var(--vds-shadow-2);
    min-inline-size: 0;
  }

  /* the glass: the clipped area the UI actually lives in */
  .vds-device__screen {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    overflow: hidden;
    border-radius: calc(var(--vds-device-radius) - var(--vds-device-bezel));
    background-color: var(--vds-surface);
    border: var(--vds-hairline) solid var(--vds-border-subtle);
  }

  /* --- phone ------------------------------------------------------------- */
  .vds-device--phone {
    --vds-device-radius: 2rem;
    --vds-device-bezel: 0.5rem;
    inline-size: min(20rem, 100%);
    margin-inline: auto;
  }
  /* The pill notch. `::before` on the FRAME, not the screen — the screen is
     `overflow: hidden` and owns its own stacking, and a notch that scrolls
     with the conversation is worse than no notch. */
  .vds-device--phone::before {
    content: "";
    position: absolute;
    inset-block-start: calc(var(--vds-device-bezel) + 0.5rem);
    inset-inline-start: 50%;
    translate: -50% 0;
    inline-size: 4.5rem;
    block-size: 0.3125rem;
    border-radius: var(--vds-radius-pill);
    background-color: var(--vds-border);
    z-index: 1;
    pointer-events: none;
  }
  .vds-device--phone .vds-device__screen { padding-block-start: 1.25rem; }

  /* --- laptop ------------------------------------------------------------
     The lid is the frame; the base is a separate element so the taper can be
     a clip-path on a box that is not clipping the screen. */
  .vds-device--laptop {
    --vds-device-radius: var(--vds-radius-md);
    --vds-device-bezel: 0.5rem;
    inline-size: 100%;
  }
  /* MEASURED: the first version made the base 116% wide with -8% inline
     margins, which is what a laptop actually looks like and which overflowed
     its grid track by 58px at 1440 — the container reported 15px of horizontal
     scroll it had no way to absorb. A base that is flush with the lid still
     reads as a laptop, because the taper and the fill are doing that work, not
     the overhang. Flush is therefore the honest geometry here. */
  .vds-device__base {
    inline-size: 100%;
    block-size: 0.75rem;
    margin-block-start: 0.25rem;
    border-radius: 0 0 var(--vds-radius-md) var(--vds-radius-md);
    background-color: var(--vds-surface-well);
    border: var(--vds-hairline) solid var(--vds-border-subtle);
    border-block-start: 0;
    /* the wrist-rest taper, without an image */
    clip-path: polygon(4% 0, 96% 0, 100% 100%, 0 100%);
  }

  /* --- the browser/app chrome strip -------------------------------------
     Three dots and a label. It exists so the laptop screen reads as a browser
     rather than as a plain card, and it is the only ornament in the frame. */
  .vds-device__bar {
    display: flex;
    align-items: center;
    gap: var(--vds-space-2);
    padding: var(--vds-space-2) var(--vds-space-3);
    border-block-end: var(--vds-hairline) solid var(--vds-border-subtle);
    background-color: var(--vds-surface-sunken);
    font-family: var(--vds-font-mono);
    font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm);
    color: var(--vds-text-secondary);
    flex: none;
  }
  .vds-device__dots { display: flex; gap: 0.25rem; flex: none; }
  .vds-device__dots i {
    inline-size: 0.5rem; block-size: 0.5rem;
    border-radius: 50%;
    background-color: var(--vds-border);
    display: block;
  }
  .vds-device__url {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-inline-size: 0;
  }
}


/* ===========================================================================
   SEGMENTED — a real radio group that looks like a switch
   (appended 2026-07-29, chatbot-API lane)
   ---------------------------------------------------------------------------
   THE RULE THIS EXISTS TO ENFORCE: a toggle on a content page must be a FORM
   CONTROL. Not a div with a click handler, not a set of buttons with
   aria-pressed guessed at. Native `<input type="radio">` inside a `<fieldset>`
   gives arrow-key navigation, a single tab stop for the whole group, the
   correct role and group label, and it works before any script loads.

   The input is NOT display:none — that removes it from the accessibility tree
   and from the tab order on some engines. It is a zero-size clipped element
   (the standard visually-hidden recipe) that still receives focus, and the
   focus ring is drawn on the LABEL via :has(). Every engine this system
   targets supports :has(); where it does not, the ring falls back to the
   input's own outline, which is 1px in the wrong place but never absent.

   The panels this switches between must use `hidden` + `inert`, never
   `display:none` in a stylesheet — see the page that consumes this. `hidden`
   is a property of the CONTENT, so assistive tech and find-in-page agree with
   the pointer about what is on screen; a CSS-only hide leaves the node
   focusable and find-in-page can scroll to text nobody can see.
   =========================================================================== */

@layer vds.components {

  .vds-seg {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0;
    padding: 0.25rem;
    margin: 0;
    border: var(--vds-hairline) solid var(--vds-border);
    border-radius: var(--vds-radius-pill);
    background-color: var(--vds-surface-sunken);
    max-inline-size: 100%;
  }
  .vds-seg > legend { /* the group name sits outside the switch */
    float: left;
    inline-size: 100%;
    padding: 0;
    margin: 0 0 var(--vds-space-3);
    font-family: var(--vds-font-mono);
    font-size: var(--vds-text-micro);
    letter-spacing: var(--vds-tracking-caps-sm);
    text-transform: uppercase;
    color: var(--vds-text-secondary);
  }
  .vds-seg__opt { position: relative; display: flex; min-inline-size: 0; }
  .vds-seg__opt input {
    /* visually hidden, NOT removed: still focusable, still in the a11y tree */
    position: absolute;
    inline-size: 1px; block-size: 1px;
    padding: 0; margin: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .vds-seg__opt label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--vds-space-2);
    padding: var(--vds-space-2) var(--vds-space-5);
    min-height: var(--vds-tap-comfortable);
    border-radius: var(--vds-radius-pill);
    font-family: var(--vds-font-mono);
    font-weight: var(--vds-weight-regular);
    font-size: var(--vds-text-xs);
    letter-spacing: var(--vds-tracking-caps-sm);
    text-transform: uppercase;
    color: var(--vds-text-secondary);
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
    transition: background-color var(--vds-duration-1) linear,
                color var(--vds-duration-1) linear,
                box-shadow var(--vds-duration-1) linear;
  }
  .vds-seg__opt:hover label { color: var(--vds-text-primary); }
  .vds-seg__opt:has(input:checked) label {
    background-color: var(--vds-surface);
    color: var(--vds-text-primary);
    box-shadow: var(--vds-shadow-1);
  }
  .vds-seg__opt:has(input:focus-visible) label {
    outline: var(--vds-focus-width) solid var(--vds-accent-primary);
    outline-offset: var(--vds-focus-offset);
  }
  /* fallback for engines without :has() — the ring lands on the input box */
  @supports not selector(:has(*)) {
    .vds-seg__opt input:checked + label {
      background-color: var(--vds-surface);
      color: var(--vds-text-primary);
      box-shadow: var(--vds-shadow-1);
    }
    .vds-seg__opt input:focus-visible + label {
      outline: var(--vds-focus-width) solid var(--vds-accent-primary);
      outline-offset: var(--vds-focus-offset);
    }
  }
}

/* -----------------------------------------------------------------------
   UNLAYERED OVERRIDE — restores .vds-seg's own track padding.
   `.vds-seg` is a <fieldset>. This page loads Tailwind's Preflight reset
   (css/tailwind.min.css) UNLAYERED in <head>; unlayered CSS always wins over
   @layer'd CSS regardless of selector specificity, so Tailwind's
   `fieldset,legend{padding:0}` strips the `padding: 0.25rem` declared on
   `.vds-seg` above, even though that rule is more specific. Same "unlayered
   beats layered" bug class as the `.vds-pill` padding override earlier in
   this file. Left unfixed, the track has zero interior padding, so the
   active option's 999px-radius stadium shape has no room to sit inside it
   and visibly overflows the track's rounded top/bottom/left edges.
   ----------------------------------------------------------------------- */
.vds-seg {
  padding: 0.25rem;
}
