/* stations.css — the list and band dial: rows, sortable header, expandable record.
   Widget: web/js/stations.js */

/* The list scrolls while the sticky, opaque header stays on top. There is deliberately no
   CSS mask: a mask forces a repaint of the whole element whenever any child changes, so the
   legitimate update of the tuned row made the entire list flicker. Without one, only the
   tuned row repaints. */
/* The card holds the list and nothing else, so it gives it all of its room: the padding of a
   widget card would push the sticky header down inside the scrollport (see below), and the
   list carries its own. */
.wcard.w-list{padding:0;}
.wcard.w-list .stations{flex:1 1 auto; min-height:0;}   /* it scrolls rather than growing */
/* Three things want the top-right corner: the slot's mark, the list's own mode selector, and
   the SCROLLBAR, which is inside the card because the list is what scrolls. The first two are
   placed against the card and stepped left of the third, whose width is measured by the script
   into `--sbw` -- the stylesheet cannot know it, it depends on the platform and on the browser's
   setting, and there is nothing to guess with since the bar is about as wide as a control.
   That is the WHOLE corner: these controls float over the column header, which runs edge to
   edge, so each one costs a column title its place and its sort click. Two fit because a glyph
   is narrow and the far-right columns are the least read; a third does not, and the discovery
   scans went to the spectrum for that reason. */
.wcard.w-list .wnav{right:calc(32px + var(--sbw, 0px));}
.wcard.w-list #stMode{right:calc(8px + var(--sbw, 0px));}
/* No padding at the top, and that is load-bearing: a sticky element cannot leave its
   containing block, which is this element's CONTENT box. Any padding here would hold the
   header that far below the top of the scrollport while rows kept scrolling through the gap,
   showing a sliver of the list above the header. The header carries its own top padding. */
.stations{padding:0 6px 6px; overflow:auto; --headH:22px;
  /* Column widths: a single source for all three row kinds, so header, row and record
     cannot diverge. Written from the script (defaults, then dragging) and persisted; the
     values here are only a bootstrap before the first application. */
  --c1:22px; --c2:48px; --c3:98px; --c4:58px; --c5:26px; --c6:96px;
  --c7:132px; --c8:96px; --c9:52px; --c10:46px; --c11:42px; --c12:50px;
  /* The template lists the VISIBLE columns and is rewritten whenever that set changes. This
     bootstrap only has to hold for the frame before the script runs: without it the template
     would resolve to nothing and the row would collapse into a single column. */
  --tmpl:var(--c1) var(--c2) var(--c3) var(--c4) var(--c5) var(--c6);}
/* Aligned columns, as in a measurement table. All fixed and packed to the left, with the
   empty space pushed to the end. No elastic column: a station name is eight characters at
   most, and giving it a flexible width dug a hole between the name and the identity code.
   Stereo has its own column, and the measurement fits in one: a fixed-width bar so rows
   compare at a glance, its value, and the unit attached to the value.
   The last columns are the transmitter, which the backend resolves for every identified
   row. That is analysis, so it sits on the right, after the measurement, and it finally
   occupies the empty end.
   Every width is adjustable with the mouse through a header handle. */
/* Uniform typography: one size and one family for all the list data. It once carried eight
   sizes and two families, with each cell negotiating its own scale. A measurement table
   reads in columns: alignment is what creates hierarchy, not size. What distinguishes a row
   is now colour and weight alone. */
/* Cells sit on ONE BASELINE, not each centred in its own box. Centring aligns boxes, and a box
   is only the same height as its neighbour while their contents are: the level cell carries a
   figure and a smaller unit, so it was centred on a taller line than the plain cells beside it
   and its text floated off the row. A table of measurements is read along a line; that line has
   to exist. */
.strow{display:grid; grid-template-columns:var(--tmpl); align-items:baseline;
       gap:9px; padding:4px 8px; border-radius:5px; cursor:pointer;
       font-family:var(--mono); font-size:12px;}
