/* Avid Quant desk design system: the component layer.

   Every value here comes from tokens.css through var(). There is no colour
   literal, no font stack and no bare pixel size in this file that a token
   could have carried, and the guard in tests/test_design_system.py fails
   the build if one appears.

   Load order matters and is fixed:

     1. the vendored renderer's own inline <style>   (never edited)
     2. tokens.css                                   (retints 1 by cascade)
     3. components.css                               (this file)
     4. shell.css                                    (site chrome)

   Two kinds of rule live here. The first names an od- class and is the
   design system proper. The second reaches an unprefixed class that the
   vendored renderers already emit, and exists to bring that markup onto
   the system without editing the renderer. Those are grouped at the end
   under ADOPTED SELECTORS and each one says which service emits it.

   No script is required by anything in this file. The sibling Risk Desk
   service serves under script-src 'none'. */

/* ------------------------------------------------------------------ */
/* Page shell                                                          */
/* ------------------------------------------------------------------ */

.od-page {
  background: var(--od-ground);
  color: var(--od-text);
  font-family: var(--od-font-sans);
  font-size: var(--od-font-size-4);
  line-height: var(--od-line-body);
}

.od-measure {
  max-width: var(--od-measure);
}

/* ------------------------------------------------------------------ */
/* Status and provenance strip                                         */
/*                                                                     */
/* One row that answers, without the reader hunting: what state is the  */
/* data in, how old is it, and where did it come from. It sits directly */
/* under the page title and above everything else, and it wraps rather  */
/* than truncating, because a provenance line that is cut off is worse  */
/* than one that takes a second line.                                   */
/* ------------------------------------------------------------------ */

.od-statusbar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--od-space-2) var(--od-space-5);
  padding: var(--od-space-3) var(--od-space-5);
  margin: 0 0 var(--od-space-5);
  border: var(--od-border-hair) solid var(--od-line);
  border-radius: var(--od-radius-2);
  background: var(--od-surface);
  font-size: var(--od-font-size-6);
  color: var(--od-text-muted);
}

.od-statusbar .od-status-item {
  display: inline-flex;
  align-items: baseline;
  gap: var(--od-space-2);
  white-space: nowrap;
}

/* The provenance item is the one thing allowed to wrap and to run long:
   a source name, a capture time or a digest must never be abbreviated. */
.od-statusbar .od-status-item.od-status-provenance {
  white-space: normal;
  overflow-wrap: anywhere;
  flex: 1 1 24ch;
}

.od-status-key {
  font-size: var(--od-font-size-7);
  text-transform: uppercase;
  letter-spacing: var(--od-tracking-label);
  color: var(--od-text-faint);
}

.od-status-value {
  color: var(--od-text);
  font-variant-numeric: tabular-nums;
}

.od-status-value.od-mono {
  font-family: var(--od-font-mono);
}

/* ------------------------------------------------------------------ */
/* State flag                                                          */
/*                                                                     */
/* A chip that names one fact about the data. The word is the primary   */
/* channel and is never omitted; colour is the second channel and the   */
/* border style is the third, so the chip still reads in monochrome.    */
/* ------------------------------------------------------------------ */

.od-flag {
  display: inline-block;
  padding: var(--od-space-1) var(--od-space-4);
  border: var(--od-border-hair) solid var(--od-line-strong);
  border-radius: var(--od-radius-pill);
  font-size: var(--od-font-size-7);
  font-weight: var(--od-weight-strong);
  text-transform: uppercase;
  letter-spacing: var(--od-tracking-label);
  color: var(--od-text-muted);
  white-space: nowrap;
}

.od-flag.od-flag-ok {
  color: var(--od-gain);
  border-color: var(--od-gain);
}

.od-flag.od-flag-stale {
  color: var(--od-stale);
  border-color: var(--od-stale);
  border-style: dashed;
}

