/* DashNomics -- the 1920x1080 screen (README §7.1, STYLEGUIDE.md).
 *
 * The colour rule this file exists to enforce: colour says WHERE, never HOW
 * GOOD. Each group owns a constant hue; nothing changes colour with the data.
 * There is no red/green axis anywhere, and the direction glyph inherits the
 * ordinary text colour whichever way it points -- rising unemployment and
 * rising GDP render identically, because the dashboard does not say which is
 * welcome.
 *
 * Palette: Foraya, hard contrast, muted accents -- the defaults of
 * ~/dotfiles/theming/foraya/generate.py, taken from its palette.yml. Foraya
 * is a dark scheme and the stream is dark, so there is no light theme.
 * Token names are semantic so a value can move without a meaning moving with
 * it; STYLEGUIDE.md maps each one back to its Foraya source.
 */

@font-face {
  font-family: "JetBrains Mono";
  src: url("fonts/JetBrainsMono.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: block;
}

:root {
  color-scheme: dark;

  /* Surfaces: Foraya's background ladder (base / lift / panel / gutter) at
   * its exact lightness, with the hue taken out. Foraya's own values sit at
   * hue 197-205 (cyan-teal) with 10-13% saturation; filling nine-tenths of
   * the frame, that read as green. These keep the depth and every contrast
   * figure, at 6% saturation leaning cool-blue (hue 220), never cyan. */
  --bg: #2a2c30;
  --surface: #323438;
  --stripe: #2c2e32;        /* every other row: a step darker than --surface */
  --grid: #3a3d42;
  --border: #44474c;

  /* Text: Foraya's warm track -- "warm text on a cool, deep backdrop", as
   * the theme describes itself. Gruvbox cream and warm greys, never
   * Everforest's green-greys, which on a screen that is mostly text turned
   * the whole dashboard green (2026-09-23). Contrast on --surface (WCAG):
   * 8.8, 7.1 and 4.3. Faint is for units, periods and titles, never a value. */
  --text: #ebdbb2;          /* foreground.vivid (Gruvbox light1) */
  --text-muted: #d3c6aa;    /* foreground.muted */
  --text-faint: #a89984;    /* neutral.vivid.bright (Gruvbox light4) */

  /* The series line: a warm neutral, so fifty sparklines read as texture,
   * not as a colour. One tone, no sentiment encoding. */
  --line: #a89984;          /* neutral.vivid.bright */
  --line-expired: #7c6f64;  /* neutral.vivid.dim */

  /* Absence. Staleness is carried by dimming, not by a hue, so every hue
   * is free to name a part of the economy. */
  --expired: #928374;       /* neutral.vivid.mid */
  --absent: #928374;

  /* Group hues: categorical, from Foraya's accents, each 4.5:1 or better on
   * --surface. A hue names the part of the economy a row belongs to; it
   * never says whether a move is good or bad. Neighbouring groups never
   * share a family. See STYLEGUIDE.md §3. */
  --hue-rates: #73b8ff;      /* special.vcs.modified -- blue */
  --hue-regime: #d3869b;     /* accents.purple.vivid -- mauve */
  --hue-output: #39bae6;     /* special.info -- cyan (Ayu) */
  --hue-inflation: #e69875;  /* accents.orange.muted -- orange */
  --hue-spending: #d699b6;   /* accents.purple.muted -- orchid */
  --hue-labor: #e67e80;      /* accents.red.muted -- coral */
  --hue-energy: #fabd2f;     /* accents.yellow.vivid -- amber */

  /* The brand accent: special.gold. The logo only -- never on
   * data, where a colour would read as a judgement. */
  --brand: #e6b450;

  --row-min: 40px;

  --sans: system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.4 var(--sans);
  /* JetBrains Mono's slashed zero and tabular figures keep columns steady. */
  font-feature-settings: "zero" 1;
}

/* At 1920x1080 this is a fixed frame: everything fits and nothing scrolls.
 * Smaller windows get the same content reflowed into fewer columns, and
 * scroll vertically rather than clip. Overflow is deliberately never hidden
 * (the ticker's viewport aside): scripts/check-layout.mjs is what catches a
 * layout that does not fit (README §7.1). */
.frame {
  min-height: 100vh;
  padding: 16px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(56px, auto) 1fr 52px auto;
  gap: 12px 16px;
}

/* The User Web App shows the sources column beside the indicators; below
 * this width it would squeeze them to two columns, so it moves under them. */
@media (min-width: 1700px) {
  .frame.with-sources { grid-template-columns: minmax(0, 1fr) 376px; }
}

.topbar {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 32px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 6px;
}

/* "Latest from the sources": one official headline, calm, between the
 * wordmark and the clocks. A title too long for the line ends in an
 * ellipsis rather than wrapping the header. */
.headline {
  /* Takes only the room between the wordmark and the clocks: it starts from
   * zero and grows, never pushing the clocks onto a second line. */
  flex: 1 1 0;
  min-width: 0;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0 12px;
  padding-left: 16px;
  border-left: 1px solid var(--border);
}

.headline.entering { animation: spot-enter 0.6s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .headline.entering { animation: none; }
}

.headline-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--text-faint);
}

