/* SHELEG Design — Patchbay token layer.
   Copy verbatim; consume only var(--…) in components. */
:root {
  /* ---- Field and ink ------------------------------------------------------
     MEASURED 2026-08-22 off https://nautilustrader.io/ with getComputedStyle on
     the running page. The reference declares NO custom properties at all — zero
     on `:root` — because it is Material UI with Emotion, so every value here was
     read off painted elements rather than lifted from a stylesheet. Two
     consequences the next author should know: there is no author-side token
     vocabulary to inherit, and the MUI theme's own defaults leak through in
     places the design plainly did not intend (see `## Gotchas`).

     One field, two steps down, and no light twin anywhere. */
  --bg: #1b1b1d; /* MEASURED — the body field */
  --surface: #171718; /* MEASURED — one step below the page */
  --surface-deep: #121214; /* MEASURED — the deepest band */

  /* The card is not a fill, it is a LIGHT. Both stops measured off the feature
     card: a teal-black at the top edge falling to near-black at the bottom, so
     the card reads as lit from above rather than raised. See `## Texture`. */
  --card-lit: #0f2026; /* MEASURED — the radial's centre, at 50% 0 */
  --card-base: #16181a; /* MEASURED — the radial's outer stop */

  --ink: #eeeeee; /* 14.82:1 on --bg — MEASURED, every heading */
  --ink-strong: #ffffff; /* 17.20:1 on --bg — MEASURED, body copy in cards */
  --ink-soft: #848895; /* 4.86:1 on --bg — MEASURED, the muted tier */
  /* [CORRECTION] The reference paints a SECOND muted grey, #686B75, in 13 text
     nodes — 3.23:1 on the field, under the 4.5 text floor and under 3 as well,
     so it is a text colour that fails as text. It is not corrected to a new
     value because there is no room for one: --ink-soft already sits at 4.86 and
     any legible "fainter" step lands on top of it. The two greys are ONE tier
     and this pack ships one. Where the reference set a word in #686B75, use
     --ink-soft; where it set a mark, use --hairline-strong. */

  /* ---- The hairline ladder, which is the whole elevation model -------------
     No shadow appears on any card in the reference. Separation is a 1px line and
     nothing else, so the ladder IS the elevation system rather than a detail.
     @role non-text: --hairline, --hairline-strong, --grid-line, --wash — four
     rules and a fill, never a text colour. They separate by position, and the
     thing they separate carries the contrast. */
  --hairline: rgba(255, 255, 255, 0.08); /* 1.25:1 on --bg — MEASURED, THE line */
  --hairline-strong: rgba(255, 255, 255, 0.1); /* MEASURED — 10% white, the emphatic rule */
  --grid-line: rgba(255, 255, 255, 0.02); /* MEASURED — 2% white on a 40px cell */
  --wash: rgba(255, 255, 255, 0.02); /* 1.06:1 on --bg — MEASURED, the quiet fill */

  /* ---- The accent is ONE teal, and it is text-safe ------------------------
     #00CFBE is the exact value carried by the diagram's endpoint circles
     (`fill="#00CFBE"` in the SVG) and by 32 painted text nodes. It may carry a
     word on this field — which is what separates this pack from a brand hue that
     may only mark. What it may NOT do is take white on top; a FILLED accent
     control takes --on-accent, and the pack's Palette states what white costs. */
  --accent: #00cfbe; /* 8.74:1 on --bg — MEASURED */
  --on-accent: #05100f; /* 9.81:1 on --accent — text ON a filled accent */

  /* The second hue exists in exactly one place: the primary button's gradient
     runs teal to this blue. It is not a second accent and nothing else uses it. */
  --accent-far: #2fadd7; /* 6.62:1 on --bg — MEASURED, the CTA gradient's far stop */

  /* The accent's alpha ladder. Every one of these is a WASH or a LINE — the
     accent at full strength is the only form that carries a word.
     @role non-text: --accent-wash, --accent-edge, --accent-edge-strong,
     --accent-rim. --accent-rim is the primary button's 1.5px border and is the
     only one of the four that has a floor to clear; the others are fills. */
  --accent-wash: rgba(0, 207, 190, 0.06); /* MEASURED — the accent at 6%, the chip fill */
  --accent-edge: rgba(0, 207, 190, 0.14); /* MEASURED — the accent at 14%, the card border */
  --accent-edge-strong: rgba(0, 207, 190, 0.25); /* MEASURED — the accent at 25%, the chip border */
  --accent-rim: rgba(0, 207, 190, 0.5); /* 3.10:1 on --bg — MEASURED, the button border */

  /* ---- Status is a PACK DECISION, not a measurement -----------------------
     The reference is a marketing page for an open-source engine and paints no
     success, warning or error state anywhere — there was nothing to measure.
     These three are derived, against a constraint this pack has that most do
     not: the accent is a MINT-CYAN, so the ordinary dark-UI green collides with
     it under dichromacy. The set shipped by `instrument-console` was tried first
     and rejected here for exactly that — the numbers are in the pack's Palette.
     Every candidate that clears the separation floor against this accent is
     LIGHT, which is why --ok sits above --warn and --danger in luminance rather
     than beside them. */
  --ok: #86f687; /* 12.73:1 on --bg — DERIVED */
  --warn: #eba002; /* 7.83:1 on --bg — DERIVED */
  --danger: #e36364; /* 5.11:1 on --bg — DERIVED */
  --info: var(--accent); /* the accent IS the informational hue; a fourth would break the one-accent rule */

  /* ---- Radii: a 2px ladder, not a doubling one ----------------------------
     Counted over the painted page: 10px on 50 elements, 14px on 37, 12px on 19.
     The tightness is the point — a diagram node, a button and a card are three
     different objects two pixels apart, so nothing reads as a different
     material. The pill is the only jump. */
  --r-node: 10px; /* MEASURED — diagram nodes */
  --r-control: 12px; /* MEASURED — buttons */
  --r-card: 14px; /* MEASURED — cards and the dashed group */
  --r-pill: 999px; /* MEASURED as 20px on a 31px-tall chip, i.e. fully round */

  /* ---- Motion -------------------------------------------------------------
     Two clocks, and they never meet. The AMBIENT clock is 8.5–11s ease-in-out on
     four drifting light layers; the SIGNAL clock is 2–3.5s linear on the
     particles travelling the diagram's cords. Nothing in this pack animates on a
     duration between 3.5s and 8.5s, and the gap is deliberate: a viewer reads
     the fast band as data and the slow band as light. */
  --dur-signal-fast: 2s; /* MEASURED — the shortest cord */
  --dur-signal: 2.5s; /* MEASURED — 23 of the 32 particles */
  --dur-signal-slow: 3.5s; /* MEASURED — the longest cord */
  --dur-ambient: 11s; /* MEASURED — the hero light layer */
  --dur-control: 0.3s; /* MEASURED — buttons and cards */
  --dur-chip: 0.2s; /* MEASURED — chips */
  --ease-ambient: ease-in-out; /* MEASURED */
  --ease-signal: linear; /* MEASURED — animateMotion carries no easing attribute */
  --ease-control: ease; /* MEASURED */
  --stagger-cord: 0.1s; /* MEASURED — consecutive cords start 0.1s apart */
}