.od-flag.od-flag-degraded {
  color: var(--od-degraded);
  border-color: var(--od-degraded);
  border-width: var(--od-border-rule);
}

.od-flag.od-flag-synthetic {
  color: var(--od-synthetic);
  border-color: var(--od-synthetic);
  border-style: dotted;
}

/* ------------------------------------------------------------------ */
/* Banner                                                              */
/*                                                                     */
/* The full-width statement of what the reader is looking at. Its       */
/* colour is carried by the state, not by the brand: a synthetic page   */
/* is violet, a stale page amber, a degraded page orange and hatched.   */
/* ------------------------------------------------------------------ */

.od-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--od-space-2) var(--od-space-5);
  padding: var(--od-space-4) var(--od-space-5);
  margin: var(--od-space-5) 0 var(--od-space-6);
  border: var(--od-border-hair) solid var(--od-line);
  border-left: var(--od-border-flag) solid var(--od-accent);
  border-radius: var(--od-radius-2);
  background: var(--od-surface);
  color: var(--od-text);
  font-size: var(--od-font-size-5);
}

/* The base variant is deliberate, not a fallback. It means: read this
   before the figures, but no claim is being made that the data is
   synthetic, stale or degraded. The private user-data view uses it,
   because that data is the reader's own and unverified is not a state
   the service is entitled to assert about it. */

.od-banner strong {
  font-weight: var(--od-weight-strong);
  text-transform: uppercase;
  letter-spacing: var(--od-tracking-label);
}

.od-banner .detail {
  flex-basis: 100%;
  font-size: var(--od-font-size-6);
  color: var(--od-text-muted);
  overflow-wrap: anywhere;
}

.od-banner .detail strong {
  color: var(--od-text);
  text-transform: none;
  letter-spacing: var(--od-tracking-none);
}

.od-banner.od-banner-synthetic {
  border-left-color: var(--od-synthetic);
  background: var(--od-synthetic-ground);
}

.od-banner.od-banner-synthetic strong {
  color: var(--od-synthetic);
}

.od-banner.od-banner-stale {
  border-left-color: var(--od-stale);
  border-left-style: dashed;
  background: var(--od-stale-ground);
}

.od-banner.od-banner-stale strong {
  color: var(--od-stale);
}

.od-banner.od-banner-degraded {
  border-left-color: var(--od-degraded);
  border-left-width: var(--od-border-flag);
  background-color: var(--od-degraded-ground);
  background-image: var(--od-degraded-hatch);
}

.od-banner.od-banner-degraded strong {
  color: var(--od-degraded);
}

/* A provenance strip inside a banner is part of the banner, not a second
   panel stacked on it: it drops its own border and ground and keeps only
   the rule that separates it from the warning above. */
.od-banner .od-statusbar {
  flex-basis: 100%;
  margin: var(--od-space-2) 0 0;
  padding: var(--od-space-3) 0 0;
  border: 0;
  border-top: var(--od-border-hair) solid var(--od-line);
  border-radius: 0;
  background: none;
}

.od-banner button {
  font: inherit;
  font-size: var(--od-font-size-5);
  font-weight: var(--od-weight-medium);
  cursor: pointer;
  padding: var(--od-space-2) var(--od-space-5);
  border: var(--od-border-hair) solid var(--od-line-strong);
  border-radius: var(--od-radius-2);
  background: var(--od-surface-raised);
  color: var(--od-text);
}

.od-banner button:hover,
.od-banner button:focus-visible {
  border-color: var(--od-accent);
  color: var(--od-accent);
}

/* ------------------------------------------------------------------ */
/* Sign convention callout                                             */
/*                                                                     */
/* Its own component because a sign convention is not a caveat. It is   */
/* the key to reading every number on the page, and on a trading floor  */
/* a misread sign is the expensive mistake. It is never collapsed, and  */
/* it renders before the figures it governs.                            */
/* ------------------------------------------------------------------ */