.headline-top {
  display: flex;
  align-items: baseline;
  gap: 12px;
  white-space: nowrap;
}

.headline-meta {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-muted);
}

.headline-title {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 15px;
  color: var(--text);
}

/* The header stays one line wherever the card layout does. */
@media (min-width: 1500px) {
  .topbar { flex-wrap: nowrap; }
  .clocks { flex: none; }
}

/* Narrower windows: the headline takes its own line under the wordmark. */
@media (max-width: 1499px) {
  .headline { order: 3; flex-basis: 100%; max-width: none; margin: 4px 0 0; padding-left: 0; border-left: 0; }
  .headline-top { flex-wrap: wrap; row-gap: 2px; }
  .headline-title { white-space: normal; }
}

.clocks {
  display: flex;
  gap: 40px;
}

.clock-city {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--text-faint);
}

.clock-time {
  font-family: var(--mono);
  font-size: 17px;
  font-variant-numeric: tabular-nums;
  margin-right: 10px;
}

.clock-status {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
}

/* City above; then "9:41 AM  Open · closes 4:00 PM" on one line. */
.clock {
  display: grid;
  grid-template-columns: auto auto;
  align-items: baseline;
  row-gap: 1px;
}

.clock-city { grid-column: 1 / -1; }

/* Open or not is weight, never hue: this bar is information, not an alert. */
.clock-state { color: var(--text-muted); }
.clock[data-state="OPEN"] .clock-state { color: var(--text); }
.clock[data-state="CALENDAR_EXPIRED"] .clock-state {
  font-family: var(--mono);
  font-style: italic;
  color: var(--absent);
}

.clock-next::before {
  content: "\00B7";
  margin: 0 6px;
  color: var(--text-faint);
}

/* Two rows of two when four clocks no longer fit beside the title. */
@media (max-width: 1099px) {
  .clocks {
    display: grid;
    grid-template-columns: repeat(2, auto);
    gap: 8px 32px;
  }
}

/* The wordmark: the mark, then "Dash" in full text colour and "Nomics" a
 * step quieter -- one word, two weights, no gradient, no gold on the type. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

/* The logo sits at cap-to-descender height beside the wordmark. */
.brand-mark {
  width: 22px;
  height: 22px;
  display: block;
}

.wordmark {
  font-size: 19px;
  letter-spacing: 0.01em;
  color: var(--text);
  font-weight: 650;
}

.wordmark-light {
  font-weight: 400;
  color: var(--text-muted);
}

.brand-tagline {
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding-left: 12px;
  margin-left: 2px;
  border-left: 1px solid var(--border);
}

/* The board (README §7.6): two columns of groups, the spotlight card, two
 * more columns. At 1920x1080 the columns stretch to the frame's full height
 * and their rows grow to fill it -- no space is left over. Narrower windows
 * put the card on top and the columns below it, at their natural height. */
.board {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)) 436px repeat(2, minmax(0, 1fr));
  gap: 12px;
  min-height: 0;
}

.board-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
  min-height: 0;
}

/* With the card in the middle the frame is fixed: the board's row may not
 * grow past it, so a card with more to say than fits shows up as a card
 * overflow (and fails check:layout), never as a frame that pushes the ticker
 * off the screen. */
@media (min-width: 1500px) {
  /* A definite height: with only a minimum, a flexible row sizes to its
   * content and the frame grows anyway. */
  .frame { height: 100vh; grid-template-rows: minmax(56px, auto) minmax(0, 1fr) 52px auto; }
}