/* ---- Reduced motion -------------------------------------------------------
   MOTION_DOCTRINE.md §9. Both clocks collapse: the ambient light layers stop
   drifting and the control transitions stop easing. Nothing here is "kept for
   feel" — a perpetual loop has no information in it that a still frame lacks.

   THE ONE THING THIS BLOCK CANNOT DO, stated here because it is a trap and not
   a nicety: the signal particles are SVG `<animateMotion>`, and SMIL does not
   read CSS `animation-duration`. Collapsing --dur-signal* below stops nothing on
   its own. The component layer MUST also call `svg.pauseAnimations()` behind the
   same query — kits/patchbay/src/Diagram.tsx is the worked example. This is the
   defect the reference ships: its single blanket reduced-motion rule cannot
   reach its own diagram, and the dots keep moving. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-signal-fast: 0.01ms;
    --dur-signal: 0.01ms;
    --dur-signal-slow: 0.01ms;
    --dur-ambient: 0.01ms;
    --dur-control: 0.01ms;
    --dur-chip: 0.01ms;
    --ease-ambient: linear;
    --ease-signal: linear;
    --ease-control: linear;
    --stagger-cord: 0s;
  }
}

/* ── components ── */
/* Measured off https://nautilustrader.io/ on 2026-08-22. No colour literal
   appears below this line: every value is a token, so moving a token moves the
   whole kit. */

/* Buttons. `primary` is a WASH and a rim, never a fill — white on the accent is
   1.97:1, and the reference fills nothing. */
.pb-btn {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 0.8rem;
  font-weight: 450;
  letter-spacing: 0.3px;
  border-radius: var(--r-control);
  padding: 9px 19px;
  border: 1px solid var(--hairline);
  background: var(--wash);
  color: var(--ink);
  cursor: pointer;
  transition: border-color var(--dur-control) var(--ease-control),
    box-shadow var(--dur-control) var(--ease-control);
}
.pb-btn--primary {
  border-width: 1.5px;
  border-color: var(--accent-rim);
  background: linear-gradient(135deg, var(--accent-wash) 0%, var(--accent-edge) 100%);
  box-shadow: 0 0 15px var(--accent-edge);
}
.pb-btn--primary:hover { box-shadow: 0 0 15px var(--accent-edge-strong); }
.pb-btn--ghost { background: none; border-color: transparent; color: var(--accent); }
.pb-btn--sm { padding: 6px 14px; font-size: 0.75rem; }
.pb-btn--lg { padding: 12px 24px; font-size: 0.875rem; }
/* A NAMED PAIR, never an opacity multiplier: --ink-soft on --wash is 4.60:1 and
   an opacity value composites against whatever sits behind it. */
