/*
 * Stalewatch — the dashboard's own components
 * ===========================================
 * Design §06.1: the status strip, the table head with its sort control, the sparkline column, the
 * snapshot line and the subscribe block. Numbered 45 so it lands after the primitives it composes
 * (40) and before the charts (50). See the numbering note in 00-tokens.css.
 */

/* --------------------------------------------------------------- Next check -------- */
/*
 * ── THE STATUS STRIP WAS HERE ───────────────────────────────────────────────────────────────
 *
 * Four cells across the top of the dashboard: Base sequencer, Paused tickers, Widest divergence,
 * Ingestion. Three of the four restated something already on the page — the widest row is the first
 * row once the table sorts by divergence, the paused count is the paused state block, and the
 * ingestion word is the degraded state block, which is louder and links its evidence. The fourth,
 * the sequencer, is a chain-wide condition rather than a measurement of a feed, and it lives on
 * /status.
 *
 * This is what survived, and it is the smallest of the four: the countdown to our next check. It
 * now sits on the snapshot line under the table, and in the anchor cell on /:ticker.
 *
 * WHAT IT IS FOR. Between two chain reads the page has nothing new to say, and for fifteen seconds
 * at a stretch that is indistinguishable from being broken.
 *
 * tabular-nums and a min-width, so 15s -> 9s does not reflow the line. A countdown that nudged the
 * layout every ten seconds would be worse than no countdown, and it is the kind of jitter that only
 * shows up once it is in front of somebody.
 */


/* --------------------------------------------------------------- Table head -------- */
/*
 * A title and its denominator, and nothing else. The sort control that used to sit on the right of
 * this row moved into the table's own header cells (see .th-sort below), which is where every table
 * anybody has ever sorted puts it.
 */
/*
 * ── THE GRID DOES NOT GET THE FULL SECTION RHYTHM ────────────────────────────────────────────────
 *
 * .section is padding-block: var(--s6) — 56px, which is the right measure between two blocks of
 * argument on a marketing page and too much here in both directions. Above, the nav is a hard dark
 * edge and 56px under it reads as an empty band rather than as separation, pushing the first row of
 * the table below the fold on a laptop. Below, the snapshot line already carries its own top rule
 * and 20px of margin, so the section padding was a second gap stacked on a boundary that had one.
 *
 * A modifier rather than retuning --s6, which every other page is built on.
 */
.section--grid { padding-block: var(--s5) var(--s4); }

.table-head { margin-bottom: var(--s4); }
.table-head__title { font-size: 34px; font-weight: 600; letter-spacing: -0.02em; margin: 0; }

/* ---------------------------------------------------------- Sortable headers -------- */
/*
 * THE ARROW IS DRAWN ON EVERY SORTABLE COLUMN, not only the active one, because a sortable header
 * that looks identical to a fixed one is a control nobody finds. The inactive glyph is a faint
 * double arrow at the lightest ink that is still legible; the active one points the way the column
 * is ordered and carries the accent.
 *
 * `white-space: nowrap` so "Depth ±1%" and its arrow never break across two lines, which at this
 * size reads as a stray character under the header.
 */
/*
 * Vertical padding off, horizontal padding KEPT, and qualified so it out-specifies the `.rows th`
 * rule in 40-primitives.css. The link supplies the vertical padding itself so the whole header cell
 * is the hit target; the horizontal padding has to stay, because it is what lines the header up
 * with the values under it — the body cells have it too.
 */
.rows th.rows__sortable { padding-block: 0; }

.th-sort {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  padding: var(--s2) 0;
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}
.rows__sortable.rows__num .th-sort { justify-content: flex-end; }
.th-sort:hover { color: var(--fg); }
.th-sort--on { color: var(--accent-ink); }

/*
 * ── THE SORT INDICATOR WAS UNREADABLE, AND IN THREE SEPARATE WAYS ───────────────────────────────
 *
 * It was `font-size: 9px; color: var(--fg-4)`. That is the SMALLEST type anywhere on the site,
 * one step below the 10px header label it sits beside, painted in the lightest ink the design
 * permits as text (--fg-4 is documented for "labels, axis ticks"). Small and faint compound: it
 * was legible on the desk it was tuned on and nowhere else.
 *
 * The third way is the one nothing would have caught by eye. The span inherits --font-mono, and
 * IBM Plex Mono has no U+21C5, so the NEUTRAL glyph fell back to whatever the OS offered while
 * the active triangles came from Plex. The two states rendered at different weights and widths in
 * the same header row, on some platforms and not others, which reads as a rendering fault rather
 * than as a control. Naming a symbol-complete stack here means all three glyphs come from one
 * font on every platform.
 *
 * 11px and --fg-3 puts the indicator at the same ink as the label it belongs to and one step above
 * it in size, which is the right way round: the label is a word you read once and the arrow is the
 * part you have to find again.
 */
