Handoff: Flash page (option 2a — console)
Overview
Replacement for /flash.html on serin-labs.com. The page keeps the existing
esp-web-tools install flow but rebuilds the surface around it as a single dark
console: a left device bay that fills in with what was read off the connected
board, and a right panel that is one surface reused across the flow — instructions
before flashing, phase progress during, pairing after. The old page's stacked
"1. Select your platform / 2. Flash / Notes" structure is gone.
About the design files
flash-2a-reference.dc.html in this folder is a design reference written in HTML —
a prototype of the intended look and states, not production code to lift. Recreate it in
the target codebase using its own conventions: Jekyll page flash.html, styles into the
existing assets/css stylesheet(s), nav and footer from the site's existing includes
(the chrome in the prototype is a stand-in).
Open it directly in a browser; it needs support.js and assets/serin_logo.svg, both
included here.
Fidelity
High fidelity for layout, color, type and copy — the values below are authoritative.
Medium fidelity for the flashing mechanics: how much of the device data you can
actually surface depends on what esp-web-tools / the esptool JS exposes (see
"Detected data" below). Where a field cannot be read, hide the row — do not show a
placeholder or "unknown".
Page frame
Content width 1200px, 48px horizontal gutters on every band. Bands in order:
- nav (existing include)
- dark header band
#1A1B1E - console band
#121316holding the panel - light "after flashing" strip
#F3F3F0notes row (3 columns)- footer (existing include)
Header band
- Background
#1A1B1E, padding34px 48px 28px, row flex, space-between,align-items: flex-end,gap: 40px. - Eyebrow "web installer" — JetBrains Mono 12.5px, letter-spacing 0.06em,
#F09A28. - H1 "Put firmware on the board" — Manrope 800, 42px/1.04, letter-spacing -0.035em,
#fff. - Lead 17px/1.5,
rgba(255,255,255,0.65), max-width 58ch: "Plug the board into this computer over USB. Everything happens in the browser — nothing to download, no account, no toolchain." - Right rail, right-aligned, JetBrains Mono 12.5px,
gap: 9px:- Browser check, live: green dot
#9BE8B0+ "Chrome 138 · Web Serial available". Ifnavigator.serialis missing, this becomes an amber row — "Web Serial not available · use Chrome, Edge or Brave" — and the primary button in the bay is disabled with the same message beneath it. - "USB data cable, not charge-only" —
rgba(255,255,255,0.5). - "No board yet? Parts list →" —
#F09A28, links/parts.html.
- Browser check, live: green dot
Console band
- Background
#121316, padding0 48px 40px. - Panel: background
#1A1B1E, 1px borderrgba(255,255,255,0.12), radius 14px,overflow: hidden, grid420px 1fr; the two columns are divided by a 1px borderrgba(255,255,255,0.12). - Left column (device bay) padding
24px 26px, column flexgap: 20px. - Right column background
#16171A, padding26px 28px, column flexgap: 20–22px. - Panel height is content-driven, but the two columns must stay equal height and the
bottom block of each column is pushed down with
margin-top: autoso the primary button and the closing row line up across states.
The four states
The panel is one component with a state machine. Everything outside the panel is static.
idle ──connect──▶ detected ──install──▶ writing ──▶ done
▲ │ │ │
└───────── disconnect / error ───────────┴──────────┘
State 1 — idle (nothing connected)
Bay: status pill "waiting" (grey dot, rgba(255,255,255,0.06) bg). Body is a dashed
drop-target block: 1px dashed rgba(255,255,255,0.22), radius 11px, padding 30px 20px,
centered — a 54px circle outline labelled "usb", heading "Plug the board in" (16px/700),
and 14px/1.5 rgba(255,255,255,0.5) copy "Then press connect and pick it from the browser's
list. We read the rest ourselves."
Firmware toggle is present and usable already. Primary button: "Connect a device",
sub-label "Chrome, Edge or Brave. Nothing is installed."
Right panel: "Two minutes, one cable" + lead; then a What we read off the chip
2×2 monospace list — chip family and revision / flash size and speed / mac address /
firmware already installed — followed by 13.5px rgba(255,255,255,0.4): "Read locally in
this tab. None of it is sent to us." Bottom card: supported boards (image slot + copy).
State 2 — detected (board read, nothing written)
This is the state shown in the full-page reference.
Bay: pill "connected" (green #9BE8B0 dot on rgba(155,232,176,0.12)).
Device card: board thumbnail 46px, board name 16.5px/700, and beneath it
"matched from USB 303A:1001" (monospace 12px). Then a 4-row monospace table
(12.5px, rows separated by 1px solid rgba(255,255,255,0.08), label
rgba(255,255,255,0.45) left / value #fff right):
| label | example value |
|---|---|
| chip | ESP32-C6 rev 0.1 |
| flash | 4 MB · quad, 80 MHz |
| mac | A0:85:E3:4C:9F:1D |
| on board now | serin-homekit 2026.4.2 (amber #F09A28) |
Below the card: firmware segmented control, then a monospace line showing the build that will be written and how it compares — "serin-homekit 2026.7.1" / "newer than the board" (green). Comparison text is one of: newer than the board (green), same version — reflash (muted), older than the board (amber), different platform (amber). Then the erase toggle: amber-tinted box, checkbox, "Erase everything first" + "Off by default, so Wi-Fi and pairing survive the update. Tick it to hand the board to someone else." Primary button label is version-aware: "Update to 2026.7.1" when a Serin build is already present, "Install firmware" on a blank or foreign board.
Right panel: "Board read. Nothing written yet." + reassurance copy; a three-row "What happens when you press it" list with right-aligned durations (~100 s / ~10 s / your pace); and two bottom cards — "Already installed in the wall?" (push over Wi-Fi instead) and "Switching platforms" (HomeKit → ESPHome wipes pairing; remove from the Home app first). The second card only appears when the selected platform differs from the detected one.
State 3 — writing
Bay: whole column at opacity: 0.55, pill turns amber "busy", device card collapses
to name + "ESP32-C6 · 4 MB" + MAC. Segmented control, erase toggle and links are disabled
(not hidden). Primary button becomes an inert rgba(255,255,255,0.08) block reading
"Writing…" with "Leave the cable alone." beneath.
Right panel: headline "Writing the app partition" (changes per phase), sub-line
"Unplugging now is recoverable — just start again.", big monospace percentage #F09A28
30px, then a 6px progress bar (#F09A28 on rgba(255,255,255,0.1)). Below it the phase
list, four rows, same grid as state 2 but with status markers: green ✓ + "done" for
finished, amber "›" + live byte count for the current one, dimmed index + "queued" for
the rest. Footer row: Wi-Fi/2.4 GHz reminder on the left, "view log" (monospace, opens the
raw esptool output in a disclosure) on the right.
State 4 — done
Bay: pill "online". Card head is tinted rgba(155,232,176,0.08) and shows the device
name Serin-4C9F with the installed build beneath. Table rows: network / address /
signal / cn105 link. The CN105 row reads "not connected yet" in amber until the board is
plugged into the indoor unit, with a 13.5px note explaining that is expected on a bench.
Secondary button "Open the device web UI" (outlined) links to http://<ip>/; monospace
line beneath: "saved to this browser · flash another board" (the second half restarts the
flow).
Right panel: green ✓ + "Flashed and on your Wi-Fi", elapsed time and "cable can come out" on the right. Two cards side by side:
- Add to Apple Home — amber-tinted, 112px QR on white, copy, and the pairing code in
monospace 15px
#F09A28(418-27-936format). Only rendered for the HomeKit build. - Wi-Fi never asked? — outlined, hotspot QR + "Serin-4C9F · serinlabs".
Footer row: "This code is derived from the board's MAC, so it is the same every time you
come back to this page." + primary "Wire it up →" (
/homekit/setup.html, or/esphome/setup.htmlfor the ESPHome build).
For the ESPHome build, state 4 replaces the pairing card with an "Adopt in Home Assistant"
card (discovery note + link to /esphome/setup.html) and keeps the hotspot card.
Detected data — what to read and from where
All of it is read in-tab over Web Serial; nothing is transmitted. Sources:
- Board name — from
SerialPort.getInfo()usbVendorId/usbProductId, matched against a small lookup table in the page (303A:1001etc.). No match → hide the board-name row and show only the chip. - Chip family + revision, flash size/speed, MAC — from the esptool JS
chipName/getChipDescription()/getFlashSize()/readMac()used byesp-web-tools. The MAC is already parsed on today's page by scrapingconsole.log; replace that hack — call the API directly, or fork the button component, rather than interceptingconsole.log. - Firmware already on the board — from the Improv Serial handshake
device_info(firmware name + version) thatesp-web-toolsperforms when it opens the port. A board that does not answer Improv shows "on board now: unrecognised firmware". - SSID / IP / signal (state 4) — returned by the Improv Wi-Fi step and its device URL. If the flow ends without an IP, drop those rows and show the hotspot card only.
- Pairing code + hotspot SSID — keep the existing derivation from the MAC in today's
flash.html(fnv1a32→ 8 digits → HAP URI, AP nameSerin-+ last two MAC bytes). Carry thelocalStorageserin_last_devicewrite over as-is.
Any field that cannot be read is omitted, never shown empty.
Behavior & other requirements
- Platform choice still deep-links:
/flash.html?platform=esphome|homekitpreselects the segmented control; keep theplatform-presetvsplatform-selectanalytics split. - Keep all existing analytics events:
flash-start,flash-device-connected,flash-flow-complete, plus the platform events. Addflash-erase-toggledif cheap. - Erase-everything is off by default and maps to the esp-web-tools erase option.
- Errors: on a failed or cancelled flash, the panel returns to
detected(oridleif the port closed) with an amber strip above the right-panel headline — one sentence plus a link to/troubleshooting.html. Never leave the progress bar frozen. - Unsupported browser: bay button disabled, header check row amber, right panel keeps the explanatory content so the page still reads.
- Responsive: below ~1000px the panel becomes one column — bay first, right panel below; the two bottom cards and the two QR cards stack. Below ~600px the monospace tables keep label/value on one line but drop to 12px. Tap targets ≥44px.
- Accessibility: segmented control is a real radio group; status pill text is in an
aria-live="polite"region; progress usesrole="progressbar"witharia-valuenow; the QR images carry the pairing code as alt text.
Design tokens
- Console surfaces: outer band
#121316, panel#1A1B1E, right column#16171A. - Hairlines on dark:
rgba(255,255,255,0.12)(structural),rgba(255,255,255,0.08)(table rows). - Text on dark:
#fff,rgba(255,255,255,0.8),.65,.55,.45,.4,.3. - Amber: action
#F09A28, hover#D4871A, tint on darkrgba(240,154,40,0.09)with borderrgba(240,154,40,0.35), text-on-light#8A5A00, tint-on-light#FEF5E6. - Success green
#9BE8B0(dot, ✓, tintrgba(155,232,176,0.08–0.12)) — new to the palette, used only for device status. - Light bands:
#fff,#F3F3F0, canvas#E8E8E4; borders#E5E5E2/#D8D8D2; ink#1A1A1A, body#5A5A5A, muted#6E6E6E, faint#8E8E86. - Type: Manrope 400–800; JetBrains Mono 400/500 for all machine data (chip, MAC, versions, durations, byte counts, status pills).
- Scale: 42 / 30 / 21 / 20 / 17 / 16.5 / 16 / 15.5 / 15 / 14.5 / 14 / 13.5 / 13 / 12.5 / 12 / 11.5 px.
- Radius: 7 (segment, QR), 8 (thumbnails), 9 (buttons), 11 (inner cards), 14 (panel), 20 (pills).
- Spacing: 48px gutters; panel columns 24–26px padding; card padding 16–18px; gaps 6/9/10/14/16/18/20/22px.
Assets
assets/serin_logo.svg— existing site asset, included so the reference renders.- Two image slots to fill: the 46px board thumbnail in the device bay and the 60px
supported-boards thumbnail in state 1. Existing photography can be cropped for both
(
serin_cn105_controller.jpg). QR codes are generated at runtime by the existingqrcodejsdependency. - Fonts: Manrope + JetBrains Mono, already loaded by the site.
Files
flash-2a-reference.dc.html— the design reference (option 2a with all four states).support.js,assets/serin_logo.svg— needed to open the reference locally.- Full exploration including options 1b and 1c:
Serin Flash Redesign.dc.htmlin the project root. - Page being replaced:
flash.html(Jekyll source), which holds the pairing-code derivation, analytics calls andesp-web-toolswiring to carry over.