.od-convention {
  margin: 0 0 var(--od-space-5);
  padding: var(--od-space-4) var(--od-space-5);
  border: var(--od-border-hair) solid var(--od-line);
  border-left: var(--od-border-flag) solid var(--od-accent);
  border-radius: var(--od-radius-2);
  background: var(--od-surface);
  color: var(--od-text);
  font-size: var(--od-font-size-5);
  line-height: var(--od-line-loose);
  max-width: var(--od-measure);
}

.od-convention > strong {
  display: block;
  margin-bottom: var(--od-space-2);
  font-size: var(--od-font-size-7);
  font-weight: var(--od-weight-strong);
  text-transform: uppercase;
  letter-spacing: var(--od-tracking-label);
  color: var(--od-accent);
}

.od-convention dl {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--od-space-2) var(--od-space-5);
  margin: var(--od-space-3) 0 0;
}

.od-convention dt {
  font-variant-numeric: tabular-nums;
  font-weight: var(--od-weight-medium);
  color: var(--od-text-muted);
  white-space: nowrap;
}

.od-convention dd {
  margin: 0;
}

/* ------------------------------------------------------------------ */
/* Card                                                                */
/*                                                                     */
/* Flat. A hairline and a surface step, no shadow. A shadow on a data   */
/* card costs vertical room and buys nothing a border does not.         */
/* ------------------------------------------------------------------ */

.od-card {
  padding: var(--od-space-5) var(--od-space-6) var(--od-space-4);
  margin: 0 0 var(--od-space-5);
  border: var(--od-border-hair) solid var(--od-line);
  border-radius: var(--od-radius-3);
  background: var(--od-surface);
  box-shadow: var(--od-elevation-0);
}

.od-card > h3,
.od-card-title {
  margin: 0 0 var(--od-space-1);
  font-size: var(--od-font-size-3);
  font-weight: var(--od-weight-medium);
  letter-spacing: var(--od-tracking-tight);
  color: var(--od-text);
}

.od-card-hint {
  margin: 0 0 var(--od-space-5);
  max-width: var(--od-measure);
  font-size: var(--od-font-size-6);
  color: var(--od-text-muted);
}

/* ------------------------------------------------------------------ */
/* Dense data table                                                    */
/*                                                                     */
/* The measured shape of this domain: the widest table served today is  */
/* 20 columns by 14 rows. So the row identity column stays put while    */
/* the numbers scroll under the reader, the column head stays put while */
/* the rows scroll, and every figure is a tabular numeral so digits in  */
/* a column line up on the decimal point.                               */
/* ------------------------------------------------------------------ */

.od-tablewrap {
  overflow: auto;
  max-height: 60vh;
  border: var(--od-border-hair) solid var(--od-line);
  border-radius: var(--od-radius-2);
  background: var(--od-surface);
}

.od-table {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  font-size: var(--od-font-size-5);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.od-table th,
.od-table td {
  padding: var(--od-cell-pad-y) var(--od-cell-pad-x);
  border-bottom: var(--od-border-hair) solid var(--od-line);
  text-align: right;
  white-space: nowrap;
}

/* The first column names the row. It is a label, so it reads left, and
   it holds its position while the figures scroll sideways. */
.od-table th:first-child,
.od-table td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  text-align: left;
  background: var(--od-surface);
  border-right: var(--od-border-hair) solid var(--od-line);
}

.od-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  font-size: var(--od-font-size-7);
  font-weight: var(--od-weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--od-tracking-label);
  color: var(--od-text-muted);
  background: var(--od-surface-raised);
  border-bottom: var(--od-border-rule) solid var(--od-line-strong);
  box-shadow: var(--od-elevation-1);
}

.od-table thead th:first-child {
  z-index: 3;
  background: var(--od-surface-raised);
}

.od-table tbody tr:last-child th,
.od-table tbody tr:last-child td {
  border-bottom: 0;
}

