/* OBEINSON FREQUENCY X — dial colour layer.
 *
 * Gives the readouts a lit-dial palette: a tuned receiver glowing against a
 * dark set, instead of one accent doing every job. Loaded after polish.css,
 * which stays the legibility layer; this one is about meaning.
 *
 * Built on the app's OWN tokens — --amber, --cyan, --green, --red are already
 * defined on :root[data-theme="dark"] — rather than a transplanted palette, so
 * the scheme belongs to the app and follows it if those tokens ever change.
 * Only two hues are added, for the label and tag roles the app had no token
 * for. Everything here is colour: no width, height, margin, padding, grid or
 * flex, so a vendor drop cannot collide with it on layout, exactly as
 * polish.css is written.
 *
 * Scoped to :root[data-theme="dark"]. Dark is the app's documented default and
 * the only theme this is tuned for; the light theme is left untouched rather
 * than half-converted.
 *
 * COLOUR BY ROLE, not by place. The same kind of thing is the same colour on
 * every panel, so the hue carries meaning instead of just being bright:
 *
 *   orange   #ff7f00  numeric values, frequencies — the data you read
 *   cyan     #00e1ff  panel and section headings
 *   green    #00ff22  live / active / ok
 *   magenta  #ff4eac  tags, badges, categories
 *   violet   #af79f6  labels and keys
 *   yellow   #ffd400  peaks, highlights, warnings
 *   red      #ff5d58  faults — outside the six, so a break never reads
 *                     as a warning
 *
 * The hue ANGLES are set first, for separation, and chroma is then maximised
 * within each one against the lightest surface the app paints (#1c2637, 5:1
 * floor). Two earlier attempts got this wrong and are worth not repeating:
 * maximising HSL "saturation" produces pastels (#ffd399 is a cream, less
 * colourful than the amber it replaced), and maximising raw chroma without
 * fixing the angles first collapsed values and peaks into the same orange,
 * because the old palette had them only 6 degrees apart and told them apart
 * by lightness alone. Closest pair is now 85 in rgb distance.
 *
 * Body copy, typed input and the donation disclosure are deliberately NOT
 * coloured — see the bottom of this file. A page where everything shouts is a
 * page where nothing reads, and #supLegal is the one block that exists to be
 * read carefully.
 *
 * The vendor sets many of these with !important, so this layer matches it —
 * the same reasoning polish.css records for its own rules. Without it the
 * headings simply inherit body text, which is what measuring showed.
 *
 * Contrast measured in Chromium against the app's real surfaces, not reasoned
 * about. Every value below clears WCAG AA (4.5:1) on --bg #0f1720 and on the
 * lighter --panel2 #1e2c3c; the figures are in AGENTS.md.
 */

:root[data-theme="dark"] {
  /* The app declares --amber/--cyan/--green/--red on this same selector, but a
     later block repurposes them: measured live, --amber resolves to #7cc0f0 and
     --cyan to #5fb0ea, both blue. Referencing them would put values and headings
     on the same colour and the role system would collapse. These are the app's
     OWN declared dark-theme values, pinned here as literals so the roles stay
     distinct regardless of what later blocks do to the token names. */
  --dial-value: #ff7f00;   /* orange — numeric values, frequencies */
  --dial-head:  #00e1ff;   /* cyan   — panel and section headings   */
  --dial-ok:    #00ff22;   /* green  — live / active / ok           */
  --dial-tag:   #ff4eac;   /* magenta— tags, badges, categories     */
  --dial-label: #af79f6;   /* violet — labels and keys              */
  --dial-peak:  #ffd400;   /* yellow — peaks, highlights, warnings  */

  /* Faults are the one role outside the six. A warning is gold; a thing that
     has actually broken needs to not read as a warning, so it keeps red. */
  --dial-bad:   #ff5d58;
}

/* ------------------------------------------------------------- headings */
:root[data-theme="dark"] h2,
:root[data-theme="dark"] h3,
:root[data-theme="dark"] .secTitle,
:root[data-theme="dark"] .panelTitle {
  color: var(--dial-head) !important;
}
:root[data-theme="dark"] h4,
:root[data-theme="dark"] h5,
:root[data-theme="dark"] legend {
  color: var(--dial-label) !important;
}

