/* ============================================================================
   Canvas 2026 — the design layer for the intelligence pages.
   ============================================================================

   Calm canvas, expressive data.

   The rule that produces everything below: the page recedes and the numbers
   come forward. Where the previous system raised data onto glass cards that
   floated above the page, this one recesses it into wells cut *into* the
   surface. A card that floats says "look at this container"; a well says
   "look at what is in it". On a page whose whole claim is the figures, the
   container should be the quiet part.

   What is kept from the existing identity, because it is coherent:
   the accent blue, the cyan, green/red for movement, and the 8/12/16 radius
   scale — which already matches the brief's ranges.

   What is dropped: the glass gradients. Universal glassmorphism is on the
   brief's avoid list, and it was doing the opposite of the rule above.

   Colour discipline, which is not decoration but meaning:
     cyan   — evidence, provenance, verification. Nothing else.
     green  — a price went up. Always with a sign or an arrow.
     red    — a price went down. Same.
     amber  — attention: a wait, a partial state, a caveat.
   A reader who learns cyan = verified must never see cyan on a price.
   ============================================================================ */

:root {
  /* --- Canvas and surfaces -------------------------------------------------
     Five steps, and the direction matters. Structural surfaces step *up*
     from the canvas; data regions step *down* from their surface. */
  --c-canvas:        #080d14;
  --c-surface:       #0d141e;
  --c-surface-raised:#111a26;
  --c-inset:         #060a10;
  --c-overlay:       #121b28;

  /* Hairlines. Two weights: one to separate, one to group. */
  --c-line:          rgba(148, 170, 200, 0.13);
  --c-line-strong:   rgba(148, 170, 200, 0.24);

  /* --- Text ---------------------------------------------------------------
     Four levels. Anything below c-text-faint is decoration and should not
     carry information. */
  --c-text:          #e8f1ff;
  --c-text-secondary:#b6c6da;
  --c-text-muted:    #8296ad;
  /* #5b6b80 was 3.40:1 on the surface, under the 4.5 AA needs -- and this
     level is not decoration in practice. An audit of the rendered page found
     it carrying an event's time, the Session label and every evidence-step
     name: all of them information. The comment above says a level below this
     must not carry information, and the fix is to make the level meet AA
     rather than to keep using it for text and hope. #74869e is 4.97:1 on the
     surface, 5.24 on the canvas, 5.33 in a well. */
  --c-text-faint:    #74869e;

  /* --- Meaning ------------------------------------------------------------ */
  --c-evidence:      #22d3ee;   /* verification, provenance, evidence only  */
  --c-evidence-dim:  rgba(34, 211, 238, 0.14);
  --c-up:            #34d399;
  --c-down:          #f87171;
  --c-attention:     #fbbf24;
  --c-neutral:       #94a3b8;

  /* --- Radius: the brief's ranges ---------------------------------------- */
  --c-r-control:     9px;
  --c-r-panel:       12px;
  --c-r-feature:     15px;
  --c-r-overlay:     16px;

  /* --- Rhythm ------------------------------------------------------------- */
  --c-space-1: 0.25rem;
  --c-space-2: 0.5rem;
  --c-space-3: 0.75rem;
  --c-space-4: 1rem;
  --c-space-5: 1.5rem;
  --c-space-6: 2rem;
  --c-space-7: 3rem;

  /* --- Type scale ---------------------------------------------------------
     A ratio near 1.2, so headings step without shouting. Figures get their
     own scale because they are read, not skimmed. */
  --c-t-label:  0.7rem;
  --c-t-meta:   0.78rem;
  --c-t-body:   0.925rem;
  --c-t-lead:   1.05rem;
  --c-t-h3:     1.15rem;
  --c-t-h2:     1.45rem;
  --c-t-h1:     clamp(1.75rem, 3.2vw, 2.4rem);
  --c-t-figure:  clamp(1.5rem, 3vw, 2.1rem);
  --c-t-figure-sm: 1.15rem;

  --c-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
            "Liberation Mono", monospace;

  --c-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --c-shadow-overlay: 0 24px 64px rgba(0, 0, 0, 0.5);
}

/* The brief says dark mode must not be a simple inversion, and it is not:
   the light theme reverses the *direction* of the inset. On dark, data is
   recessed into a well darker than its surface. On light, a well darker
   than white reads as dirt, so data sits on a cool tint instead and the
   separation comes from the hairline. Same hierarchy, different mechanics. */