/* A unit belongs with its column head, not repeated in every cell. */
.od-table thead th .od-unit {
  display: block;
  font-size: var(--od-font-size-7);
  font-weight: var(--od-weight-normal);
  text-transform: none;
  letter-spacing: var(--od-tracking-none);
  color: var(--od-text-faint);
}

/* ------------------------------------------------------------------ */
/* Signed figure                                                       */
/*                                                                     */
/* Three channels, so no single failure hides the direction:            */
/*   1. the sign character, always rendered, never dropped on a gain    */
/*   2. the hue                                                        */
/*   3. a leading glyph, which survives monochrome and colour blindness */
/* ------------------------------------------------------------------ */

.od-num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.od-num-gain {
  color: var(--od-gain);
}

.od-num-loss {
  color: var(--od-loss);
}

.od-num-gain::before {
  content: var(--od-gain-glyph) " ";
  font-size: 0.8em;
}

.od-num-loss::before {
  content: var(--od-loss-glyph) " ";
  font-size: 0.8em;
}

/* A row whose direction matters carries it on its edge as well. The edge
   differs in border STYLE, not only in colour: solid for a gain, dashed
   for a loss. Two rows of opposite sign stay distinguishable on a
   monochrome screen and in a printout. */
.od-table tr.od-row-gain td:first-child {
  border-left: var(--od-border-flag) var(--od-gain-border-style)
               var(--od-gain);
  background: var(--od-gain-ground);
}

.od-table tr.od-row-loss td:first-child {
  border-left: var(--od-border-flag) var(--od-loss-border-style)
               var(--od-loss);
  background: var(--od-loss-ground);
}

/* ------------------------------------------------------------------ */
/* Chart frame                                                         */
/*                                                                     */
/* The existing charts are kept exactly as they are: the Option Desk    */
/* dashboard draws with its own bundled script into a div, and the Risk */
/* Desk report emits inline SVG from the standard library. This frames  */
/* both and rewrites neither. The frame owns the title, the units, the  */
/* provenance note and the border; the chart owns the pixels inside.    */
/* ------------------------------------------------------------------ */

.od-chartframe {
  margin: 0 0 var(--od-space-5);
  border: var(--od-border-hair) solid var(--od-line);
  border-radius: var(--od-radius-3);
  background: var(--od-surface);
}

.od-chartframe-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--od-space-2) var(--od-space-5);
  padding: var(--od-space-4) var(--od-space-5);
  border-bottom: var(--od-border-hair) solid var(--od-line);
}

.od-chartframe-title {
  margin: 0;
  font-size: var(--od-font-size-3);
  font-weight: var(--od-weight-medium);
  letter-spacing: var(--od-tracking-tight);
  color: var(--od-text);
}

.od-chartframe-unit {
  font-size: var(--od-font-size-7);
  text-transform: uppercase;
  letter-spacing: var(--od-tracking-label);
  color: var(--od-text-faint);
}

.od-chartframe-body {
  padding: var(--od-space-4) var(--od-space-5);
}

/* An inline SVG chart fills the frame and keeps its aspect ratio. */
.od-chartframe-body > svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
}