/* --------------------------------------------- numeric readouts, frequencies
 * tabular-nums so a changing frequency does not jitter its own width. That is
 * a font feature, not a box property; it cannot move a layout.
 */
:root[data-theme="dark"] .freq,
:root[data-theme="dark"] .readout,
:root[data-theme="dark"] .val,
:root[data-theme="dark"] .hz,
:root[data-theme="dark"] .db,
:root[data-theme="dark"] output {
  color: var(--dial-value) !important;
  font-variant-numeric: tabular-nums;
}
:root[data-theme="dark"] .dial,
:root[data-theme="dark"] .rxHead,
:root[data-theme="dark"] #npTrack,
:root[data-theme="dark"] .nowFab .lbl {
  color: var(--dial-head) !important;
}

/* ------------------------------------------------------- labels and keys */
:root[data-theme="dark"] .lbl,
:root[data-theme="dark"] .cap,
:root[data-theme="dark"] th,
:root[data-theme="dark"] label,
:root[data-theme="dark"] .group {
  color: var(--dial-label) !important;
}
/* .group (the band-list section headers) and label were measured at 4.3:1 and
   3.5:1 — both under AA before this layer existed. They are labels and keys,
   so the role hue is also the accessibility fix: violet clears at 9.0:1. */

/* ------------------------------------------------------------- tables
 * The channel list is the densest data on the page: 500 visible cells. Left
 * plain it reads as a wall. The frequency column is the number you actually
 * came to read, so it takes the value hue; the rest stays plain, because
 * colouring every cell would be the "sprinkled about" failure mode rather
 * than hue-by-role.
 *
 * td.f is the resolved frequency (88.100 MHz) and first-child is the channel
 * designation (88.1 FM) — measured, 100 cells each, both numeric.
 */
:root[data-theme="dark"] td.f,
:root[data-theme="dark"] tbody td:first-child {
  color: var(--dial-value) !important;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------- tags, badges, categories */
:root[data-theme="dark"] .qsMeta em,
:root[data-theme="dark"] .mpMeta em,
:root[data-theme="dark"] kbd {
  color: var(--dial-tag) !important;
  border-color: rgba(240, 122, 168, .30);
}
:root[data-theme="dark"] kbd {
  background: rgba(240, 122, 168, .10);
}

/* ------------------------------------------------------------- state */
:root[data-theme="dark"] .ok,
:root[data-theme="dark"] .live {
  color: var(--dial-ok) !important;
}
:root[data-theme="dark"] .warn,
:root[data-theme="dark"] .peak,
:root[data-theme="dark"] mark {
  color: var(--dial-peak) !important;
}
:root[data-theme="dark"] mark {
  background: rgba(255, 200, 87, .12) !important;
}
:root[data-theme="dark"] .bad,
:root[data-theme="dark"] .err {
  color: var(--dial-bad) !important;
}

/* ---------------------------------------------------------------- bands
 * The band list cycles the palette so the dial reads as a spectrum rather than
 * a list of identical rows. Colour only — the selected band keeps whatever
 * background the app gives it.
 */
:root[data-theme="dark"] .band:nth-of-type(6n+1) { color: var(--dial-value) !important; }
:root[data-theme="dark"] .band:nth-of-type(6n+2) { color: var(--dial-head) !important; }
:root[data-theme="dark"] .band:nth-of-type(6n+3) { color: var(--dial-ok) !important; }
:root[data-theme="dark"] .band:nth-of-type(6n+4) { color: var(--dial-tag) !important; }
:root[data-theme="dark"] .band:nth-of-type(6n+5) { color: var(--dial-label) !important; }
:root[data-theme="dark"] .band:nth-of-type(6n+6) { color: var(--dial-peak) !important; }

/* ============================================= what the panel sweep found
 * Opening every panel and measuring each rendered text node against its own
 * background turned up seven failures that eyeballing the dial had missed.
 * All of them pre-date this layer. Most are role assignments rather than
 * patches, which is the point: a frequency, a value, a category, a label.
 *
 * Gradient-backed text is excluded from the measurement rather than counted
 * as transparent — polish.css puts dark ink on bright gradient buttons on
 * purpose, and an earlier version of the sweep reported all of those as 1.1:1
 * failures. They were never failures.
 */

/* Surfaces the dark skin never reached: white boxes carrying near-white text.
 * .toolsBtn measured 1.18:1 — white on white, effectively invisible. */
:root[data-theme="dark"] .toolsBtn,
:root[data-theme="dark"] #scanfb,
:root[data-theme="dark"] .scanfb,
:root[data-theme="dark"] .ok100 {
  background: var(--panel2, #1c2637) !important;
  border-color: rgba(183, 155, 240, .22) !important;
}
:root[data-theme="dark"] .toolsBtn,
:root[data-theme="dark"] #scanfbTxt,
:root[data-theme="dark"] .ok100,
:root[data-theme="dark"] .ok100 * {
  color: var(--text) !important;
}

/* #chLabel b is the tuned frequency (88.1 FM) in dark teal ink left over from
 * the light skin — 1.19:1 on the dark panel. It is the single number the dial
 * exists to show, so it takes the value hue. */
:root[data-theme="dark"] #chLabel b,
:root[data-theme="dark"] #sqVal,
:root[data-theme="dark"] #scopeRd,
:root[data-theme="dark"] .band.sel,
:root[data-theme="dark"] .band.sel span {
  color: var(--dial-value) !important;
}

/* "FM radio · WFM · 100 channels" is a category line, not a reading. */
:root[data-theme="dark"] #bandLabel {
  color: var(--dial-tag) !important;
}

/* "Strongest signals now:" introduces a list — a key, not data. */
:root[data-theme="dark"] #best .t {
  color: var(--dial-label) !important;
}