.th-sort__arrow {
  font-size: 11px;
  line-height: 1;
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI Symbol", sans-serif;
  color: var(--fg-3);
}
.th-sort--on .th-sort__arrow { color: var(--accent-ink); }

/* --------------------------------------------------------------- Instrument table -------- */
.rows--instrument td, .rows--instrument th { padding-top: 9px; padding-bottom: 9px; }
.rows__sub {
  display: block;
  font: 400 11px/1.3 var(--font-sans);
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg-4);
  margin-top: 3px;
}
/* --------------------------------------------------------------- Snapshot -------- */
/*
 * The formula, the venues and the instant. A reader who screenshots a row has to be able to
 * reproduce it, and a snapshot with no timestamp is not reproducible.
 */
.snapshot {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule-soft);
}
/*
 * `max-width: none` overrides the global prose measure on `p` (10-base.css). That measure exists so
 * an argument stays readable at about 68 characters; this is not an argument, it is the formula and
 * the timestamp for the table directly above, and it should run the width of what it describes.
 * Capped, it wrapped to three lines in the left third of the row with the rest of the row empty.
 *
 * ── `flex: 1 1 0`, NOT `1 1 auto`, AND THE DIFFERENCE IS NOT COSMETIC ────────────────────────────
 *
 * With `auto` the flex BASIS is the paragraph's max-content width — the whole formula on one
 * unbroken line. A wrapping flex container breaks items into lines using those hypothetical sizes
 * and only shrinks them AFTERWARDS, within a line. So the paragraph claimed the entire row on its
 * own and the Methodology button was pushed to a second line, indented under it, looking like a
 * broken line break rather than a layout that ran out of room.
 *
 * It fit before only because the sentence was short enough. Adding "4 of 13 assets have a pool"
 * crossed the threshold, which means this was always one clause away from breaking and nothing
 * would have caught it.
 *
 * A basis of 0 takes the paragraph out of the line-breaking decision entirely: all three items
 * collect onto one line, then `flex-grow: 1` hands the paragraph whatever is left after the
 * countdown and the button, and it wraps its own text inside that. `min-width: 0` is the other
 * half — a flex item's automatic minimum is its min-content width, which would otherwise stop it
 * shrinking and reintroduce the overflow.
 */
.snapshot__line {
  margin: 0;
  flex: 1 1 0;
  min-width: 0;
  max-width: none;
  color: var(--fg-4);
  line-height: 1.6;
}
.snapshot__line time { color: var(--fg-3); }

/* --------------------------------------------------------------- Subscribe -------- */
.subscribe {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5);
  flex-wrap: wrap;
  border: 1px solid var(--rule-firm);
  padding: var(--s4);
}
.subscribe__title { font-size: 19px; font-weight: 600; letter-spacing: -0.01em; margin: 0; }
.subscribe__sub { margin: 6px 0 0; color: var(--fg-3); font-size: 14px; max-width: 52ch; }
.subscribe__form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; }
.subscribe__input { min-width: 260px; margin: 0; }
.subscribe__more { flex-basis: 100%; margin: 0; color: var(--fg-4); font-size: 12px; }


/* --------------------------------------------------------------- Nav additions -------- */
/*
 * CENTRE, NOT BASELINE. The MONITOR chip is a bordered box, not a word: aligning its text baseline
 * to the wordmark's hung the box below the letters by the depth of its own bottom padding and
 * border, which is what it looked like — a chip sitting a couple of pixels low against a 15px mark.
 * Its optical centre is what should line up with the wordmark's, and that is what this does.
 */
/*
 * The brand is two lines now: the wordmark and the MONITOR chip on one row, the byline under it.
 *
 * COLOURS ARE THE ALIASES, NOT PANEL-SPECIFIC VALUES. `.nav` carries `panel`, which rebinds --fg-2
 * and --fg-3 to the panel's inks (20-ground.css), so this is written once and would be correct on
 * the light ground too. Same reasoning as `.logo` in 10-base.css, which is why that one has no
 * `.panel .logo` override anywhere.
 */
