UI

No build step. Plain ES modules, served as static files; the Pi never needs Node.

frontend/
  index.html  style.css  manifest.webmanifest  sw.js  icons/
  src/
    main.js  api.js  audio.js  tuner.js  util.js  meter-cal.js
    pages/    login.js  radio.js  admin.js
    components/
      freq-display.js   LCD-style frequency; tap a digit = tuning step, wheel over a digit tunes by it
      vfo-b.js          VFO B readout, swap / A=B / B=A, SPLIT, quick split, set B
      dial.js           small round dial (tablet/desktop): drag, wheel, arrow keys; Fine / Coarse x10
      tuning-strip.js   sliding tuning ruler (phones): swipe, flick, x10
      meters.js         coloured segment meters: S-meter, power, SWR, ALC, COMP
      controls.js       colour-coded chips, level sliders, and the filter/DSP panel as tabs per group (Filter, Noise,
                        Receiver, Transmit; last tab remembered). All generated from the radio's capabilities
      ptt.js            hold-to-transmit, heartbeat, TX timeout bar

Layouts

Width Layout
< 700 px (phone) Compact top menu on one line, radio bar with a green dot and the control status. Same VFO block as large screens, just smaller: VFO A with the signal meter, the small TX meters under it, VFO B as an attached tab (red TX badge when it is the one transmitting in split); a thin sliding tuning scale; smaller buttons in VFO tools / step / band / mode / chips (side-scrolling rows). Bottom tabs: Radio / Filters / Levels / Audio; PTT sticky on the Radio tab
Tablet (touch screen, no hover, >= 700 px wide; e.g. iPad 10") Phone-style tabs Radio / Filters / Audio & Levels / Digital with the large-screen look: Radio tab = the large-screen arrangement in compact form (smaller buttons, thin tuning scale): VFO block (A + meters + B tab), tuning scale, [VFO tools
There is no round dial any more: the sliding tuning scale (swipe or drag, x10 button) is used on every screen size. It shows frequency numbers whose spacing follows the tuning step (bigger step or x10 = wider span).
Look: flat dark-slate buttons with a small LED bar on the left edge (grey = off, yellow = selected/on), controls grouped in labelled
blocks (VFO TOOLS, TUNING STEP, MODE, BAND SELECT, SET FREQUENCY, RECEIVER & TOOLS). The active VFO panel has a cyan border, the transmitting
panel turns red with a TX badge, the transmit VFO in split has an amber border and SPLIT badge. Meter colours keep their meaning
(blue/green/yellow/red). VFO B shows no S-meter or mode because the CAT command set has none for VFO B. Respects prefers-reduced-motion
and safe-area insets.
## Meters (provisional scales: read this)

The radio's CAT meters are raw 0-255 values and the manual gives no calibration. So:

To calibrate properly (bench 5 in 03-bench-checklist.md): feed known signals (S-meter), and compare against the radio's own meters and a wattmeter (power, SWR, ALC).

Tuning behaviour

Honest limits

PWA

manifest.webmanifest + sw.js (network-first shell cache, never caches /api or /ws) + generated icons (python tools/make_icons.py). Static files are served with Cache-Control: no-cache; an open page shows a "Reload now" banner when a new version has been installed under it.

Device detection: src/main.js sets data-tablet on the page when the screen has no hover (touch) and is 700 px or wider. To force a mode open the page with ?device=tablet, ?device=desktop or ?device=auto (remembered per browser).

Changes after the first field tests