/* ============================================================================
   Expanding Ranks, system token layer
   ----------------------------------------------------------------------------
   Load SECOND, immediately after 01-brand-tokens.css, on every page.

   01-brand-tokens.css owns color ramps, the semantic color aliases and the
   three font families. It is generated from docs/DESIGN-TOKENS.css and is not
   edited here. This file owns everything a brand palette does not cover: the
   surface roles, the type bundles, space, radius, elevation, layering,
   breakpoints, motion, and the geometry of the dot motif.

   One owner per property. If a value appears in a component file that could
   have come from here, that is a finding, and scripts/check-design-system.py
   is written to fail on it.

   Contrast ratios in the comments are computed by scripts/contrast.py, which
   reads these declarations rather than holding a second copy of them. A ratio
   written here that the script does not check is a gap in the script, not a
   licence to trust the comment.
   ========================================================================== */

:root {
  /* ==========================================================================
     1. SURFACES AND THE ROLES THAT SIT ON THEM

     Four surfaces, and every text and boundary role is measured against all
     four. A role that cannot clear its requirement on one of them is rebound
     inside that surface's scope class in section 1.1. Nothing is left to
     inherit a value that was only ever checked on white.
     ========================================================================== */

  --color-plate:             var(--color-oxford-navy-950); /* The dark plate. #021c36 */
  --color-plate-raised:      var(--color-oxford-navy-900); /* A panel on the plate */

  /* Text on the dark plate. Bound here and applied by .er-on-dark below. */
  --color-on-dark:           #ffffff;                       /* 17.19:1 on plate */
  --color-on-dark-body:      var(--color-light-cyan-200);   /* 13.95:1 on plate */
  --color-on-dark-muted:     var(--color-slate-grey-400);   /*  6.99:1 on plate */
  --color-on-dark-link:      var(--color-twitter-blue-400); /*  7.53:1 on plate */
  --color-on-dark-signal:    var(--color-atomic-tangerine-400); /* 7.16:1 on plate */
  --color-focus-ring-on-dark:var(--color-light-cyan-500);   /*  7.28:1 on plate. Needs 3:1 */
  --color-border-control-on-dark: var(--color-slate-grey-500); /* 4.28:1 on plate. Needs 3:1 */

  /* Signal, split by job. The single token --color-signal is a FILL and its
     ratio on white is 2.77:1, which is why it has never been allowed to carry
     type or to be the only mark distinguishing a state. These three are the
     roles that do carry meaning, and each is measured for the job it does. */
  --color-signal-mark:       var(--color-atomic-tangerine-600); /* 4.31:1 white, 4.03:1 sunken. A dot,
                                                                   an icon, a boundary. Needs 3:1 */
  --color-signal-text:       var(--color-atomic-tangerine-700); /* 6.28:1 white, 5.87:1 sunken, 5.32:1
                                                                   on signal-surface. Needs 4.5:1 */
  --color-signal-border:     var(--color-atomic-tangerine-600); /* 3.65:1 on signal-surface. Needs 3:1 */
  --color-on-signal:         var(--color-oxford-navy-950);      /* 6.21:1 on the signal fill */

  /* Confirmation. There is no green in this palette and one is not being
     added. Light Cyan carries confirmation, which is already what the coming
     soon page does. Because orange against cyan is a weaker pairing than the
     red/green a reader expects, NOTHING in this system signals state by color
     alone. Every one carries an icon or a word as well. */
  --color-confirm-text:      var(--color-light-cyan-800);   /* 7.38:1 white, 6.90:1 sunken */
  --color-confirm-mark:      var(--color-light-cyan-700);   /* 4.78:1 white, 4.47:1 sunken. Needs 3:1 */
  --color-confirm-surface:   var(--color-light-cyan-100);
  --color-on-dark-confirm:   var(--color-light-cyan-400);   /* 9.10:1 on plate */

  /* There is deliberately no warning tier. Atomic Tangerine is the only color
     in this system that means attention, and a second attention level would
     spend the thing section 8.2 of DESIGN.md reserves it for. Something either
     needs a human or it does not. */

  /* Dots carry data, so the unfilled ones are load bearing: they are the
     denominator. Both states clear 3:1, and they also differ in SIZE, so the
     rank reads correctly in grayscale and to a color-blind reader. */
  --color-dot-filled:        var(--color-oxford-navy-900);  /* 12.61:1 white, 11.78:1 sunken */
  --color-dot-empty:         var(--color-slate-grey-500);   /*  4.02:1 white,  3.75:1 sunken */
  --color-dot-filled-on-dark:var(--color-light-cyan-200);   /* 13.95:1 on plate */
  --color-dot-empty-on-dark: var(--color-slate-grey-400);   /*  6.99:1 on plate */

  --color-divider:           var(--color-slate-grey-200);   /* Decorative. No requirement */
  --color-selected-surface:  var(--color-light-cyan-100);
  --color-hover-surface:     var(--color-slate-grey-50);
  --color-scrim:             rgba(2, 28, 54, 0.62);         /* navy-950 at 62 percent */

  /* Component surfaces. These exist so no component file ever names a raw ramp
     step. A component reaching for --color-slate-grey-100 directly has made a
     decision that belongs here, and it is invisible to every check that looks
     at roles. A grep for the raw token name finds these in seconds and nothing
     else will, which is why check-design-system.py greps for exactly that. */
  --color-surface-disabled:   var(--color-slate-grey-100);  /* Disabled control fill */
  --color-surface-brand-tint: var(--color-oxford-navy-100); /* Avatar disc, current nav item.
                                                               --color-brand on it reads 10.65:1 */
  --color-track:              var(--color-slate-grey-200);  /* Meter track, count pill */
  --color-switch-off:         var(--color-slate-grey-500);  /* Switch track, off. The white knob on
                                                               it reads 4.02:1, which is what makes
                                                               the position readable. A paler track
                                                               puts the knob under 2:1 and the state
                                                               becomes a guess */
  --color-signal-hover:       var(--color-atomic-tangerine-400); /* Signal fill, hover. --color-on-signal
                                                                    on it reads 7.16:1 */
  --color-skeleton:           var(--color-slate-grey-100);
  --color-skeleton-sheen:     rgba(255, 255, 255, 0.72);

  /* ==========================================================================
     2. TYPE

     Sizes and leadings are declared as inseparable pairs and are applied
     through the .er-t-* bundles in 04-components.css. Setting font-size on its
     own is the documented way a type system comes apart, so the check script
     fails a component rule that sets one without the other.

     Body is 16px. 14px and below is permitted ONLY for tabular data, table
     column heads, badges and metadata, and the exemption is recorded here
     rather than at each call site.
     ========================================================================== */

  --text-3xs:      0.6875rem;  /* 11px. Uppercase micro-labels with tracking. Never prose */
  --lead-3xs:      1.2727;     /* 14px */
  --text-2xs:      0.75rem;    /* 12px. Badges, table column heads */
  --lead-2xs:      1.3333;     /* 16px */
  --text-xs:       0.8125rem;  /* 13px. Dense metadata */
  --lead-xs:       1.3846;     /* 18px */
  --text-sm:       0.875rem;   /* 14px. Table cells, secondary interface text */
  --lead-sm:       1.4286;     /* 20px */
  --text-base:     1rem;       /* 16px. Body, inputs, buttons. Inputs never go below this:
                                  Safari on iOS zooms the viewport on focus under 16px */
  --lead-base:     1.5;        /* 24px */
  --text-lg:       1.125rem;   /* 18px. Lede */
  --lead-lg:       1.5556;     /* 28px */
  --text-xl:       1.375rem;   /* 22px. h3, card titles */
  --lead-xl:       1.2727;     /* 28px */
  --text-2xl:      1.75rem;    /* 28px. h2 */
  --lead-2xl:      1.2143;     /* 34px */
  --text-3xl:      2.25rem;    /* 36px. h1 */
  --lead-3xl:      1.1667;     /* 42px */
  --text-4xl:      3rem;       /* 48px. A single display metric. One per screen */
  --lead-4xl:      1.0833;     /* 52px */

  /* Weight and width, as variation settings. Archivo carries wght and wdth;
     DM Sans carries wght and opsz. Never synthesise either: a font-weight the
     file cannot reach gets an algorithmic smear, and both of these families
     reach every weight this system asks for. */
  --wght-regular:  400;
  --wght-medium:   500;
  --wght-semibold: 600;
  --wght-bold:     700;
  --wdth-display:  105;   /* Archivo headings */
  --wdth-nav:       98;   /* Archivo navigation, slightly narrowed */

  --track-tight:  -0.01em;  /* Display sizes only */
  --track-normal:  0;
  --track-label:   0.06em;  /* Uppercase micro-labels */

  --measure:      68ch;   /* Prose column cap. NOTE: ch is the advance of the
                             zero glyph, so this cap moves if the body face is
                             ever swapped. Re-measure it on any swap */
  --measure-tight: 52ch;  /* Empty state and modal copy */

  /* ==========================================================================
     3. SPACE

     4px base, 8px rhythm. Thirteen values. Nothing in a component sets a
     spacing value that is not one of these.
     ========================================================================== */

  --space-1:   4px;
  --space-2:   8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;
  --space-32: 128px;

  /* ==========================================================================
     4. LAYOUT

     Breakpoints are declared here ONCE and every media query in this system
     uses one of these five literals. CSS custom properties cannot be used
     inside a media query, so the values are necessarily repeated in the
     component files. That is exactly the shape of drift the runbook warns
     about, so check-design-system.py greps every min-width and max-width in
     site/css and fails on any value not in this list. The check is the reason
     the repetition is safe.

       560px   single column
       768px   tablet stack
      1024px   navigation collapses to a drawer
      1280px   desktop
      1440px   widest tested viewport

     Design mobile first. Test at 375px and 1440px.
     ========================================================================== */

  --container:      1280px;  /* Application shell */
  --container-wide: 1440px;
  --container-prose: 736px;  /* Documentation and long copy */
  --gutter:         var(--space-5);   /* 20px under 768 */
  --gutter-lg:      var(--space-8);   /* 32px at 768 and up */

  /* The application grid. Twelve columns, because the ten stage columns of a
     pipeline board plus a rail divide into it cleanly. */
  --grid-columns: 12;
  --grid-gap:     var(--space-6);

  /* ==========================================================================
     5. RADIUS AND ELEVATION

     Four radii. A fifth value appearing anywhere is a finding.
     Shadows are navy tinted rather than black, because every surface in this
     product sits on a navy-derived neutral and a black shadow on it reads grey
     and dirty.
     ========================================================================== */

  --radius-sm:   6px;    /* Badges, checkboxes, small controls */
  --radius:     10px;    /* Buttons, inputs, cards */
  --radius-lg:  16px;    /* Modals, large panels */
  --radius-pill: 999px;  /* Tags, counts, pills */

  --shadow-sm:    0 1px 2px rgba(2, 28, 54, 0.06),
                  0 1px 1px rgba(2, 28, 54, 0.04);
  --shadow:       0 2px 4px rgba(2, 28, 54, 0.06),
                  0 4px 12px rgba(2, 28, 54, 0.08);
  --shadow-lg:    0 4px 8px rgba(2, 28, 54, 0.07),
                  0 12px 28px rgba(2, 28, 54, 0.12);
  --shadow-modal: 0 16px 48px rgba(2, 28, 54, 0.24);

  /* ==========================================================================
     6. LAYERING

     Every positioned element in this system reads one of these. A raw z-index
     integer in a component file is a finding; the check script greps for it.
     Gaps are deliberate, so a new layer can be inserted without renumbering.
     ========================================================================== */

  --z-behind:     -1;   /* Decorative texture painted behind its own content.
                           The only negative layer, and it is scoped by an
                           isolation: isolate on the parent so it can never
                           escape behind the page itself */
  --z-base:        0;
  --z-raised:     10;   /* A card lifting on hover */
  --z-sticky-cell:20;   /* A frozen first column in a table */
  --z-sticky:    100;   /* Sticky table head, sticky page header */
  --z-dropdown:  200;   /* Select menu, popover, tooltip */
  --z-scrim:     300;
  --z-modal:     310;
  --z-toast:     400;
  --z-skiplink:  900;   /* Above everything. It is the escape hatch */

  /* ==========================================================================
     7. MOTION

     Two families, and they are not interchangeable.

     PRODUCT motion is everything inside the application. It is short, and the
     ceiling is 300ms. A recruiter works this screen all day and an animation
     that is pleasant once is an obstruction on the four hundredth repetition.

     IDENTITY motion is the mark building itself. It is slower on purpose, it
     runs once per page load, and it is confined to the identity mark and the
     full-page loader. The numbers come from the coming soon page, which is
     where this vocabulary was set: 620ms per dot, rows building bottom to top
     at 300ms apart, and dots within a row landing outward from the centre at
     45ms apart.

     Easing: three curves, three jobs, no overlap. Which curve you use is
     decided by WHAT IS CHANGING, not by what feels right.
     ========================================================================== */

  --ease:        cubic-bezier(0.2, 0, 0.2, 1);      /* color, opacity, border, shadow.
                                                       Anything with no position */
  --ease-out:    cubic-bezier(0.22, 1, 0.36, 1);    /* ANYTHING THAT MOVES. transform,
                                                       entrances, a dot landing, a card lifting.
                                                       This is the curve the coming soon page uses */
  --ease-reveal: cubic-bezier(0.16, 0.84, 0.44, 1); /* scroll reveal, and nothing else */

  /* There is deliberately no ease-in token. An ease-in withholds movement
     during exactly the frames a user is watching hardest. If an exit ever
     genuinely needs one, add --ease-exit with a written reason rather than
     reaching for the nearest available name. */

  --dur-press:   100ms;  /* Down stroke of a press. Asymmetric on purpose */
  --dur-fast:    120ms;  /* Color and opacity */
  --dur:         180ms;  /* Everything else */
  --dur-slow:    260ms;  /* Entrances, menus, disclosure */
  --dur-reveal:  300ms;  /* Scroll reveal. The product ceiling */

  --dur-dot:     620ms;  /* IDENTITY ONLY. One dot of the mark building */
  --dur-rise:    700ms;  /* IDENTITY ONLY. The wordmark and hero copy rising */

  /* The three permitted loops, and there is no fourth of either kind. Two are
     functional: the busy spinner and the skeleton shimmer. Both mean the
     system is working right now, both stop the moment the work does, and both
     are neutralised under reduced motion. One is the indeterminate loader,
     which is the mark breathing while something is being read.

     These are exempt from the 300ms product ceiling because a loop's duration
     is a rate, not a delay before something becomes usable. --ease-linear
     exists for exactly one of them: a spinner that accelerates reads as
     stuttering, and it is the only place a linear curve is correct. */
  --dur-spin:    720ms;
  --dur-shimmer: 1400ms;
  --dur-pulse:   1800ms;
  --ease-linear: linear;
  --stagger-pulse-row: 120ms;
  --stagger-pulse-dot:  50ms;

  --stagger-lateral:  45ms;   /* Between dots, outward from the centre of a row */
  --stagger-row:     300ms;   /* Between rows of the identity mark, bottom to top */
  --stagger-rank:     90ms;   /* Between dots of a rank meter filling */
  --stagger-reveal:   60ms;   /* Between rows of a revealing grid */
  --stagger-cap:         4;   /* Rows, not siblings. Read by er-motion.js */

  --reveal-distance:  10px;   /* Upward. Never more than 24, never sideways */
  --reveal-trigger:   -12%;   /* Root margin. Fires when the top edge crosses 88vh */

  --press-scale:     0.97;    /* Every pressable element, while held */

  /* ==========================================================================
     8. THE DOT

     The motif's geometry, in one place, because a dot that is 7px in one
     component and 8px in another is the whole system coming apart in the one
     place anybody would notice.

     Row counts come from the mark: 4, 5, 6, 7 building downward, 22 in total.
     ========================================================================== */

  --dot-xs:      4px;
  --dot-sm:      6px;
  --dot:         8px;
  --dot-lg:     12px;
  --dot-xl:     16px;

  --dot-empty-scale: 0.6;   /* An unfilled dot is 60 percent of a filled one.
                               This is the redundant, non-color channel that
                               carries filled versus empty */
  --dot-gap-xs:  4px;
  --dot-gap:     6px;
  --dot-gap-lg: 10px;

  --dot-rise:   6px;        /* How far a dot travels as it lands */
  --dot-overshoot: 1.14;    /* Peak scale before it settles. From the mark */

  --texture-cell-x: 38px;   /* Background dot grid. From the coming soon page */
  --texture-cell-y: 33px;
  --texture-dot:   1.1px;
}

