# Spectrum Controls — Visual Rework

The strip between the spectrum and the radio controls (`#spectrum-controls`)
holds eight controls in two groups. This proposes how it should *look*.

Every control stays, in its current order, with its current name and its
current behaviour. Nothing here changes what a button does, what commits when,
or what is stored. It is a styling and layout change.

*Status: implemented. Kept as the record of what was changed and why.*

---

## What it looks like now

```
Bandwidth [ 12 ] kHz  [Set] [Auto BW] [Sweet-spot]            Peak Hold [2 s] Floor [-115] dB Range [90] dB [Auto] Contrast [——●——] 1.0
```

Four problems, all of them visual:

**1. Four different control heights on one line.** The bare number inputs, the
buttons, the `select` and the range slider are each sized by their own rule, so
nothing shares a baseline and the row reads as a pile rather than a strip.

**2. Units are loose text.** `kHz`, `dB`, `dB` and the contrast value `1.0` are
text nodes sitting outside the control they belong to, separated from it by a
gap the same size as the gap between unrelated controls. The eye has to work
out which number owns which unit.

**3. A quarter of the strip is a hole.** `justify-content: space-between` puts
about 250 px of nothing in the middle at 1600 px, and the two groups read as
two unrelated things because the only thing between them is emptiness.

**4. The groups stagger between 1100 and 1400 px.** The bandwidth group wraps
to two lines while the level group stays on one, so the level group floats at a
height of its own, aligned with neither line of the group beside it. This is
the worst of it, and it happens at a common window width.

Two smaller things: 24 px controls are below any touch-target guideline, and
the contrast readout has no fixed width, so the row twitches as the value
changes between `1.0` and `0.9`.

## Proposed

**One control height, units inside their field, and a rule where the clusters
meet.**

- **A field is one box.** Label, value and unit share a single bordered box —
  `Bandwidth │ 12.0 │ kHz` — so a number and its unit can never be read apart.
  Fields, buttons, the peak-hold select and the contrast slider are all 1.7 rem
  tall, on 44 px targets under a coarse pointer.
- **A rule, not a hole.** The two clusters are separated by a thin vertical
  rule with normal spacing either side. The slack goes to a flexible spacer, so
  the strip is left-aligned rather than pushed apart.
- **A cluster never splits.** Each cluster is `nowrap`; the container wraps. If
  a cluster does not fit on the line it drops whole to the next one,
  left-aligned with the one above. No staggering, at any width.
- **Rules fall away at line starts.** A cluster that begins a line has no rule
  hanging off its left edge.
- **The contrast readout gets a fixed, tabular slot**, so the row is still.

Below the existing mobile breakpoint the strip already stacks; the same field
component applies there, which is most of what makes it look deliberate.

## What this does not change

`Set` stays. `Auto BW` and `Auto` keep their names, even though they mean
different things — that is a naming question, not a styling one. Sweet-spot
stays where it is and keeps its behaviour. Nothing gains or loses persistence.
Nothing moves into a popover, and no control is hidden behind a click.

Those are all worth arguing about separately; a note of them is at the end of
this file so the arguments are not lost.

## Implementation

One pass, no behaviour touched:

1. `.spectrum-field` and `.spectrum-btn` in `style.css`, replacing the six
   per-id rules (`#spectrum-bw-input`, `#spectrum-floor-input`,
   `#spectrum-range-input`, `#spectrum-bw-label`, `#spectrum-floor-label`,
   `#spectrum-range-label`) that currently repeat the same declarations.
2. Markup in `index.html`: the loose `kHz` / `dB` text nodes move inside their
   label, which keeps every id and every event handler exactly where it is.
3. `#spectrum-controls` becomes a wrapping flex row with a spacer;
   `#spectrum-bw-row` and `#spectrum-level-row` become `nowrap` clusters with a
   left rule.
4. The mobile block in the media query drops the rules it no longer needs.

`app.ts` is not touched. Every id survives, so the existing handlers, the
`spectrum-layout.mjs` geometry test and the broadcast-layout highlight all keep
working.

### Tests

Extend `spectrum-layout.mjs`, which already measures this area:

- Every control in the strip shares one height, at 1600, 1200 and 900 px.
- No two clusters sit at different vertical offsets on the same line — the
  staggering bug, asserted directly.
- The strip never overflows its container and never overlaps the hint line.

---

## Noted for later, not proposed here

Behavioural observations from reading the code, kept so they are not lost:

- `Auto BW` (filter) and `Auto` (display scaling) are both called Auto, 600 px
  apart.
- `Floor`, `Range` and `Contrast` are not persisted; `Peak Hold` is.
- `Auto` and `Auto BW` are one-shot: no state, nothing to turn off.
- `Sweet-spot` retunes the SDR and waits up to 1.4 s per candidate centre, with
  no busy indication.
- Contrast resets on a double-click that nothing advertises.