.nav__brand { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.nav__brand-row { display: flex; align-items: center; gap: 9px; }

.nav__by {
  margin: 0;
  font: 400 11px/1 var(--font-mono);
  letter-spacing: 0.08em;
  color: var(--fg-3);
}
.nav__by a { color: var(--fg-2); text-decoration: none; }
.nav__by a:hover { color: var(--fg); text-decoration: underline; }

/*
 * THE MONITOR CHIP. It carries the LIVE hue as text, which is only legible because this bar is
 * always the dark panel — that is the ground the four signal hues were chosen against. On the light
 * page --live as text is about 1.9:1, which is why the mark/ink split exists everywhere else. The
 * chip never appears there.
 */
.nav__mode {
  font: 500 8.5px/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--live);
  border: 1px solid color-mix(in oklch, var(--live) 50%, transparent);
  padding: 3px 5px;
}

.nav__meta { display: flex; align-items: center; gap: 18px; margin-left: auto; }
.nav__clock {
  font: 500 12px/1 var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--fg-3);
}
.nav__alerts { white-space: nowrap; }

/* The session pill annotates and never states. It carries a mark and a word, and nothing else on
   any page is coloured by it. See app/services/analysis/market_calendar.rb. */
.pill__mark { width: 6px; height: 6px; background: var(--fg-4); flex: 0 0 auto; }
.pill--open .pill__mark { background: var(--inband); }
.pill--closed .pill__mark { background: var(--fg-5); }
.pill--open, .pill--closed { color: var(--fg-2); }

/* .pill is inline-flex, which beats the browser's own [hidden] rule, so hiding it needs saying.
   session_pill_controller.js hides the pill past the end of what config/market_calendar.yml
   covers, rather than asserting a session from a calendar that has never heard of the date. */
.pill[hidden] { display: none; }

/* Inside the nav the pill is a mark and a word with no box: the bar already frames it, and a border
   inside a bordered bar reads as a button somebody forgot to make clickable. */
.nav .pill { border: 0; padding: 0; font-size: 11px; letter-spacing: 0.1em; }

@media (max-width: 860px) {
  .nav__clock, .nav__mode { display: none; }
}

/* --------------------------------------------------------------- Ticker page -------- */
/*
 * THE HEAD IS ONE BASELINE ROW: the key, then the company name. It used to be a space-between bar
 * with an issuer chip on the left and four buttons on the right, and the chip is why the row never
 * lined up. `align-items: baseline` aligns TEXT baselines; a bordered chip is a box with padding of
 * its own, so its text baseline sits above the row's and no amount of alignment recovers it. The
 * chip said "Coinbase B20" on all thirteen pages, so it was carrying nothing worth that.
 */
.ticker-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  padding-bottom: var(--s4);
  margin-bottom: var(--s5);
  border-bottom: 1px solid var(--rule);
}
.ticker-head h1 { margin: 0; font-size: 34px; letter-spacing: -0.02em; }
.ticker-head__name { color: var(--fg-3); font-size: 15px; }

/*
 * THE HEADLINE STRIP. Six cells, equal width, hairline separated. Not a card grid: a card implies
 * each cell is an independent object, and these six are one reading of one instrument at one
 * moment.
 *
 * SIX, BECAUSE THE GRID AT `/` HAS SIX MEASURED COLUMNS. A reader arrives from a row of that table
 * and the strip is that row, expanded. Five different cells in a different order was the same
 * instrument described twice in two vocabularies.
 *
 * The breakpoints divide six evenly at every step -> 3 -> 2 -> 1. The old strip went five to two,
 * which left the fifth cell alone on a row of its own: a two-column grid of five things is a
 * ragged grid.
 */
.headline {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  border: 1px solid var(--rule-firm);
  margin-top: var(--s3);
}
.headline__cell { padding: 16px 18px; border-right: 1px solid var(--rule-soft); }
.headline__cell:last-child { border-right: 0; }
/* ── THE LABEL ROW IS ONE HEIGHT WHETHER OR NOT IT CARRIES A BADGE ────────────────────────────
   A source badge is a bordered box, so an inline label carrying one sets a 30px line box while a
   plain text label sets 12px. Every value below a badged label therefore sat 18px lower than the
   one value whose label had none — and a row of tabular figures that does not share a baseline is
   the one thing this strip cannot do.

   Fixed at the row level rather than by giving every label a badge: the next label to lose one, or
   to wrap, would reintroduce it. */
.headline__cell > .label { min-height: 20px; margin-bottom: 10px; }
/* `.src` carries `margin-bottom: 10px` for the headings it sits beside elsewhere. Inside this row
   the label already owns that gap, so the badge was adding a second one: 20px of badge plus 10px
   of margin against a plain label's 12px line box. Zero it, then floor the label at the badge's
   own height, and a label with a badge and one without occupy the same 20px.

   DELIBERATELY NOT A FLEX ROW. Centring the badge that way stretched it to the full 132px
   flex-basis the transposed layout gives the label under 720px, turning every badge into a wide
   empty box. `.src` also carries `.label`, hence the child selectors. */