:root[data-theme="light"] {
  --c-canvas:        #f7f9fc;
  --c-surface:       #ffffff;
  --c-surface-raised:#ffffff;
  --c-inset:         #f1f5fa;
  --c-overlay:       #ffffff;
  --c-line:          rgba(15, 35, 60, 0.11);
  --c-line-strong:   rgba(15, 35, 60, 0.2);
  --c-text:          #0f2033;
  --c-text-secondary:#3c5064;
  --c-text-muted:    #5f7286;
  /* Worse than the dark theme's was: 2.62:1 in a well. Same reasoning, same
     fix -- #5f6e80 is 4.76:1 at its lowest across the three surfaces. */
  --c-text-faint:    #5f6e80;
  /* 3.36:1 in a well. Cyan on white is the hardest pairing in this palette
     and the one carrying the site's central claim, so it gets the deepest
     correction: #0e7490 is 4.52:1 at its lowest. */
  --c-evidence:      #0e7490;
  --c-evidence-dim:  rgba(8, 145, 178, 0.1);
  /* #059669 was 3.77:1 -- under AA in the theme where a green on white is
     hardest. #047857 is 5.01:1 at its lowest. */
  --c-up:            #047857;
  /* #dc2626 was 4.41:1 in a well -- under AA, and this is the colour that
     tells a reader a price fell. #c81e1e is 5.24:1 at its lowest. */
  --c-down:          #c81e1e;
  --c-attention:     #b45309;
  /* Inherited from the dark palette, where #94a3b8 is right, and never
     overridden -- so on a light ground it was 2.34:1. A token defined in one
     theme and used in both is the failure the light theme exists to avoid,
     and it was hiding in the one token I did not restate. */
  --c-neutral:       #5b6b7f;
  --c-shadow-overlay: 0 20px 48px rgba(15, 35, 60, 0.16);
}

/* ---------------------------------------------------------------------------
   Surface hierarchy
   Canvas → structural surface → section → inset data region → control → overlay
   Never a card inside a card inside a card.
   --------------------------------------------------------------------------- */

.c-surface {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-panel);
}

.c-section {
  padding: var(--c-space-5);
}
.c-section + .c-section { border-top: 1px solid var(--c-line); }

/* The well. Data is recessed, not raised — no shadow, no gradient. */
.c-inset {
  background: var(--c-inset);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-control);
  padding: var(--c-space-4);
}

/* Shadows are for things that genuinely float. Not data panels. */
.c-overlay {
  background: var(--c-overlay);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--c-r-overlay);
  box-shadow: var(--c-shadow-overlay);
}

/* ---------------------------------------------------------------------------
   Typography
   --------------------------------------------------------------------------- */

.c-label {
  font-size: var(--c-t-label);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--c-text-faint);
}
.c-meta  { font-size: var(--c-t-meta); color: var(--c-text-muted); }
.c-body  { font-size: var(--c-t-body); line-height: 1.65; color: var(--c-text-secondary); }
.c-lead  { font-size: var(--c-t-lead); line-height: 1.6; color: var(--c-text-secondary); }

.c-h1 { font-size: var(--c-t-h1); font-weight: 700; line-height: 1.15;
        letter-spacing: -0.02em; color: var(--c-text); text-wrap: balance; margin: 0; }
.c-h2 { font-size: var(--c-t-h2); font-weight: 650; line-height: 1.25;
        letter-spacing: -0.01em; color: var(--c-text); text-wrap: balance; margin: 0; }
.c-h3 { font-size: var(--c-t-h3); font-weight: 600; line-height: 1.35;
        color: var(--c-text); margin: 0; }

/* Every figure. Tabular numerals so columns of prices align and a changing
   value does not make the row jump. */
.c-figure, .c-figure-sm, .c-num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: -0.015em;
}
.c-figure    { font-size: var(--c-t-figure);    font-weight: 680; color: var(--c-text); line-height: 1.05; }
.c-figure-sm { font-size: var(--c-t-figure-sm); font-weight: 640; color: var(--c-text); line-height: 1.15; }
.c-num       { font-size: var(--c-t-body); }
.c-mono      { font-family: var(--c-mono); font-size: var(--c-t-meta);
               font-variant-numeric: tabular-nums; }

/* Direction of movement. The class carries the colour; the glyph carries the
   meaning for anyone who cannot use the colour. Never colour alone. */
.c-up   { color: var(--c-up); }
.c-down { color: var(--c-down); }
.c-flat { color: var(--c-neutral); }
.c-up::before   { content: "▲ "; font-size: 0.75em; }
.c-down::before { content: "▼ "; font-size: 0.75em; }
.c-flat::before { content: "— "; }

/* ---------------------------------------------------------------------------
   3.1 Market Horizon — a time-aware state rail.
   Not a ticker: it does not scroll, and every marker is inspectable.
   --------------------------------------------------------------------------- */