.od-chartframe-note {
  margin: 0;
  padding: var(--od-space-3) var(--od-space-5) var(--od-space-4);
  border-top: var(--od-border-hair) solid var(--od-line);
  font-size: var(--od-font-size-6);
  color: var(--od-text-muted);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------------ */
/* Focus                                                               */
/*                                                                     */
/* The brand signal earns its keep here. A focus ring must be the most  */
/* visible thing on the page and must not be confusable with any data   */
/* state, which is exactly why the signal colour is reserved for it.    */
/* ------------------------------------------------------------------ */

:where(a, button, input, select, summary, [tabindex]):focus-visible {
  outline: var(--od-border-rule) solid var(--od-signal);
  outline-offset: var(--od-space-1);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ================================================================== */
/* ADOPTED SELECTORS                                                   */
/*                                                                     */
/* Unprefixed classes emitted by a vendored renderer that this repo     */
/* does not edit. Each rule below brings that markup onto the tokens    */
/* without touching the renderer, and names the service that emits it.  */
/* ================================================================== */

/* Option Desk renderer: the selected pill and the pressed picker button
   both set an explicit white foreground on the accent. Measured against
   the shipped accent that pair is 2.58:1, below the 4.5:1 threshold, so
   the label on a selected control is the least legible text on the page.
   These two rules put the accent's own ink on it instead. */
.pill.on,
.picker button[aria-pressed="true"] {
  color: var(--od-accent-ink);
}

/* Option Desk renderer: tiles, panels and tables. */
.tile .v,
.tile .s,
table {
  font-variant-numeric: tabular-nums;
}

.tile .v {
  font-size: var(--od-font-size-2);
  line-height: var(--od-line-tight);
  letter-spacing: var(--od-tracking-figure);
}

.tile .k,
.selector .lbl {
  letter-spacing: var(--od-tracking-label);
}

/* Option Desk renderer: its own warn block. The renderer emits this
   element on exactly two paths and both are degraded conditions: a
   sampler that did not converge (page.py:698) and an explicitly degraded
   snapshot (page.py:1382). So it is coloured from the degraded tokens and
   carries the hatch, rather than inheriting the general attention amber.
   The :not() excludes the site chrome's own banner, which is the same
   class but states a different fact and carries its own variant. */
.warn:not(.od-banner) {
  border-left-color: var(--od-degraded);
  color: var(--od-text);
  background-color: var(--od-degraded-ground);
  background-image: var(--od-degraded-hatch);
}

.warn:not(.od-banner) strong {
  color: var(--od-degraded);
}

/* Option Desk renderer: the status dot.

   The modifier class is named "stale" but the renderer emits it only when
   the payload is DEGRADED, and prints the word "degraded" next to it:

       stale=" stale" if degraded else ""        page.py:1369
       status="degraded" if degraded else "clean"  page.py:1370

   So it is coloured from the degraded tokens. Colouring it by its class
   name would paint a degraded page in the amber that means stale, one
   character away from the word "degraded" on screen. The renderer has no
   separate stale state in this header at all.

   Colour alone carried the state, so the dot gains a shape channel: a
   clean feed is a circle, a degraded one a square. */
.dot {
  background: var(--od-gain);
  border-radius: var(--od-radius-dot);
}

.dot.stale {
  background: var(--od-degraded);
  border-radius: 0;
}

/* Risk Desk renderer: the report's own state flag and sign convention.
   Present here because this is the shared layer both services adopt;
   Option Desk does not currently emit these two classes. */
.flag.ok {
  background: var(--od-gain-ground);
  color: var(--od-gain);
}

.flag.degraded {
  background: var(--od-degraded-ground);
  color: var(--od-degraded);
}

.convention {
  border-left-color: var(--od-accent);
  background: var(--od-surface);
  color: var(--od-text);
}

/* Risk Desk renderer: inline SVG charts, styled entirely by class, with
   no presentational fill or stroke attribute in the markup. That is what
   makes them retintable from here without rewriting a chart function. */
.chart .axis {
  stroke: var(--od-axis);
}

.chart .bar.loss,
.chart .bar.short,
.chart .mark.var {
  fill: var(--od-loss);
  stroke: var(--od-loss);
}

.chart .bar.gain,
.chart .bar.long {
  fill: var(--od-gain);
}

.chart .bar.flat {
  fill: var(--od-text-faint);
}

.chart .bar.share,
.chart .bin {
  fill: var(--od-accent);
}

.chart .mark.es {
  stroke: var(--od-synthetic);
}

.chart .label,
.chart .value,
.chart .tick,
.chart .marklabel {
  fill: var(--od-text);
  font-family: var(--od-font-sans);
  font-size: var(--od-font-size-6);
}

.chart .tick {
  fill: var(--od-text-muted);
}