/* The World explorer's active category chip measured 2.0:1 — near-white text
 * on its orange pill. The app already intends dark ink there:
 *
 *   .obnWCats button[aria-pressed="true"]{background:#ff8a00 !important;
 *                                         ... color:#0b1b2e}
 *
 * but !important landed on background and not on color, so a later sheet kept
 * the orange and overrode the ink. This restores the author's own value; it is
 * a cascade repair, not a new colour decision, which is why the hex is theirs
 * rather than one of the six. */
:root[data-theme="dark"] .obnWCats button[aria-pressed="true"],
:root[data-theme="dark"] .obnWCats button[aria-pressed="true"] * {
  color: #0b1b2e !important;
}

/* ------------------------------------------- definition lists and tables
 * The station-details rail is a real <dl>: dt is the key, dd is the reading.
 * That is the label/value pair the role system exists for, and it was sitting
 * entirely plain — the most visible uncoloured block in the app.
 *
 * thead th needs the extra specificity: a plain `th` rule loses to the
 * vendor's own, which is why the earlier one never landed.
 */
:root[data-theme="dark"] dt,
:root[data-theme="dark"] dl dt,
:root[data-theme="dark"] thead th,
:root[data-theme="dark"] table th {
  color: var(--dial-label) !important;
}
:root[data-theme="dark"] dd,
:root[data-theme="dark"] dd.m {
  color: var(--dial-value) !important;
}
:root[data-theme="dark"] #flowInfo b,
:root[data-theme="dark"] #best .sel span {
  color: var(--dial-value) !important;
}

/* The World explorer is the largest panel in the app — 3867 text nodes — and
 * was almost entirely monochrome: its 15 section headings sat grey and the
 * title of each of its 816 cards sat plain. Both are headings by role. */
:root[data-theme="dark"] .obnWSec,
:root[data-theme="dark"] .obnWItem b {
  color: var(--dial-head) !important;
}

/* ------------------------------------------------- the station catalog
 * A station row carries five different kinds of thing and was showing them in
 * one colour. Each takes its role hue, so a row can be read at a glance
 * instead of parsed:
 *
 *   1                 the index          -> value
 *   WNYC-FM (New York) the station       -> head
 *   USA · New York    where it is        -> label
 *   news              what it plays      -> tag
 *   128k              the bitrate        -> value
 *   IN-APP            it plays in here   -> ok
 *
 * Selectors are anchored on .qsRow rather than written flat: a bare
 * `.qsMeta em` rule with !important was already in this file and still lost,
 * so the extra specificity is what makes these land.
 */
:root[data-theme="dark"] .qsRow .qsNum,
:root[data-theme="dark"] .qsRow .qsMeta em.br {
  color: var(--dial-value) !important;
}
:root[data-theme="dark"] .qsRow .qsInfo b {
  color: var(--dial-head) !important;
}
/* These carry -webkit-text-fill-color, which paints the glyphs and beats
 * `color` whatever its specificity. Setting color alone changed nothing on
 * screen — the fill has to be set too, or the rule is inert. */