.horizon {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-panel);
  padding: var(--c-space-4) var(--c-space-5);
  display: grid;
  gap: var(--c-space-4);
}

.horizon__head {
  display: flex; align-items: center; gap: var(--c-space-3);
  flex-wrap: wrap;
}

/* The regime badge. The word does the work; the colour agrees with it. */
.horizon__regime {
  display: inline-flex; align-items: center; gap: var(--c-space-2);
  font-size: var(--c-t-label); font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.32rem 0.6rem;
  border-radius: var(--c-r-control);
  border: 1px solid currentColor;
}
.horizon__regime--risk-on  { color: var(--c-up); }
.horizon__regime--risk-off { color: var(--c-down); }
.horizon__regime--mixed    { color: var(--c-neutral); }

/* The rail itself. A single line with markers placed by inline-start %, so
   it flips with direction without a second stylesheet. */
.horizon__rail {
  position: relative;
  height: 2.75rem;
  border-radius: var(--c-r-control);
  background: var(--c-inset);
  border: 1px solid var(--c-line);
}
.horizon__track {
  position: absolute; inset-block-start: 50%; inset-inline: var(--c-space-4);
  height: 1px; background: var(--c-line-strong);
}
.horizon__marker {
  position: absolute; inset-block-start: 50%;
  transform: translate(-50%, -50%);
  display: grid; place-items: center; gap: 0;
}
[dir="rtl"] .horizon__marker { transform: translate(50%, -50%); }
.horizon__dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--c-text-faint);
  border: 2px solid var(--c-canvas);
  box-shadow: 0 0 0 1px var(--c-line-strong);
}
.horizon__marker--now .horizon__dot {
  width: 11px; height: 11px;
  background: var(--c-evidence);
  box-shadow: 0 0 0 4px var(--c-evidence-dim);
}
.horizon__marker--past .horizon__dot { background: var(--c-text-faint); }
/* Captions are positioned relative to the marker, which is itself centred on
   its point. A caption near either end would overflow the rail, and `now`
   moves through the day -- before 01:00 and after 23:00 it sits inside 5% of
   an edge every single day. Those anchor to the edge instead. */
.horizon__caption {
  position: absolute; inset-block-start: calc(100% + 4px);
  white-space: nowrap;
  font-size: var(--c-t-label); color: var(--c-text-muted);
}
.horizon__caption--start { inset-inline-start: 0; }
.horizon__caption--end   { inset-inline-end: 0; }

/* Below ~34rem the session labels collide: measured at 375px, "After hours"
   and "Now" overlap, and any two can collide as `now` moves. The rail still
   shows position, which is the part that matters on a phone, so only `now`
   keeps its label there. Not a shrunken desktop layout -- a different answer
   for a different amount of room. */
@media (max-width: 34rem) {
  .horizon__marker:not(.horizon__marker--now) .horizon__caption {
    position: absolute;
    width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  .horizon__rail { height: 2.25rem; }
}

.horizon__assets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 1px;
  background: var(--c-line);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-control);
  overflow: hidden;
}
/* One hairline between cells, drawn by the gap over a line-coloured
   background. Cheaper than borders that double up, and it keeps the corners
   clean without :first-child gymnastics. */
.horizon__asset {
  background: var(--c-inset);
  padding: var(--c-space-3) var(--c-space-4);
  display: grid; gap: 2px;
}
.horizon__asset-name { font-size: var(--c-t-label); color: var(--c-text-muted);
                       letter-spacing: 0.06em; text-transform: uppercase; }
.horizon__asset-value { font-size: var(--c-t-figure-sm); font-weight: 640;
                        font-variant-numeric: tabular-nums; color: var(--c-text); }
.horizon__asset-change { font-size: var(--c-t-meta); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
   3.2 Narrative Spine — event, effects, interpretations, in one connected run.
   --------------------------------------------------------------------------- */

.spine { display: grid; gap: var(--c-space-5); }

.spine__event {
  position: relative;
  padding-inline-start: var(--c-space-6);
}
/* The vertical run. inset-inline-start, so RTL needs no override. */
.spine__event::before {
  content: ""; position: absolute;
  inset-inline-start: 5px; inset-block: 0.65rem 0;
  width: 1px; background: var(--c-line-strong);
}
.spine__event:last-child::before { inset-block-end: auto; height: 0.65rem; }

.spine__node {
  position: absolute; inset-inline-start: 0; inset-block-start: 0.45rem;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--c-evidence); box-shadow: 0 0 0 3px var(--c-evidence-dim);
}

.spine__time { font-size: var(--c-t-label); color: var(--c-text-faint);
               font-variant-numeric: tabular-nums; letter-spacing: 0.06em; }