.strow .idx{color:var(--dim2); text-align:right; font-variant-numeric:tabular-nums;}
/* Column header: sticky and clickable to sort. Same grid as a row, so they align. */
.sthead{display:grid; grid-template-columns:var(--tmpl); gap:9px;
        padding:3px 8px 5px; position:sticky; top:0; z-index:2; border-bottom:1px solid var(--edge);
        background:linear-gradient(180deg,var(--panel2),var(--panel));
        font-size:9px; letter-spacing:.1em; color:var(--dim2); text-transform:uppercase;}
.sthead span{cursor:pointer; user-select:none; white-space:nowrap; position:relative;}
/* Resize handle: invisible at rest, a hairline under the cursor. It is a control, not
   decoration, and it overlaps the gutter, so it consumes no data width. */
.colres{position:absolute; top:-3px; right:-7px; width:10px; height:calc(100% + 6px);
        cursor:col-resize; z-index:3;}
.colres:hover{background:linear-gradient(90deg,transparent 0 4px,var(--teal) 4px 5px,transparent 5px);}
.sthead span:hover{color:var(--ink);}
.sthead span.act{color:var(--teal);}
.sthead .idx{text-align:right;}
/* Band mode: the position carries the information and no column sorts, so the header says so. */
.sthead.nosort span{cursor:default;}
.sthead.nosort span:hover{color:var(--dim2);}
/* The mode selector takes the shared panel corner toggle as it is, positioned against the CARD
   like every other widget's -- so nothing is defined here at all. It used to be anchored to the
   header, and that was the defect: a sticky header is a positioned ancestor, but its right edge
   sits inside the card by the width of the scrollbar, so the control drifted right and ended up
   underneath the slot's corner mark. Hanging off the card also keeps it in view while the list
   scrolls, which was the reason for the original anchoring. */

/* Which columns are shown is the operator's choice, kept by position like the widths. The
   template is built from the visible ones alone, so a hidden column leaves no gap behind it,
   and the cells go through one class on the panel rather than a style written on every cell
   of every row -- 206 rows in band mode.
   Written out, one rule per column: a loop would have to live in JavaScript and touch the
   DOM on every toggle, where this is a single class swap. */
.stations.hc3 > .sthead > span:nth-child(3),
.stations.hc3 .strow > span:nth-child(3),
.stations.hc3 .ssrow > span:nth-child(3){display:none;}
.stations.hc4 > .sthead > span:nth-child(4),
.stations.hc4 .strow > span:nth-child(4),
.stations.hc4 .ssrow > span:nth-child(4){display:none;}
.stations.hc5 > .sthead > span:nth-child(5),
.stations.hc5 .strow > span:nth-child(5),
.stations.hc5 .ssrow > span:nth-child(5){display:none;}
.stations.hc6 > .sthead > span:nth-child(6),
.stations.hc6 .strow > span:nth-child(6),
.stations.hc6 .ssrow > span:nth-child(6){display:none;}
.stations.hc7 > .sthead > span:nth-child(7),
.stations.hc7 .strow > span:nth-child(7),
.stations.hc7 .ssrow > span:nth-child(7){display:none;}
.stations.hc8 > .sthead > span:nth-child(8),
.stations.hc8 .strow > span:nth-child(8),
.stations.hc8 .ssrow > span:nth-child(8){display:none;}
.stations.hc9 > .sthead > span:nth-child(9),
.stations.hc9 .strow > span:nth-child(9),
.stations.hc9 .ssrow > span:nth-child(9){display:none;}
.stations.hc10 > .sthead > span:nth-child(10),
.stations.hc10 .strow > span:nth-child(10),
.stations.hc10 .ssrow > span:nth-child(10){display:none;}
.stations.hc11 > .sthead > span:nth-child(11),
.stations.hc11 .strow > span:nth-child(11),
.stations.hc11 .ssrow > span:nth-child(11){display:none;}
.stations.hc12 > .sthead > span:nth-child(12),
.stations.hc12 .strow > span:nth-child(12),
.stations.hc12 .ssrow > span:nth-child(12){display:none;}
.stations.hc13 > .sthead > span:nth-child(13),
.stations.hc13 .strow > span:nth-child(13),
.stations.hc13 .ssrow > span:nth-child(13){display:none;}