:root[data-theme="dark"] .qsRow .qsMeta em {
  color: var(--dial-tag) !important;
  -webkit-text-fill-color: var(--dial-tag) !important;
}
:root[data-theme="dark"] .qsRow .qsMeta em.geo {
  color: var(--dial-label) !important;
  -webkit-text-fill-color: var(--dial-label) !important;
}
:root[data-theme="dark"] .qsRow .qsMeta em.routeBadge {
  color: var(--dial-ok) !important;
  -webkit-text-fill-color: var(--dial-ok) !important;
}
:root[data-theme="dark"] .qsRow .qsMeta em.br {
  -webkit-text-fill-color: var(--dial-value) !important;
}
:root[data-theme="dark"] .qsRow .qsInfo b {
  -webkit-text-fill-color: var(--dial-head) !important;
}

/* The big frequency readout is gradient-clipped: -webkit-text-fill-color is
 * transparent and a background gradient paints the glyphs. A `color` rule on
 * it does nothing at all — this file claimed for several commits that .freq
 * had gone flat amber, and it never had. The gradient is kept, because the
 * hero number is better as a gradient, and restated in the value hue instead
 * of the old blue-teal. */
:root[data-theme="dark"] .freq {
  background: linear-gradient(135deg, #ffc46b, #ff7f00 55%, #e05d00) !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
}

/* Band list: the name keeps the spectrum cycle, the frequency range is a
 * reading like any other. 55 of these sat grey. */
:root[data-theme="dark"] .band .r,
:root[data-theme="dark"] span.r {
  color: var(--dial-value) !important;
  font-variant-numeric: tabular-nums;
}

/* The catalog's own column headings (A — BROWSE, GENRES & COLLECTIONS). */
:root[data-theme="dark"] .railTitle,
:root[data-theme="dark"] .qsRail .railTitle {
  color: var(--dial-head) !important;
}

/* ------------------------------------------------- the channel table
 * 101 rows, five columns, all one colour. The vendor anchors its rules on
 * `.channels` at (0,3,1), so a `table th` rule at (0,2,2) loses however many
 * !importants it carries — the anchor has to match, not the importance.
 *
 * Columns are 1=spacer 2=Channel 3=Frequency 4=Level 5=Notes.
 */
:root[data-theme="dark"] .channels th {
  color: var(--dial-label) !important;
}
:root[data-theme="dark"] .channels td.f,
:root[data-theme="dark"] .channels tbody td:nth-child(2),
:root[data-theme="dark"] .channels tbody td:nth-child(3) {
  color: var(--dial-value) !important;
  -webkit-text-fill-color: var(--dial-value) !important;
}
:root[data-theme="dark"] .channels tbody td:nth-child(4) {
  color: var(--dial-peak) !important;
  -webkit-text-fill-color: var(--dial-peak) !important;
}

/* ------------------------------------------- the pieces common to every panel
 * These sit behind whichever overlay is open, so they are on screen in all of
 * Browse, Genres, Station details, GeoMap, Free TV and World.
 *
 * SVG needs `fill`, not `color`: the dial's scale numbers are <text> nodes and
 * a color rule on them does nothing, the same class of mistake as the
 * text-fill one below.
 */
:root[data-theme="dark"] svg text,
:root[data-theme="dark"] svg tspan {
  fill: var(--dial-value) !important;
}

/* Header counters: "Daily hits", "Total hits", "Listening now" are keys and
 * the figures beside them are readings. */
:root[data-theme="dark"] #obnHitCount span,
:root[data-theme="dark"] .statStrip span {
  color: var(--dial-label) !important;
}
:root[data-theme="dark"] #obnHitCount b,
:root[data-theme="dark"] #obnHitCount strong,
:root[data-theme="dark"] #obnLiveListening {
  color: var(--dial-value) !important;
}

/* Unit and caption text (MHz, NOW PLAYING) is a key, not a reading. */
:root[data-theme="dark"] small {
  color: var(--dial-label) !important;
}