.spine__claim { font-size: var(--c-t-lead); font-weight: 600; color: var(--c-text);
                line-height: 1.4; margin: 2px 0 var(--c-space-3); text-wrap: balance; }

.spine__branch { display: grid; gap: 1px; background: var(--c-line);
                 border: 1px solid var(--c-line); border-radius: var(--c-r-control);
                 overflow: hidden; }
.spine__leaf {
  background: var(--c-inset);
  display: flex; align-items: baseline; gap: var(--c-space-3);
  padding: 0.45rem var(--c-space-4);
  font-size: var(--c-t-meta);
}
.spine__leaf-symbol { font-weight: 650; color: var(--c-text-secondary);
                      min-width: 4.5rem; }
.spine__leaf-value  { font-variant-numeric: tabular-nums; margin-inline-start: auto; }

/* Model readings sit under the market effects, visibly a different kind of
   thing: they are interpretations, not measurements. */
.spine__models { margin-block-start: var(--c-space-3); display: grid; gap: var(--c-space-2); }
.spine__model {
  display: flex; align-items: center; gap: var(--c-space-3);
  font-size: var(--c-t-meta); color: var(--c-text-muted);
}
.spine__model-name { color: var(--c-text-secondary); font-weight: 600; }
.spine__model-call { color: var(--c-evidence); font-weight: 600; }

/* ---------------------------------------------------------------------------
   3.3 Evidence Threads — source → claim → asset → inference → outcome.
   Quiet by default: a <details> that costs nothing until opened.
   --------------------------------------------------------------------------- */

.thread { border-block-start: 1px solid var(--c-line); margin-block-start: var(--c-space-4); }
.thread > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: var(--c-space-2);
  padding-block: var(--c-space-3);
  font-size: var(--c-t-label); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--c-evidence); font-weight: 650;
}
.thread > summary::-webkit-details-marker { display: none; }
.thread > summary::after {
  content: "＋"; margin-inline-start: auto; font-size: 0.9rem;
  color: var(--c-text-faint); transition: transform 0.18s var(--c-ease);
}
.thread[open] > summary::after { content: "−"; }
.thread:focus-within > summary { outline: 2px solid var(--c-evidence); outline-offset: 3px; }

.thread__steps { display: grid; gap: 0; padding-block-end: var(--c-space-4); }
.thread__step {
  position: relative;
  padding-inline-start: var(--c-space-5);
  padding-block: var(--c-space-2);
  font-size: var(--c-t-meta);
  color: var(--c-text-secondary);
}
.thread__step::before {
  content: ""; position: absolute; inset-inline-start: 4px; inset-block: 0;
  width: 1px; background: var(--c-evidence-dim);
}
.thread__step:last-child::before { inset-block-end: 50%; }
.thread__step::after {
  content: ""; position: absolute; inset-inline-start: 1px;
  inset-block-start: 0.85rem;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--c-canvas); border: 1.5px solid var(--c-evidence);
}
.thread__step-label { display: block; font-size: var(--c-t-label);
                      color: var(--c-text-faint); letter-spacing: 0.07em;
                      text-transform: uppercase; }

/* ---------------------------------------------------------------------------
   3.5 Verification Lifecycle — never colour alone; the glyph and the word
   both carry the state.
   --------------------------------------------------------------------------- */

.lifecycle { display: flex; flex-wrap: wrap; gap: var(--c-space-1); align-items: center; }
.lifecycle__step {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: var(--c-t-label); letter-spacing: 0.04em;
  padding: 0.24rem 0.5rem; border-radius: var(--c-r-control);
  border: 1px solid var(--c-line);
  color: var(--c-text-faint);
  white-space: nowrap;
}
.lifecycle__step--done    { color: var(--c-evidence); border-color: var(--c-evidence); }
.lifecycle__step--current { color: var(--c-attention); border-color: var(--c-attention); }
.lifecycle__step--excluded{ color: var(--c-text-muted); border-style: dashed; }
.lifecycle__step--done::before    { content: "✓"; }
.lifecycle__step--current::before { content: "◔"; }
.lifecycle__step--pending::before { content: "○"; }
.lifecycle__step--excluded::before{ content: "⊘"; }
.lifecycle__sep { color: var(--c-text-faint); font-size: 0.7rem; }

/* Pipeline chip. Same shape as the data-health chip and deliberately a
   different class: this one says whether the evaluation job is keeping up,
   which is a claim about our own scoring, not about the market data. */