/* ============================================================================
   1.1 SCOPE REBINDING

   Put .er-on-dark on any element whose subtree sits on the dark plate, and
   .er-on-info or .er-on-signal on a tinted panel. These rebind EVERY role that
   changes, derived from the measurement rather than from the ones anybody
   happened to think of.

   The failure this prevents is specific and it has shipped before elsewhere:
   a scope that rebinds body and heading and forgets the error and confirmation
   roles, so a validation message renders at 2:1 on a dark panel and nothing
   reports it. Every role in the list below is checked by scripts/contrast.py.
   ========================================================================== */

.er-on-dark {
  --color-surface:        var(--color-plate);
  --color-surface-sunken: var(--color-plate);
  --color-hover-surface:  rgba(205, 237, 246, 0.07);
  --color-text:           var(--color-on-dark);
  --color-text-muted:     var(--color-on-dark-muted);
  --color-text-disabled:  var(--color-slate-grey-500);
  --color-interactive:      var(--color-on-dark-link);
  --color-interactive-hover:var(--color-light-cyan-200);
  --color-focus-ring:     var(--color-focus-ring-on-dark);
  --color-border:         rgba(205, 237, 246, 0.18);
  --color-border-control: var(--color-border-control-on-dark);
  --color-divider:        rgba(205, 237, 246, 0.18);
  --color-signal-text:    var(--color-on-dark-signal);
  --color-signal-mark:    var(--color-atomic-tangerine-500);
  --color-signal-surface: rgba(239, 123, 69, 0.14);
  --color-signal-border:  var(--color-atomic-tangerine-500);
  --color-confirm-text:   var(--color-on-dark-confirm);
  --color-confirm-mark:   var(--color-on-dark-confirm);
  --color-confirm-surface:rgba(109, 201, 226, 0.14);
  --color-info-surface:   rgba(205, 237, 246, 0.10);
  --color-dot-filled:     var(--color-dot-filled-on-dark);
  --color-dot-empty:      var(--color-dot-empty-on-dark);
  --color-brand:          var(--color-on-dark);
  /* These next two were missing on the first pass and the contrast script
     caught both, which is the entire reason it resolves scopes rather than
     :root alone. --color-brand-hover left at navy-800 measured 1.80:1 against
     the plate, and --color-selected-surface left at its light cyan tint put
     white type on a pale row at 1.14:1. Neither is a colour anybody would have
     thought to list. */
  --color-brand-hover:    var(--color-light-cyan-200);   /* 13.95:1 on plate */
  --color-selected-surface: rgba(205, 237, 246, 0.14);   /* white type over it reads 14.03:1 */
  --color-surface-disabled: rgba(205, 237, 246, 0.08);
  --color-surface-brand-tint: rgba(205, 237, 246, 0.14);
  --color-track:            rgba(205, 237, 246, 0.18);
  --color-switch-off:       var(--color-slate-grey-500);  /* 4.28:1 on plate, white knob 4.02:1 on it */
  --color-skeleton:         rgba(205, 237, 246, 0.10);
  --color-skeleton-sheen:   rgba(205, 237, 246, 0.14);
  color: var(--color-text);
  background: var(--color-surface);
}