@media (max-width: 1499px) {
  .board { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .spotlight { grid-column: 1 / -1; order: -1; min-height: 640px; }
}

@media (max-width: 759px) {
  .board { grid-template-columns: minmax(0, 1fr); }
}

.group[data-group="rates"]     { --accent: var(--hue-rates); }
.group[data-group="regime"]    { --accent: var(--hue-regime); }
.group[data-group="output"]    { --accent: var(--hue-output); }
.group[data-group="inflation"] { --accent: var(--hue-inflation); }
.group[data-group="spending"]  { --accent: var(--hue-spending); }
.group[data-group="labor"]     { --accent: var(--hue-labor); }
.group[data-group="energy"]    { --accent: var(--hue-energy); }

.group {
  /* flex-grow is the group's row count, set by the renderer, so a panel's
   * share of its column is its share of the rows and every row in the column
   * comes out the same height. */
  flex-basis: 0;
  display: flex;
  flex-direction: column;
  /* Rows adapt to the width of their group, not of the window. */
  container-type: inline-size;
}

.group,
.sources {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 12px 2px;
}

/* After the shared panel border, so the group's hue wins on top. */
.group[data-group] { border-top: 2px solid var(--accent); }

.group-label {
  margin: 0;
  height: 28px;
  line-height: 28px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--accent, var(--text-faint));
}

/* label · figure (value and unit) · change · sparkline -- the period joins
 * when the group is wide enough (the spotlight card always shows it). */
.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 96px 84px 28px;
  column-gap: 4px;
  align-items: center;
  /* At least --row-min, never shorter than its content (a label wrapping to
   * three lines), and growing to share out the column's height. */
  flex: 1 1 var(--row-min);
  border-top: 1px solid var(--grid);
  /* Rows run edge to edge across the panel, like a spreadsheet's, so the
   * stripes below read as whole rows. The padding gives back exactly what
   * the margin takes, so the cells keep their widths. */
  margin: 0 -12px;
  padding: 0 12px;
}

/* Alternate rows a step darker: current, darker, current, darker. A banding
 * aid for following a row across, not a highlight -- it never changes with
 * the data. nth-of-type counts only the rows, not the group title. */
.row:nth-of-type(even) { background: var(--stripe); }

.row-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.2;
  color: var(--text-muted);
  /* Last resort only: a label must never run under the value beside it. */
  overflow-wrap: anywhere;
}

.row-figure {
  display: flex;
  justify-content: flex-end;
  align-items: baseline;
  gap: 4px;
  white-space: nowrap;
}