.pipeline {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: var(--c-t-label); font-weight: 650; letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.22rem 0.5rem; border-radius: var(--c-r-control);
  border: 1px solid currentColor;
}
.pipeline--live      { color: var(--c-up); }
.pipeline--delayed,
.pipeline--partial   { color: var(--c-attention); }
.pipeline--stale     { color: var(--c-text-muted); }
.pipeline--unavailable { color: var(--c-down); }
.pipeline::before { content: "◇"; font-size: 0.7em; }
.pipeline--stale::before        { content: "◌"; }
.pipeline--unavailable::before  { content: "⚠"; font-size: 0.85em; }

/* Data-health chip. Same rule: the word is the state, the colour agrees. */
.health {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: var(--c-t-label); font-weight: 650; letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.22rem 0.5rem; border-radius: var(--c-r-control);
  border: 1px solid currentColor;
}
.health--live        { color: var(--c-up); }
.health--delayed,
.health--refreshing,
.health--partial     { color: var(--c-attention); }
.health--stale,
.health--historical  { color: var(--c-text-muted); }
.health--unavailable,
.health--error,
.health--offline     { color: var(--c-down); }
.health::before { content: "●"; font-size: 0.6em; }
.health--stale::before,
.health--historical::before  { content: "◌"; }
.health--unavailable::before,
.health--error::before,
.health--offline::before     { content: "⚠"; font-size: 0.85em; }
.health--delayed::before,
.health--refreshing::before,
.health--partial::before     { content: "◐"; font-size: 0.85em; }

/* ---------------------------------------------------------------------------
   3.6 Adaptive Composition
   Container queries where supported, so a component reacts to the space it
   is given rather than to the viewport. A panel in a narrow column and the
   same panel full-width are different situations even at one screen size.
   --------------------------------------------------------------------------- */

.c-adaptive { container-type: inline-size; }

@container (min-width: 34rem) {
  .c-adaptive .horizon__head { flex-wrap: nowrap; }
}
@container (max-width: 30rem) {
  .c-adaptive .spine__leaf { flex-wrap: wrap; }
  .c-adaptive .spine__leaf-value { margin-inline-start: 0; }
}

/* ---------------------------------------------------------------------------
   Motion — only where it explains continuity, and never against a stated
   preference.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .thread__steps { animation: c-reveal 0.22s var(--c-ease); }
  @keyframes c-reveal {
    from { opacity: 0; transform: translateY(-3px); }
    to   { opacity: 1; transform: none; }
  }
}

/* Keyboard focus is visible everywhere, in the evidence colour so the
   affordance reads as part of the system. */
.c-surface :focus-visible,
.horizon :focus-visible,
.spine :focus-visible,
.thread :focus-visible {
  outline: 2px solid var(--c-evidence);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ---------------------------------------------------------------------------
   Home — Daily Intelligence Canvas
   --------------------------------------------------------------------------- */

.home-canvas { margin-block: var(--c-space-5); }

.home-canvas__head {
  display: flex; align-items: baseline; gap: var(--c-space-4);
  margin-block-end: var(--c-space-2);
}
.home-canvas__link {
  margin-inline-start: auto;
  font-size: var(--c-t-meta); font-weight: 600;
  color: var(--c-evidence); text-decoration: none;
  white-space: nowrap;
}
.home-canvas__link:hover { text-decoration: underline; }
.home-canvas__note { margin: 0 0 var(--c-space-5); max-width: 62ch; }

.home-bench__figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--c-space-3);
}
.home-bench__figures .c-inset { display: grid; gap: 3px; align-content: start; }
.c-text-faint { color: var(--c-text-faint); }

/* ---------------------------------------------------------------------------
   Accuracy — pipeline state
   --------------------------------------------------------------------------- */

.pipeline { margin-block-end: var(--c-space-5); }
.pipeline__head {
  display: flex; align-items: center; gap: var(--c-space-3);
  flex-wrap: wrap; margin-block-end: var(--c-space-4);
}
.pipeline__when { margin-inline-start: auto; }
.pipeline__figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: var(--c-space-3);
}
.pipeline__figures .c-inset { display: grid; gap: 3px; align-content: start; }

/* ---------------------------------------------------------------------------
   Model Field (brief §10.5) — accuracy against sample, so the reader can see
   at a glance which figures are estimates and which are anecdotes.

   The brief asks for something other than a table as the primary expression,
   and the reason is specific to this data: a table gives 0% on n=3 and 17.6%
   on n=1,618 the same visual weight, when the first says nothing at all. Here
   sample size *is* the horizontal axis, so a lonely dot on the left reads as
   provisional without anyone having to write the word. The table stays below
   it, and is what a screen reader gets.
   --------------------------------------------------------------------------- */