.pb-btn:disabled {
  color: var(--ink-soft);
  border-color: var(--hairline);
  background: var(--wash);
  box-shadow: none;
  cursor: not-allowed;
}
.pb-btn:focus-visible {
  outline: 1.5px solid var(--accent-rim);
  outline-offset: 2px;
}

/* The card is a LIGHT: lit from its top edge, never raised. No shadow at any state. */
.pb-card {
  background: radial-gradient(circle at 50% 0, var(--card-lit), var(--card-base));
  border: 1px solid var(--accent-edge);
  border-radius: var(--r-card);
  padding: 24px;
  transition: border-color var(--dur-control) var(--ease-control);
}
.pb-card:hover { border-color: var(--accent-edge-strong); }
.pb-card__head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.pb-card__title { font-size: 1.25rem; font-weight: 600; letter-spacing: -0.2px; color: var(--ink); margin: 0; }
.pb-card__meta { font-family: ui-monospace, 'SF Mono', monospace; font-size: 0.6875rem; letter-spacing: 0.5px; text-transform: uppercase; color: var(--ink-soft); }

/* The version chip: a pill, and the accent may set its word. */
.pb-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: ui-monospace, 'SF Mono', monospace;
  font-size: 0.75rem;
  font-weight: 500;
  border-radius: var(--r-pill);
  padding: 5.6px 13.6px;
  border: 1px solid var(--hairline);
  background: var(--wash);
  color: var(--ink-soft);
  transition: border-color var(--dur-chip) var(--ease-control);
}
.pb-chip--accent { background: var(--accent-wash); border-color: var(--accent-edge-strong); color: var(--accent); }

/* Headings. Tracked negative above 24px and zero below — a PACK DECISION; the
   reference inherits MUI's +0.00938em and tracks its display line the wrong way. */
.pb-h { color: var(--ink); margin: 0; }
.pb-h--1 { font-size: 2.8rem; font-weight: 500; line-height: 1.1; letter-spacing: -0.5px; }
.pb-h--2 { font-size: 2.5rem; font-weight: 650; line-height: 1.2; letter-spacing: -0.3px; }
.pb-h--3 { font-size: 1.25rem; font-weight: 600; line-height: 1.4; letter-spacing: -0.2px; }
/* PAGE — the display step belongs to the page, not to a box. The reference sets
   the hero at 2.8rem below 900px and 3.7rem above with no interpolation between
   them, and a hero is the one element whose size is decided by how much screen
   there is rather than by the width of its container. Nothing else in this kit
   changes at a width at all. */
@media (min-width: 900px) { .pb-h--1 { font-size: 3.7rem; } }

/* The whole elevation model. */
.pb-rule { border: 0; border-top: 1px solid var(--hairline); margin: 0; }
.pb-rule--strong { border-top-color: var(--hairline-strong); }

/* The figure that refuses to render a zero it does not have. */
.pb-stat { display: flex; flex-direction: column; gap: 6px; }
.pb-stat__value { font-size: 2.3rem; font-weight: 700; line-height: 1; letter-spacing: -1.28px; color: var(--ink); }
.pb-stat__label { font-family: ui-monospace, 'SF Mono', monospace; font-size: 0.7rem; letter-spacing: 1px; text-transform: uppercase; color: var(--ink-soft); }
.pb-stat__source { font-size: 0.75rem; color: var(--ink-soft); }
.pb-stat__skeleton {
  display: block;
  height: 2.3rem;
  width: 4ch;
  border-radius: var(--r-node);
  background: var(--wash);
  animation: pb-pulse 2s var(--ease-ambient) 0.5s infinite;
}
@keyframes pb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.pb-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* Status: the DOT repeats what the WORD already said. */
.pb-status { display: inline-flex; align-items: center; gap: 8px; color: var(--ink); font-size: 0.875rem; }
.pb-status__dot { width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--ink-soft); }
.pb-status--ok .pb-status__dot { background: var(--ok); }
.pb-status--warn .pb-status__dot { background: var(--warn); }
.pb-status--danger .pb-status__dot { background: var(--danger); }

/* The signature. The grid is the only texture in the pack. */
.pb-diagram {
  background-image: linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 40px 40px, 40px 40px;
}
.pb-diagram__cord { fill: none; stroke: var(--hairline); stroke-width: 1.5; }
.pb-diagram__cord--replay { stroke-dasharray: 4 4; }
.pb-diagram__head { fill: var(--hairline-strong); }
.pb-diagram__dot { fill: var(--ink-soft); }

/* Degrade to calm — MOTION_DOCTRINE.md §9. The token layer above already
   collapses every duration; this stops the one thing CSS cannot reach on its
   own is handled in Diagram.tsx via pauseAnimations(). What is left here is the
   component-layer promise the gate checks. */
@media (prefers-reduced-motion: reduce) {
  .pb-stat__skeleton { animation: none; }
  .pb-btn, .pb-card, .pb-chip { transition: none; }
}