.strow:hover{background:#13202b;}
/* The tuned station is the centre of the dial: emphasised and one notch stronger. */
.strow.on{background:#15222c; box-shadow:inset 3px 0 0 var(--amber);}
.strow.on .f{color:var(--amber); font-weight:600;}
.strow.on .ps{color:#f3f8fb; font-weight:600;}
.strow.on .pi{color:#cfe0ea;}
/* Band mode: a channel of the plan that nothing occupies. It keeps its place, which is the
   point of a band ruler, but recedes -- the frequency and its measured level place it. */
.strow.empty .f{color:var(--dim2);}
.strow.empty .lvl{opacity:.55;}
.strow.transient{font-style:italic;}   /* the live dial position, not a catalogued station */
.strow.transient .idx{color:var(--amber);}
.strow .f{color:#dbe6ee;}
.strow .ps{color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
/* The progressively decoded name of the tuned row uses the same rendering as the listening
   panel; only the colours are redeclared here, the panel's being scoped to its own tile. */
.strow .ps .psc{white-space:pre;}
.strow .ps .psc.dec{color:#eaf7f2;}
.strow .ps .psc.hint{color:#93a6b4;}
.strow .ps.inh{opacity:.5; font-style:italic;}   /* name inherited from another frequency */
/* One doubt, marked wherever it reaches. The tuner never granted this code its full
   confidence: the code is shown with a reservation rather than withheld, and so is a name
   that is only a translation of that code. Generated content, so it survives the text being
   rewritten on every refresh and costs no element. A name decoded off the air carries no
   such mark -- it does not come from the code. */
.strow .piv.q::after, .strow .ps.q::after{content:"?"; color:var(--dim); margin-left:2px;}
/* Qualified with no identity: a confirmed peak or a measured width, but nothing naming it.
   The dash says "there is something here, we do not know what"; an empty cell would read as
   a bug. */
.strow.anon .ps{color:var(--dim2);}
/* Refuted by measurement: the sweep saw a peak and the tune denied it. Visible in band mode
   only, and struck through, because it is a diagnostic tool. */
.strow.refuted .f, .strow.refuted .ps{text-decoration:line-through; color:var(--dim2);}
.strow.refuted .lvl{color:var(--dim2); opacity:.5;}
/* Stereo: the letters alone, not a filled chip. The listening panel shows one flag on one
   station and can afford a solid badge; a list stacks hundreds of them down a column, where
   the same badge becomes a wall of colour competing with the measurement. Density is the
   reason the two differ, and the meaning is unchanged -- absent when the pilot was never
   seen, which is not "mono" but "no proof". */
.strow .stf{font-style:normal; letter-spacing:.08em; font-weight:400; color:#3f7d77;}
.strow.on .stf{color:var(--teal);}
.strow .stf:empty{display:none;}
.strow .pi{color:var(--dim); display:flex; align-items:baseline; gap:5px;}
/* Channel record: several occupants on one frequency. The counter appears only from two,
   since a single station is the normal case. It expands rather than tunes, hence the wider
   click target and the distinct cursor. */
.strow .stn{font-style:normal; line-height:1.35; padding:0 4px; border-radius:2px;
            color:var(--dim); background:none; border:1px solid var(--edge);
            font-variant-numeric:tabular-nums; cursor:pointer;}
.strow .stn:hover{color:var(--ink); border-color:var(--dim);}
.strow .stn.open{color:var(--teal); border-color:var(--teal);}   /* a state, not decoration */
/* The expanded record is a full-width band beneath the cells, so the grid keeps its columns. */
.stsub{grid-column:1/-1; margin:4px 0 1px; padding:3px 0 1px; border-top:1px solid var(--edge);}
/* The record reuses exactly the row's columns: name under name, identity under identity,
   level under level. It once had a grid of its own, aligned with nothing -- a table inside a
   table. The index column serves as indentation and the frequency column carries the date
   last heard. */
.ssrow{display:grid; grid-template-columns:var(--tmpl); gap:9px;
       align-items:center; padding:2px 8px; color:var(--dim);
       font-variant-numeric:tabular-nums;}
.ssrow.on{color:var(--ink);}                  /* the current occupant of the row */
.ssrow.on .sspi{color:var(--amber);}
.ssrow .ssps{overflow:hidden;
             text-overflow:ellipsis; white-space:nowrap;}
/* The catch count annotates the name in its own cell: it informs the decision without
   taking it. No day counter, because the table is the live view and not a logbook. */
.ssrow .ssrec{font-style:normal; color:var(--dim2); margin-left:8px;}
.ssrow .sslvl{text-align:right;}   /* aligned under the parent row's value */
/* Scoped to a row on purpose: the meter class already belongs to the listening panel, and an
   unscoped rule here reflowed its cards at the other end of the page. General rule: this
   sheet defines only selectors descending from the list. */
/* Packed to the RIGHT, like the header above it and like every other measurement column. It was
   packed left inside a cell wide enough for the transmitter block, so the figures sat some forty
   pixels from the word they belong under -- and the wider the operator made the column, the
   further they drifted. The digits keep their own fixed box inside, so they still line up with
   each other whatever the unit. */
.strow .meter{display:flex; align-items:baseline; justify-content:flex-end; gap:4px; min-width:0;}
.strow .lvl{width:22px; text-align:right; color:var(--dim);
            font-variant-numeric:tabular-nums; font-weight:400;}
/* The level is read as a figure and placed by its shade. The shade codes DECIBELS ABOVE THE
   MEASURED NOISE FLOOR, not absolute dBuV: what counts is how far a carrier stands out of
   the noise where it sits, and the floor depends on the antenna and the site.

   A continuous ramp, written directly in oklch so all three dimensions move at once and
   perceptually: lightness, chroma and hue. Mixing two fixed colours instead left the middle
   of the scale grey, because the shortest path between them in a perceptual space goes
   through low chroma -- a luminance ramp, not a colour one.

   Continuous because a stepped scale makes a carrier hovering near a boundary flip between
   two shades without its signal really changing.

   Chroma is what carries the reading: buried in the noise is nearly grey, and colour comes
   with the signal, so a strong carrier is vivid rather than merely pale. The hue turns from
   blue to green along the way -- the idiom of a spectrogram, not of a car radio, which is a
   red-to-green verdict on quality and not a scale of level. It stops short of yellow, which
   belongs to the tuned row. */
.strow .lvl.sig{
  color:oklch(calc(45% + var(--sig, 0) * 43%)      /* dim to bright */
              calc(0.02 + var(--sig, 0) * 0.15)    /* grey to vivid */
              calc(250 - var(--sig, 0) * 95));     /* blue through cyan to green */
}
/* The unit is no longer distinguished by size, which is uniform, but by colour alone: it
   accompanies the value without competing with it. */
.strow .unit{color:var(--dim2); margin-left:3px;}
/* Transmitter columns. Numeric ones are right-aligned with tabular figures and the unit
   attached, the same idiom as the signal: a column of measurements reads as a column. */
.strow .num, .sthead .num{text-align:right; font-variant-numeric:tabular-nums;}
.strow .num{color:var(--dim);}
.strow .site{display:flex; align-items:baseline; gap:5px; min-width:0; color:var(--dim);}
.strow .city, .strow .pol{color:var(--dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.strow .site .gnm{font-weight:400; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.strow .site .gfl{font-style:normal; line-height:1;}
/* The approximation marker means several sites share the identity code and cannot be told
   apart. Same convention as the transmitter widget: nothing when the site is certain. */
.strow .site .gamb{font-style:normal; color:var(--amber);}
.strow.on .site, .strow.on .num, .strow.on .city, .strow.on .pol{color:var(--ink);}
/* The tuned row keeps its measured shade and gains weight only. Repainting it amber would
   erase the one reading you are actually looking at; the row already says where you are with
   its background, its edge and its frequency. */
.strow.on .lvl{font-weight:600;}
.strow.on .unit{color:var(--dim);}