.field {
  position: relative;
  background: var(--c-inset);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-panel);
  padding: var(--c-space-5) var(--c-space-5) var(--c-space-7);
  min-height: 16rem;
}
.field__grid { position: absolute; inset: var(--c-space-5); pointer-events: none; }
.field__gridline {
  position: absolute; inset-inline: 0; height: 1px; background: var(--c-line);
}
.field__gridlabel {
  position: absolute; inset-inline-start: 0; transform: translateY(-50%);
  font-size: var(--c-t-label); color: var(--c-text-faint);
  font-variant-numeric: tabular-nums;
}
/* The baseline any model has to beat. Dashed, because it is a reference and
   not a measurement. */
.field__baseline {
  position: absolute; inset-inline: 0; height: 0; border-block-start: 1px dashed var(--c-attention);
}
.field__baseline-label {
  position: absolute; inset-inline-end: 0; inset-block-start: -1.15rem;
  font-size: var(--c-t-label); color: var(--c-attention); letter-spacing: 0.06em;
}
.field__point {
  position: absolute; transform: translate(-50%, 50%);
  display: grid; place-items: center;
}
[dir="rtl"] .field__point { transform: translate(50%, 50%); }
.field__dot {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--c-evidence); border: 2px solid var(--c-canvas);
  box-shadow: 0 0 0 3px var(--c-evidence-dim);
}
/* A provisional point is hollow. Colour is not doing the work: the ring
   itself says "not enough behind this yet". */
.field__point--provisional .field__dot {
  background: transparent; border-color: var(--c-text-muted);
  box-shadow: none;
}
.field__point--fallback .field__dot { background: var(--c-text-muted); box-shadow: none; }
/* Two lines, not one. Measured: a single-line caption carrying the full
   provider name collided with its neighbours and overflowed the plot even at
   900px. Stacking name over sample halves the width, and edge points anchor
   rather than centre so they cannot overflow. */
.field__caption {
  position: absolute; inset-block-start: calc(100% + 5px);
  display: grid; gap: 0; text-align: center;
  font-size: var(--c-t-label); line-height: 1.25;
}
.field__caption--start { inset-inline-start: 0; text-align: start; }
.field__caption--end   { inset-inline-end: 0; text-align: end; }
.field__caption-name { color: var(--c-text-secondary); white-space: nowrap; }
.field__caption-n {
  color: var(--c-text-faint); font-variant-numeric: tabular-nums;
}

/* On a phone three captions cannot share this width at any length. The plot
   keeps its shape -- which point is high, which is lonely on the left -- and
   the table underneath carries every name and number. The figure is
   aria-hidden, so nothing is lost that was ever announced. */
@media (max-width: 34rem) {
  .field__caption { display: none; }
  .field { min-height: 11rem; padding-block-end: var(--c-space-5); }
}
/* The axis sits below the plot area, not inside it, so an edge caption and
   the axis label cannot occupy the same pixels. They did: the rightmost
   point's "n=900" landed on top of the axis's own "900 scored". */
.field__axis {
  position: absolute; inset-block-end: var(--c-space-2); inset-inline: var(--c-space-5);
  display: flex; justify-content: space-between;
  font-size: var(--c-t-label); color: var(--c-text-faint);
  opacity: 0.75;
}
.field__legend {
  display: flex; gap: var(--c-space-4); flex-wrap: wrap;
  margin-block-start: var(--c-space-3);
  font-size: var(--c-t-label); color: var(--c-text-muted);
}
.field__key { display: inline-flex; align-items: center; gap: 0.35rem; }
.field__key-dot { width: 9px; height: 9px; border-radius: 50%; }

/* ---------------------------------------------------------------------------
   News — narrative grouping
   --------------------------------------------------------------------------- */

.narratives { margin-block: var(--c-space-5); }
.narratives__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: var(--c-space-4);
}

/* A narrative is a semantically independent unit, so it earns a surface.
   The stories inside it do not — they are an inset list, not more cards. */
.narrative {
  background: var(--c-inset);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-panel);
  padding: var(--c-space-4);
}
.narrative__head {
  display: flex; align-items: baseline; gap: var(--c-space-3);
  flex-wrap: wrap; margin-block-end: var(--c-space-3);
  padding-block-end: var(--c-space-3);
  border-block-end: 1px solid var(--c-line);
}
.narrative__symbol {
  font-size: var(--c-t-h3); font-weight: 700; color: var(--c-text);
  text-decoration: none; letter-spacing: -0.01em;
}
.narrative__symbol:hover { color: var(--c-evidence); }

/* Agreement carries a glyph as well as a colour, because "models disagree"
   is the finding, not an error state, and it has to survive greyscale. */