.headline__cell > .label > .src { margin-bottom: 0; }
.headline__cell .datum--lg { display: block; margin-bottom: 8px; }
.headline__note { font: 400 11px/1.4 var(--font-mono); color: var(--fg-4); }

/* The staleness clock is the first cell's VALUE now rather than a note under one, so it is set at
   the same size as the prices beside it. Without this the most important number on the page was
   the only one rendered at note size. */
.headline__cell .datum--lg .clock__t { font: 500 24px/1.2 var(--font-mono); }
.headline__cell .datum--lg .clock__mark { width: 9px; height: 9px; }

/* The row separators the wrapped layouts need, and the last row's suppressed: a cell bottom border
   sitting on the container's own bottom border draws the strip's base at twice the weight of every
   rule inside it. Six cells is a fixed count, so "the last row" is a counted selector rather than a
   guess — three of them at 3-across, two at 2-across, one when it transposes. */
@media (max-width: 1180px) {
  .headline { grid-template-columns: repeat(3, 1fr); }
  .headline__cell { border-bottom: 1px solid var(--rule-soft); }
  .headline__cell:nth-child(3n) { border-right: 0; }
  .headline__cell:nth-last-child(-n+3) { border-bottom: 0; }
}

@media (max-width: 980px) {
  .headline { grid-template-columns: repeat(2, 1fr); }
  .headline__cell:nth-child(3n) { border-right: 1px solid var(--rule-soft); }
  .headline__cell:nth-child(2n) { border-right: 0; }
  .headline__cell:nth-last-child(3) { border-bottom: 1px solid var(--rule-soft); }
}

/* The chart's stat header. The two middle figures are the product's claim stated as a contrast, so
   they sit side by side and are never blended into one. */
.chart-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap; margin-bottom: var(--s3);
}
.chart-stats { display: flex; gap: var(--s5); flex-wrap: wrap; }
.chart-stats .label { margin-bottom: 7px; }
.chart-stats .datum { font-size: 17px; font-weight: 500; }
.datum--frozen { color: var(--frozen-ink); }
.datum--breach { color: var(--breach-ink); }

/* A caption states what the chart shows. It is COUNTED, never asserted: see the note in
   app/views/tickers/show.html.erb. */
.caption {
  margin: var(--s3) 0 0;
  color: var(--fg-3);
  font-size: 13px;
  line-height: 1.65;
  max-width: var(--measure);
}
.caption--filter { color: var(--fg-4); }

.split { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5); align-items: start; }
@media (max-width: 980px) { .split { grid-template-columns: 1fr; } }

/*
 * ── THE STALL TABLE IS CAPPED ───────────────────────────────────────────────────────────────────
 *
 * Six columns across the full 1440 put Start at one edge and Oracle move at the other, about
 * 1200px of travel per row. Tabular figures exist so the eye can compare down a column; they
 * cannot help it get across one. 1000px is where a row still reads as a row.
 */
.rows--stalls { max-width: 1000px; }

/*
 * ── CONTRACTS ───────────────────────────────────────────────────────────────────────────────────
 * Full addresses, never truncated: the only thing anybody does with one is paste it into a block
 * explorer, and half an address cannot be pasted. `break-all` because a 42-character hex string
 * has no break opportunity of its own and would otherwise set the page's minimum width.
 */
.contracts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s4); margin-top: var(--s3); }
.contracts div { font: 400 11px/1.7 var(--font-mono); color: var(--fg-4); word-break: break-all; }
.contracts b {
  display: block;
  font: 500 9px/1.4 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-3);
  margin-bottom: 3px;
}
@media (max-width: 720px) { .contracts { grid-template-columns: 1fr; gap: var(--s3); } }
/*
 * ── THE HEADLINE STRIP TRANSPOSES ON A PHONE ────────────────────────────────────────────────────
 *
 * Two columns at 980px still makes every label wrap under its own source badge, and a strip of
 * six values two abreast on a phone is a grid rather than a reading.
 *
 * One per row instead, label left and value right — the same shape the tables already transpose
 * to, so the whole page reads one way at this width.
 */