/* Mixer channel keys (L, R, and the strip labels). */
:root[data-theme="dark"] .mixer em,
:root[data-theme="dark"] .chan em,
:root[data-theme="dark"] .vu em {
  color: var(--dial-label) !important;
}

/* ------------------------------------------------------------ GeoMap
 * The map is drawn on a canvas, so its colours live in geoDraw() in
 * index.html, not here. The legend is markup, and it has to agree with what
 * the canvas now paints: the three dots were all one blue (#5fb0ea) with only
 * their SIZE differing, while the map's bubble colour carried nothing but
 * hover state. Hue now encodes density in both places.
 */
:root[data-theme="dark"] .qsGeoLegend i.s { background: #00e1ff !important; opacity: 1 !important; }
:root[data-theme="dark"] .qsGeoLegend i.m { background: #ffd400 !important; opacity: 1 !important; }
:root[data-theme="dark"] .qsGeoLegend i.l { background: #ff4eac !important; opacity: 1 !important; }
:root[data-theme="dark"] .qsGeoLegend { color: var(--dial-label) !important; }

/* -------------------------------------------------- World explorer sections
 * 816 cards in 15 sections, every card outlined in the same #1c2c42. The card
 * TEXT cannot carry more colour without breaking the role system — the titles
 * are headings and the lines under them are descriptions, and colouring prose
 * is the "everything shouts" failure. The border is not text, so it is free:
 * cycling it by section groups the grid visually and adds colour at no cost
 * to reading.
 *
 * .obnWList alternates .obnWSec, .obnWGrid, so the grids sit on even child
 * positions and 14n+k steps one hue per section.
 */
:root[data-theme="dark"] .obnWList > div:nth-child(14n+2) .obnWItem {
  border-color: rgba(0,225,255, .42) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+2) .obnWItem:hover {
  border-color: rgba(0,225,255, .95) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+4) .obnWItem {
  border-color: rgba(255,127,0, .42) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+4) .obnWItem:hover {
  border-color: rgba(255,127,0, .95) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+6) .obnWItem {
  border-color: rgba(0,255,34, .42) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+6) .obnWItem:hover {
  border-color: rgba(0,255,34, .95) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+8) .obnWItem {
  border-color: rgba(255,78,172, .42) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+8) .obnWItem:hover {
  border-color: rgba(255,78,172, .95) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+10) .obnWItem {
  border-color: rgba(175,121,246, .42) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+10) .obnWItem:hover {
  border-color: rgba(175,121,246, .95) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+12) .obnWItem {
  border-color: rgba(255,212,0, .42) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+12) .obnWItem:hover {
  border-color: rgba(255,212,0, .95) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+14) .obnWItem {
  border-color: rgba(255,93,88, .42) !important;
}
:root[data-theme="dark"] .obnWList > div:nth-child(14n+14) .obnWItem:hover {
  border-color: rgba(255,93,88, .95) !important;
}

/* ================================================================ held back
 * Everything below keeps full-contrast plain text on purpose.
 *
 * #supLegal carries the voluntary, free-access and non-tax-deductible
 * disclosure, which AGENTS.md requires stay visible where someone actually
 * gives. It is the one block on the Donate screen that exists to be read
 * carefully, so it is the last thing that should be tinted.
 *
 * Typed text stays neutral so what someone has entered is never mistaken for
 * a readout the app produced.
 *
 * .eulaChk is the "I have read and agree" consent. The label rule above had
 * caught it and tinted it violet, which made a legal consent read as a piece
 * of UI chrome. Consent text is the clearest case of something that must look
 * like text to be read, not like a control to be clicked past.
 *
 * polish.css darkens the ink on bright accent buttons to clear AA; nothing
 * here may set a colour on those, or it would undo a measured fix.
 */
:root[data-theme="dark"] #supLegal,
:root[data-theme="dark"] #supLegal *,
:root[data-theme="dark"] .eulaBody p,
:root[data-theme="dark"] .eulaBody li,
:root[data-theme="dark"] .eulaChk,
:root[data-theme="dark"] .eulaChk *,
:root[data-theme="dark"] .eulaFoot,
:root[data-theme="dark"] input,
:root[data-theme="dark"] textarea,
:root[data-theme="dark"] select {
  color: var(--text);
}
:root[data-theme="dark"] input::placeholder,
:root[data-theme="dark"] textarea::placeholder {
  color: var(--muted);
}