.narrative__agree {
  margin-inline-start: auto;
  font-size: var(--c-t-label); font-weight: 650; letter-spacing: 0.05em;
  padding: 0.2rem 0.45rem; border-radius: var(--c-r-control);
  border: 1px solid currentColor; white-space: nowrap;
}
.narrative__agree--agree  { color: var(--c-evidence); }
.narrative__agree--mostly { color: var(--c-text-secondary); }
.narrative__agree--split  { color: var(--c-attention); }
.narrative__agree--none   { color: var(--c-text-faint); }
.narrative__agree--agree::before  { content: "≡ "; }
.narrative__agree--mostly::before { content: "≈ "; }
.narrative__agree--split::before  { content: "⇄ "; }

.narrative__stories { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--c-space-3); }
.narrative__story { display: grid; gap: 2px; }
.narrative__story + .narrative__story {
  padding-block-start: var(--c-space-3);
  border-block-start: 1px solid var(--c-line);
}
.narrative__time { font-size: var(--c-t-label); color: var(--c-text-faint);
                   font-variant-numeric: tabular-nums; }
.narrative__title {
  font-size: var(--c-t-body); font-weight: 550; line-height: 1.45;
  color: var(--c-text-secondary); text-decoration: none; text-wrap: pretty;
}
.narrative__title:hover { color: var(--c-text); }
.narrative__source { font-size: var(--c-t-label); color: var(--c-text-faint); }

/* ---------------------------------------------------------------------------
   Symbol — model calls and their verification state
   --------------------------------------------------------------------------- */

.calls { margin-block: var(--c-space-5); }
.calls__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--c-space-4); }
.calls__item {
  background: var(--c-inset);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-control);
  padding: var(--c-space-4);
  display: grid; gap: var(--c-space-3);
}
.calls__row { display: flex; align-items: baseline; gap: var(--c-space-3); flex-wrap: wrap; }
.calls__provider { font-weight: 650; color: var(--c-text); font-size: var(--c-t-body); }
.calls__call { color: var(--c-evidence); font-weight: 600; font-size: var(--c-t-meta); }
.calls__when { margin-inline-start: auto; }
.calls__reason { margin: 0; font-size: var(--c-t-meta); color: var(--c-text-muted); }
.calls__outcome { margin: 0; font-size: var(--c-t-meta); font-weight: 600; }

.article-evidence { margin-block: var(--c-space-5); }

/* ---------------------------------------------------------------------------
   Tabular numerals, site-wide (brief §4.4).

   Required for "prices, returns, volumes, and benchmark tables", and the
   reason is mechanical rather than aesthetic: proportional digits give 1 a
   narrower advance than 0, so a price ticking from 65,110 to 65,011 changes
   width, the row reflows, and a column of figures does not line up. On a page
   that updates in place this reads as the number flickering.

   Set on the root and opted out of, rather than listed class by class.

   I tried the list first: reading the templates for number-bearing class
   names produced 57 of them and still left 181 numeric elements untouched
   across ten pages, because figures also live in bare <td>s, in spans with
   layout-only classes, and in markup no name predicts. A list like that is
   never finished, goes stale the moment a template changes, and fails
   silently when it does — which is the exact failure this whole engagement
   kept finding elsewhere.

   Inverting it is one rule that cannot go stale. This is a financial data
   site: nearly every digit on it is a measurement, so aligned is the right
   default and prose is the exception. Use .c-prose where proportional digits
   genuinely read better — running body copy with a year or a count in it.

   Note this sets only digit *rendering*. It cannot move, resize or restyle
   anything, so it is safe to apply globally over a shell it does not own.
   --------------------------------------------------------------------------- */

body {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.c-prose, .c-prose * {
  font-variant-numeric: normal;
  font-feature-settings: normal;
}

/* A scheduled event on the rail. Distinguished from a session boundary by
   shape as well as colour -- a session marker is a filled dot, an event is a
   ring with a stem, because they are different kinds of moment: one is where
   the day is, the other is what is coming. */
.horizon__marker--event .horizon__dot {
  width: 10px; height: 10px;
  background: var(--c-canvas);
  border: 2px solid var(--c-attention);
  box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.14);
}
.horizon__marker--event .horizon__caption {
  color: var(--c-attention);
  font-weight: 650;
}

/* ---------------------------------------------------------------------------
   3.4 Context Lens
   A panel that opens beside what you selected and gives your place back when
   it closes. Three shapes, one element: side inspector, sheet, bottom sheet.
   --------------------------------------------------------------------------- */

.lens {
  border: none;
  padding: 0;
  background: var(--c-overlay);
  color: var(--c-text);
  box-shadow: var(--c-shadow-overlay);
  max-height: 100dvh;
  overscroll-behavior: contain;
}
.lens::backdrop {
  background: rgba(4, 8, 14, 0.62);
  backdrop-filter: blur(2px);
}