.row-value {
  font-family: var(--mono);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.row-unit {
  font-size: 10.5px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row-change {
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-family: var(--mono);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Direction is orientation and neutral weight -- never hue. Every branch
 * below resolves to the same colour on purpose: the selectors exist so the
 * rule is visible and any future divergence is a deliberate edit, not a
 * default that crept in. */
.row-direction { color: var(--text); }
.row-direction[data-direction="UP"]   { color: var(--text); }
.row-direction[data-direction="DOWN"] { color: var(--text); }
.row-direction[data-direction="FLAT"] { color: var(--text-muted); }
.row-direction[data-direction="NONE"] { color: var(--text-faint); }

.row-delta { color: var(--text); }

.row-absent-inline {
  font-style: italic;
  font-size: 10px;
  color: var(--absent);
}

.row-change-period {
  font-size: 10px;
  color: var(--text-faint);
}

/* An absent value is typographically distinct from a number, so it can never
 * be mistaken for one at a glance. It spans the value and unit cells. */
.row-absent {
  grid-column: span 2;
  font-family: var(--mono);
  font-size: 11px;
  font-style: italic;
  color: var(--absent);
  text-align: right;
  white-space: nowrap;
}

.row-spark-cell {
  display: block;
  height: 24px;
}

.row-spark {
  width: 100%;
  height: 24px;
  display: block;
  overflow: visible;
}

.chart-line {
  fill: none;
  stroke: var(--accent, var(--line));
  stroke-width: 1.25;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* An expired series is drawn withheld: present enough to show the shape that
 * was last known, muted enough that it is not read as current. */
.chart-line-expired {
  stroke: var(--line-expired);
  stroke-dasharray: 3 3;
}

.chart-last {
  fill: var(--accent, var(--line));
}

.row[data-freshness="STALE_EXPIRED"] .chart-last {
  fill: var(--line-expired);
}

.row-period {
  font-family: var(--mono);
  font-size: 10px;
  text-align: right;
  white-space: nowrap;
  color: var(--text-faint);
}

/* Stale -- past its refresh window without a fresh confirmation -- dims the
 * whole row and italicises its date: the reading is still shown, visibly
 * not current, without spending a hue that a group already owns. */
.row[data-freshness="STALE"] { opacity: 0.55; }
.row-period[data-freshness="STALE"] { font-style: italic; }
.row-period[data-freshness="STALE_EXPIRED"] { color: var(--expired); }

/* The row template follows the group's own width. At 1920 the board's
 * columns are about 320px of content: label, figure, change and a small
 * sparkline, with the period left to the spotlight card. Wider groups (the
 * two-column layout of a narrower window) get the period and a longer
 * sparkline back; the narrowest shed the sparkline, then the change's
 * period label. The value and its direction always stay. */
.row-period { display: none; }

@container (width >= 470px) {
  .row { grid-template-columns: minmax(0, 1fr) 110px 92px 64px 44px; column-gap: 6px; }
  .row-period { display: block; }
}

@container (width >= 560px) {
  .row { grid-template-columns: minmax(0, 1fr) 136px 110px 116px 52px; }
  .row-value { font-size: 17px; }
}

@container (width < 290px) {
  .row { grid-template-columns: minmax(0, 1fr) 92px 64px; }
  .row-spark-cell { display: none; }
  .row-change-period { display: none; }
}

/* The row the spotlight card is describing: tinted with its group's hue,
 * with a bar in the hue at its left edge. It is the only highlight on the
 * board, and it moves only when the card does. Declared after the stripe so
 * it wins on a darker row too. */
.row.is-spot {
  background: color-mix(in srgb, var(--accent) 16%, var(--surface));
  box-shadow: inset 3px 0 0 var(--accent);
}

.row.is-spot .row-label { color: var(--text); }

/* --- The spotlight card (README §7.6) ----------------------------------- */

.spotlight {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 2px solid var(--accent, var(--border));
  border-radius: 6px;
  overflow: hidden;
}

.spotlight[data-group="rates"]     { --accent: var(--hue-rates); }
.spotlight[data-group="regime"]    { --accent: var(--hue-regime); }
.spotlight[data-group="output"]    { --accent: var(--hue-output); }
.spotlight[data-group="inflation"] { --accent: var(--hue-inflation); }
.spotlight[data-group="spending"]  { --accent: var(--hue-spending); }
.spotlight[data-group="labor"]     { --accent: var(--hue-labor); }
.spotlight[data-group="energy"]    { --accent: var(--hue-energy); }

.spot {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px 20px 14px;
}

/* A new indicator arrives with one soft fade, never a slide or a flash. */
.spot.entering { animation: spot-enter 0.6s ease-out; }

@keyframes spot-enter {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .spot.entering { animation: none; }
}

.spot-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.spot-group {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--accent, var(--text-faint));
}

.spot-count {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-faint);
}

.spot-title {
  margin: 0;
  font-size: 26px;
  font-weight: 650;
  letter-spacing: 0.01em;
  line-height: 1.15;
  color: var(--text);
}

.spot-source {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-faint);
}

.spot-headline {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 4px;
}

.spot-value {
  font-family: var(--mono);
  font-size: 46px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.spot-unit {
  font-size: 17px;
  color: var(--text-muted);
}

.spot-moves {
  display: flex;
  gap: 22px;
  min-height: 18px;
  font-family: var(--mono);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* A fixed, readable chart height. The words -- figures, context, what the
 * indicator is -- get the rest of the card. */
.spot-chart {
  flex: none;
  height: 160px;
  margin: 4px 0;
}

.spot-chart .row-spark {
  width: 100%;
  height: 100%;
}

.spot-chart .chart-line { stroke-width: 2; }

.chart-grid line {
  stroke: var(--grid);
  stroke-width: 1;
}

.chart-label {
  fill: var(--text-faint);
  font-family: var(--mono);
  font-size: 10px;
}

.spot-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px 14px;
  padding: 10px 0;
  border-top: 1px solid var(--grid);
  border-bottom: 1px solid var(--grid);
}

.spot-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.spot-stat-term {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.spot-stat-value {
  font-family: var(--mono);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.spot-stat-when {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-faint);
}

/* Context: facts to judge by -- where the reading stands, how long it has
 * moved one way, the distance to a published reference level. Never a
 * verdict. */
.spot-context {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.spot-context li {
  position: relative;
  padding-left: 16px;
  font-size: 14px;
  line-height: 1.4;
  color: var(--text);
}

.spot-context li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.55em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent, var(--text-faint));
}

/* Next release and data status: the card's provenance, in two cells. */
.spot-status {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 14px;
  padding-top: 10px;
  border-top: 1px solid var(--grid);
}

.spot-status .spot-stat[data-freshness="STALE"] .spot-stat-value { font-style: italic; color: var(--text-muted); }
.spot-status .spot-stat[data-freshness="STALE_EXPIRED"] .spot-stat-value { color: var(--expired); }

/* The last six published periods, oldest to newest. */
.spot-recent {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}

.spot-recent-title {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.spot-recent-cells {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}

.spot-recent-cell {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "when move" "value value";
  align-items: baseline;
  gap: 1px 6px;
  min-width: 0;
  padding: 6px 7px;
  border-radius: 4px;
  background: var(--stripe);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The newest reading, in the group's hue: the one the headline describes. */
.spot-recent-cell:last-child { box-shadow: inset 0 -2px 0 var(--accent, var(--text-faint)); }

.spot-recent-when { grid-area: when; font-size: 10px; color: var(--text-faint); }
.spot-recent-move { grid-area: move; justify-self: end; font-size: 10.5px; color: var(--text-muted); }
.spot-recent-value { grid-area: value; font-size: 14px; color: var(--text); overflow: hidden; text-overflow: ellipsis; }
.spot-recent-cell .row-absent { grid-area: value; text-align: left; }

.spot-about {
  margin: 0;
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid var(--grid);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-muted);
}

.chart-reference {
  stroke: var(--text-faint);
  stroke-width: 1;
  stroke-dasharray: 4 4;
}

.chart-reference-label {
  fill: var(--text-faint);
  font-size: 10.5px;
}

/* The time left on this indicator. It fills at the ticker's pace -- the
 * page sets the duration from the bar's width and the ticker's px/s -- so
 * the two motions on screen always move at one speed. */
.spot-progress {
  flex: none;
  height: 3px;
  background: var(--grid);
}

.spot-progress-fill {
  height: 100%;
  background: var(--accent, var(--text-faint));
  transform-origin: left center;
  transform: scaleX(0);
}

.spot-progress-fill.running {
  animation: spot-fill var(--spot-duration, 15s) linear forwards;
}

@keyframes spot-fill {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

.sources {
  align-self: start;
}

/* "From the sources": calm by construction (README §7.4). Muted text, no
 * bold, no hue, no badges, no ticker; a title is clamped to two lines so
 * the column never grows or scrolls. */
.source-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.source-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 9px 0 10px;
  border-top: 1px solid var(--grid);
}

.source-meta {
  font-size: 10.5px;
  letter-spacing: 0.03em;
  color: var(--text-faint);
}

.source-title {
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--text-muted);
  text-decoration: none;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

a.source-title:hover { color: var(--text); }

/* A new headline arrives by fading in once, over about a second. */
.source-item.arriving { animation: arrive 1.2s ease-out; }

@keyframes arrive {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .source-item.arriving { animation: none; }
}

.sources .row-absent {
  display: block;
  text-align: left;
  padding: 8px 0 10px;
  border-top: 1px solid var(--grid);
}

/* The calendar ticker: one slow, continuous line. Impact is carried by
 * brightness only -- no red/orange/yellow, no icons -- and nothing on it
 * counts down. Edges fade so items drift in and out rather than pop. */
.ticker {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.ticker-label {
  flex: none;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--text-faint);
}

.ticker-viewport {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, black 32px, black calc(100% - 32px), transparent);
  mask-image: linear-gradient(to right, transparent, black 32px, black calc(100% - 32px), transparent);
}

.ticker-track {
  display: inline-flex;
  white-space: nowrap;
  will-change: transform;
  animation: ticker-scroll var(--ticker-duration, 120s) linear infinite;
}

@keyframes ticker-scroll {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .ticker-track { animation: none; }
}

/* Sized to be read on a stream at 1080p: the strip is 52px tall. */
.tick {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font-size: 17px;
  color: var(--text-muted);
}

.tick::after {
  content: "\00B7";
  margin: 0 30px;
  color: var(--text-faint);
}

.tick-when,
.tick-figure {
  font-family: var(--mono);
  font-size: 14px;
  color: var(--text-faint);
}

.tick-country {
  font-family: var(--mono);
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

/* The currencies behind the four clocks, plus the euro, get hues of their
 * own so the strip reads at a glance; the rest stay neutral. */
.tick-country[data-country="USD"] { color: var(--hue-rates); }
.tick-country[data-country="EUR"] { color: var(--hue-output); }
.tick-country[data-country="GBP"] { color: var(--hue-spending); }
.tick-country[data-country="JPY"] { color: var(--hue-labor); }
.tick-country[data-country="CNY"] { color: var(--hue-energy); }

.tick[data-impact="High"] .tick-title { color: var(--text); }
.tick[data-impact="Low"] .tick-title { color: var(--text-faint); }
.tick[data-impact="Holiday"] .tick-title { font-style: italic; color: var(--text-faint); }
.tick[data-past] { opacity: 0.55; }

.attribution {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 11px;
  line-height: 20px;
  color: var(--text-faint);
}

@media (max-width: 599px) {
  .frame { padding: 12px; }
  .brand-tagline { display: none; }
  .clocks { gap: 10px 16px; }
  /* The status drops under the time so two clocks fit side by side. */
  .clock { grid-template-columns: auto; }
  .clock-status { white-space: normal; }
}