@media (max-width: 720px) {
  .headline { grid-template-columns: 1fr; }
  .headline__cell {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px var(--s3);
    padding: 13px 14px;
  }
  /* Written at the same specificity as the two rules above that turn column separators back ON at
     the wider breakpoints. A plain `.headline__cell { border-right: 0 }` here loses to
     `.headline__cell:nth-child(3n)` no matter which comes last, and the third and sixth cells kept
     a stray hairline down their right edge in the transposed layout. */
  .headline__cell:nth-child(n) { border-right: 0; }
  .headline__cell:nth-last-child(2) { border-bottom: 1px solid var(--rule-soft); }
  /* `flex-wrap` is what makes `flex-basis: 100%` on the note mean "your own line" rather than
     "grow"; without it the note is squeezed onto the value's line and the longest label wraps
     under its own source badge. 132px fits "Chainlink TRV" beside its badge on one line. */
  .headline__cell .label { flex: 0 0 132px; margin-bottom: 0; }
  .headline__cell .datum--lg { margin-bottom: 0; margin-left: auto; text-align: right; }
  .headline__note { flex-basis: 100%; text-align: right; margin-top: 2px; }
}

/*
 * THE SESSION CLOCK. Two states, derived from config/market_calendar.yml. The design draws five
 * segments; only the regular session is configured, and inventing pre- and post-market boundaries
 * would be a claim about the market nothing here has verified.
 */
.sessions { display: flex; height: 28px; border: 1px solid var(--rule-firm); margin-top: var(--s2); }
.sessions__seg { flex-basis: 0; }
.sessions__seg--open { background: color-mix(in oklch, var(--inband) 22%, transparent); }
.sessions__seg--closed { background: var(--panel-deep); }

/* --------------------------------------------------------------- State blocks -------- */
/*
 * LOUD ABOUT OURSELVES, QUIET ABOUT THE MARKET — the design's rule, expressed as GROUND.
 *
 * DEGRADED inverts to the dark panel because it is our fault, and it is the one thing on the page
 * a reader cannot verify against the chain themselves.
 *
 * PAUSED gets a light wash and no inversion. A paused token is the issuer doing something they are
 * supposed to do, noticed and handled. Treating it as an alarm would train readers to ignore the
 * alarm that matters.
 */
.state-block {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 16px 18px;
  margin-bottom: var(--s4);
  border: 1px solid var(--rule-firm);
}
.state-block__mark { width: 6px; align-self: stretch; flex: 0 0 auto; }
.state-block__title { margin: 0 0 6px; font-size: 15px; font-weight: 600; }
.state-block__body { margin: 0 0 4px; font-size: 13px; line-height: 1.6; color: var(--fg-2); max-width: var(--measure); }
.state-block__body:last-child { margin-bottom: 0; }

.state-block--degraded { border-color: var(--panel-rule); }
.state-block--degraded .state-block__mark { background: var(--breach); }

.state-block--paused {
  background: color-mix(in oklch, var(--frozen) 10%, transparent);
  border-color: color-mix(in oklch, var(--frozen) 40%, transparent);
}
.state-block--paused .state-block__mark { background: var(--frozen); }

/* EMPTY is neither our fault nor the market's: it is the beginning. Quiet, and it carries a date
   rather than a promise, because "coming soon" is what a reader has learned to ignore. */
.state-block--empty { border-color: var(--rule); }
.state-block--empty .state-block__mark { background: var(--fg-5); }

/* The dollar register. A GLOSS on the basis-point figure beside it, never a headline of its own:
   dollars do not compare across tickers, and a reader who sees only the dollar figure cannot tell
   whether $5.82 on a $310 share is normal. PRD §10 requires both, always, with no toggle. */
.gloss { font: 400 11px/1.4 var(--font-mono); font-variant-numeric: tabular-nums; color: var(--fg-4); }

/*
 * THE CONFIDENCE GRADE. Renders only when it is NOT high: a flag on every row trains the eye to
 * skip the column, and the rows worth flagging are the ones that are not high.
 *
 * UNMEASURED is not a low grade. A feed with no pool has no divergence to be confident about, and
 * treating its absent number as a bad measurement would imply we tried and did badly.
 */
.conf {
  font: 500 8.5px/1 var(--font-mono);
  letter-spacing: 0.11em;
  padding: 4px 6px;
  margin-left: 6px;
  vertical-align: middle;
}
.conf--medium { background: color-mix(in oklch, var(--frozen) 22%, transparent); color: var(--frozen-ink); }
.conf--low { background: color-mix(in oklch, var(--breach) 18%, transparent); color: var(--breach-ink); }
.conf--unmeasured { background: color-mix(in oklch, var(--fg-4) 12%, transparent); color: var(--neutral-ink); }