/* Desktop: a right-hand inspector, full height, anchored to the inline end so
   it flips for Arabic without a second rule. */
@media (min-width: 60rem) {
  .lens {
    margin: 0 0 0 auto;
    inset-inline-end: 0;
    inset-block: 0;
    width: min(30rem, 92vw);
    height: 100dvh;
    max-width: none;
    border-inline-start: 1px solid var(--c-line-strong);
    border-radius: 0;
  }
  [dir="rtl"] .lens { margin: 0 auto 0 0; }
}

/* Tablet: a centred sheet -- at this width a side panel leaves too little
   beside it to be worth keeping in view. */
@media (min-width: 40rem) and (max-width: 59.999rem) {
  .lens {
    margin: auto;
    width: min(34rem, 92vw);
    border-radius: var(--c-r-overlay);
    border: 1px solid var(--c-line-strong);
  }
}

/* Phone: a bottom sheet. Reachable by thumb, and it keeps the top of the
   page visible so the reader can see what they came from. */
@media (max-width: 39.999rem) {
  .lens {
    /* margin-block-end 0, explicitly. A <dialog> carries a default margin on
       every side, so `margin: auto auto 0 auto` still left the browser's own
       block-end margin in play and the sheet sat 16px below the viewport --
       its bottom edge, and the safe-area it should be respecting, off
       screen. */
    margin: auto 0 0 0;
    inset-block-end: 0;
    width: 100%;
    max-width: 100%;
    max-height: 82dvh;
    border-start-start-radius: var(--c-r-overlay);
    border-start-end-radius: var(--c-r-overlay);
    border-end-start-radius: 0;
    border-end-end-radius: 0;
    border-block-start: 1px solid var(--c-line-strong);
    padding-block-end: env(safe-area-inset-bottom, 0);
  }
  .lens__head::before {
    content: ""; display: block;
    width: 2.25rem; height: 3px; border-radius: 2px;
    background: var(--c-line-strong);
    position: absolute; inset-block-start: 0.5rem;
    inset-inline-start: 50%; transform: translateX(-50%);
  }
}

.lens__head {
  position: relative;
  display: flex; align-items: flex-start; gap: var(--c-space-4);
  padding: var(--c-space-5);
  border-block-end: 1px solid var(--c-line);
}
.lens__kicker { margin: 0 0 2px; }
.lens__close {
  margin-inline-start: auto;
  background: none; border: 1px solid var(--c-line);
  border-radius: var(--c-r-control);
  color: var(--c-text-muted);
  width: 2rem; height: 2rem; line-height: 1;
  font-size: 1.1rem; cursor: pointer;
  flex: none;
}
.lens__close:hover { color: var(--c-text); border-color: var(--c-line-strong); }

.lens__body {
  padding: var(--c-space-5);
  overflow-y: auto;
  display: grid; gap: var(--c-space-4);
}
.lens__foot {
  padding: var(--c-space-4) var(--c-space-5);
  border-block-start: 1px solid var(--c-line);
}
.lens__link { color: var(--c-evidence); text-decoration: none; font-weight: 600;
              font-size: var(--c-t-meta); }
.lens__link:hover { text-decoration: underline; }

.lens__row {
  display: flex; align-items: baseline; gap: var(--c-space-3);
  padding-block: 0.4rem;
  border-block-end: 1px solid var(--c-line);
  font-size: var(--c-t-meta);
}
.lens__row:last-child { border-block-end: none; }
.lens__row dt { color: var(--c-text-faint); min-width: 7.5rem; }
.lens__row dd { margin: 0; color: var(--c-text-secondary);
                margin-inline-start: auto; text-align: end; }

/* Motion explains where the panel came from, and only where motion is
   welcome. Each shape animates from its own edge, so the gesture matches the
   geometry rather than being one generic fade. */
@media (prefers-reduced-motion: no-preference) {
  @media (min-width: 60rem) {
    .lens[open] { animation: lens-in-side 0.2s var(--c-ease); }
    @keyframes lens-in-side {
      from { transform: translateX(12px); opacity: 0; }
      to   { transform: none; opacity: 1; }
    }
    [dir="rtl"] .lens[open] { animation-name: lens-in-side-rtl; }
    @keyframes lens-in-side-rtl {
      from { transform: translateX(-12px); opacity: 0; }
      to   { transform: none; opacity: 1; }
    }
  }
  @media (max-width: 39.999rem) {
    .lens[open] { animation: lens-in-sheet 0.22s var(--c-ease); }
    @keyframes lens-in-sheet {
      from { transform: translateY(16px); opacity: 0; }
      to   { transform: none; opacity: 1; }
    }
  }
}