/* Body copy on the plate is cyan rather than pure white. Headings stay white.
   Set on the container so a paragraph does not have to opt in. */
.er-on-dark p,
.er-on-dark li,
.er-on-dark td { color: var(--color-on-dark-body); }

/* A tinted panel. --color-text-muted at slate-600 measures 4.26:1 on the
   signal tint and 4.09:1 on the info tint, both under AA, so muted text is
   rebound one step darker inside them. This is the pair a hand-written list
   does not contain. */
.er-on-info,
.er-on-signal {
  --color-text-muted: var(--color-slate-grey-700);  /* 5.75:1 on info, 6.00:1 on signal */
  --color-hover-surface: rgba(2, 28, 54, 0.04);
}
.er-on-info   { background: var(--color-info-surface);   color: var(--color-text); }
.er-on-signal { background: var(--color-signal-surface); color: var(--color-text); }

/* ============================================================================
   REDUCED MOTION, mechanism 1 of 3

   Durations AND delays. Collapsing durations alone leaves the stagger chain
   intact, so a motion-sensitive visitor gets no animation and still gets a
   staccato flicker of elements appearing one at a time over most of a second.
   That is worse than the animation it replaced.

   1ms rather than 0.01ms: sub-tick durations round to zero in some engines and
   cancel the animationend event, so anything waiting on one waits forever.
   animation-delay of -1ms starts an animation already complete rather than
   pending.

   Mechanism 2 is in 03-motif.css and forces the end state on every element
   that starts hidden. Mechanism 3 is in er-motion.js, which skips the observer
   entirely. All three are required. Mechanism 1 alone leaves elements stuck at
   opacity 0.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: -1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}
