/* config.txt editor dialog. */

#cfg-overlay {
  position: fixed; inset: 0; z-index: 30;
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(3px);
  display: grid; place-items: center; padding: 24px;
}

.cfg-modal {
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  width: min(760px, 100%); max-height: 100%;
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 12px 40px rgb(0 0 0 / 0.28);
}

.cfg-head, .cfg-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; background: var(--card); flex: none;
}
.cfg-head { border-bottom: 1px solid var(--line); }
.cfg-foot { border-top: 1px solid var(--line); flex-wrap: wrap; }
.cfg-head .title {
  font-size: 12px; font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--muted);
}
.cfg-head .path { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.cfg-head .grow, .cfg-foot .grow { flex: 1; }

.cfg-body { overflow: auto; padding: 4px 16px 16px; background: var(--bg); }

.cfg-lede {
  color: var(--muted); font-size: 12px; line-height: 1.55;
  margin: 14px 0 4px; max-width: 70ch;
}

/* One row per key: name + control on a line, everything explanatory folded
   away behind the (i). config.txt's keys are not self-explanatory and the
   tuning ones are actively counter-intuitive, so the text has to exist — but
   fourteen paragraphs at once is a document, not a settings screen. */
.cfg-row {
  display: grid; grid-template-columns: 1fr auto;
  gap: 4px 16px; align-items: center;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.cfg-row.open { padding-bottom: 12px; }

.cfg-row .cfg-name {
  grid-column: 1; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.cfg-row label {
  font-size: 13px; font-weight: 500; cursor: pointer;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.cfg-row label .key {
  font-family: var(--mono); font-size: 11px; color: var(--muted); font-weight: 400;
}

/* Disclosure affordance. Sized to read as a marginal note rather than an
   action — it must not compete with the control it explains. */
.cfg-info {
  flex: none; width: 17px; height: 17px; padding: 0;
  border-radius: 50%; border: 1px solid var(--line);
  background: transparent; color: var(--muted);
  font-family: var(--mono); font-size: 11px; font-style: italic; line-height: 1;
  align-self: center;
  /* base.css floors every button at --minhit (36px, 44px on a coarse
     pointer) for a thumb-sized tap target. **min-height beats height in the
     cascade**, so without this line the used box is 17px wide by 36px tall
     and the 50% radius draws an ellipse, not a circle. Any round icon button
     added later will need the same line — see the guard in render.test.mjs. */
  min-height: 0;
  position: relative;
}

/* Giving the height back as a hit area rather than as pixels: an invisible
   --minhit square centred on the 17px dot. The dot stays a marginal note that
   does not compete with the control it explains, and the thumb still gets the
   target base.css intends. Inside the button, so it cannot steal the click
   from it; the only thing it overlaps is the 8px gap to the label, whose own
   target is the whole label. */
.cfg-info::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: var(--minhit); height: var(--minhit);
  transform: translate(-50%, -50%);
}
.cfg-info:hover { border-color: var(--accent); color: var(--accent); }
.cfg-row.open .cfg-info {
  border-color: var(--accent); color: var(--accent-fg); background: var(--accent);
}

.cfg-row .ctl {
  grid-column: 2; justify-self: end;
  display: flex; align-items: center; gap: 8px;
}
/* Sits in the label now, next to the title — see ConfigDialog.js. Quieter
   than the title and not monospace: the mono face in this row is reserved for
   the config.txt key, which is a literal you type, and a unit is not. */
.cfg-row label .unit {
  font-size: 11px; color: var(--muted); font-weight: 400; margin-left: 4px;
}

/* The folded-away half. */
.cfg-help { grid-column: 1 / -1; padding-top: 2px; }
/* The narrow-screen home of the config.txt key — hidden while the title line
   still has room for it. See the media query below. */
.cfg-help .key-echo { display: none; }
.cfg-row .desc {
  color: var(--muted); font-size: 12px; line-height: 1.5; max-width: 72ch;
}
.cfg-row .note {
  grid-column: 1 / -1; font-size: 12px; line-height: 1.5;
  color: var(--muted); font-style: italic; max-width: 72ch;
}
.cfg-help .note { margin-top: 4px; }
.cfg-row .note.err { color: var(--danger); font-style: normal; font-family: var(--mono); font-size: 11px; }
.cfg-row.invalid input, .cfg-row.invalid select { border-color: var(--danger); }

/* Rows whose control is a whole editor rather than one input: the top line
   carries only the name and the reset chip, the editor sits below it. */
.cfg-row.wide { align-items: baseline; }
.cfg-wide { grid-column: 1 / -1; padding-top: 6px; }

.cfg-row input[type=text], .cfg-row input[type=number], .cfg-row select {
  font: inherit; font-family: var(--mono); font-size: 13px;
  padding: 4px 8px; border: 1px solid var(--line); border-radius: 4px;
  background: var(--card); color: var(--fg); outline: none;
}
.cfg-row input[type=number] { width: 9ch; text-align: right; }
.cfg-row input[type=text]   { width: 28ch; max-width: 40vw; }
.cfg-row input:focus, .cfg-row select:focus { border-color: var(--accent); }
.cfg-row input[type=checkbox] { accent-color: var(--accent); width: 16px; height: 16px; }

/* The "default" chip doubles as a reset button, and lights up when the field
   no longer matches what the device has stored. */
.cfg-def {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  background: transparent; border: 1px dashed var(--line);
  padding: 1px 7px; border-radius: 3px; white-space: nowrap;
}
.cfg-def:hover:not(:disabled) { border-color: var(--accent); color: var(--fg); }
.cfg-row.modified .cfg-def {
  border-style: solid; border-color: var(--accent); color: var(--accent);
}

.cfg-section {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--muted); font-weight: 600;
  padding: 20px 0 6px; border-bottom: 1px solid var(--line);
}

.cfg-banner {
  margin: 10px 0 0; padding: 8px 11px;
  border: 1px solid var(--line); border-left-width: 3px;
  border-radius: 4px; background: var(--card);
  font-size: 12px; line-height: 1.5; color: var(--muted);
}
.cfg-banner.warn { border-left-color: var(--warn); }
.cfg-banner.err  { border-left-color: var(--danger); color: var(--fg); }
.cfg-banner.ok   { border-left-color: var(--accent); }

.cfg-raw { margin-top: 16px; }
.cfg-extra {
  margin: 8px 0 0; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 4px; background: var(--card);
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  white-space: pre-wrap; display: none; overflow-x: auto;
}
.cfg-extra.shown { display: block; }

.cfg-status { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.cfg-status.err { color: var(--danger); }

/* --- ble_firmware_mapping rule editor --------------------------- */

/* The packed "name:glob|name:glob" string is a wire format, not something to
   type. Each rule gets a row that reads as the sentence it is: when the peer's
   advertised name contains X, flash the file matching Y. */
.map-editor {
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); padding: 8px 10px;
}
.map-empty {
  margin: 2px 0 8px; color: var(--muted); font-size: 12px; line-height: 1.5;
}
.map-rule {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 4px 0;
}
.map-rule .lead, .map-rule .arrow {
  color: var(--muted); font-size: 11px; white-space: nowrap;
}
.map-rule .arrow { font-family: var(--mono); }
.map-rule input {
  font: inherit; font-family: var(--mono); font-size: 12px;
  padding: 4px 7px; border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg); color: var(--fg); outline: none;
  min-width: 0; width: auto;      /* beat .cfg-row input[type=text]'s 28ch */
}
.map-rule input:focus { border-color: var(--accent); }
.map-rule .map-name { flex: 1 1 9ch; }
.map-rule .map-file { flex: 2 1 16ch; }
.map-rule.incomplete input:placeholder-shown { border-color: var(--danger); }
.map-rule .map-ops { display: flex; gap: 3px; margin-left: auto; }
/* The mobile stand-in for the arrow — see the narrow-screen block. */
.map-rule .lead-file { display: none; }
.map-rule .map-ops button { padding: 1px 6px; font-size: 11px; }

.map-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 6px; padding-top: 7px; border-top: 1px solid var(--line);
}
.map-foot .grow { flex: 1; }
.map-hint { color: var(--muted); font-size: 11px; }
.map-size {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.map-size.err { color: var(--danger); }

/* --- USB flasher ------------------------------------------------ */

/* Shares .cfg-modal / .cfg-head / .cfg-body / .cfg-section with the config
   editor — it is the same kind of screen and should not invent a second
   visual language for it. */
#flash-overlay {
  position: fixed; inset: 0; z-index: 30;
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(3px);
  display: grid; place-items: center; padding: 24px;
}

/* Which board is being flashed. A segmented row rather than a <select>: there
   are three of them, they are the whole reason this dialog has branches, and a
   collapsed control would hide the fact that a choice was made at all. */
.flash-boards {
  display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 10px;
}
.flash-board {
  font-family: var(--sans); font-size: 12px;
  padding: 5px 11px; border: 1px solid var(--line); border-radius: 4px;
  background: var(--card); color: var(--muted); cursor: pointer;
}
.flash-board:hover:not(:disabled) { color: var(--fg); }
.flash-board.on {
  background: var(--accent); border-color: var(--accent); color: var(--accent-fg);
}
.flash-board:disabled { opacity: .5; cursor: default; }

/* The button dance that puts a board in its bootloader. Numbered because the
   order matters on both boards and neither is guessable. */
.flash-steps {
  margin: 0 0 4px; padding-left: 20px;
  font-family: var(--sans); font-size: 12px; color: var(--muted); line-height: 1.6;
}
.flash-steps li { padding: 2px 0; }

.flash-step {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 0;
}
.flash-step .grow { flex: 1; }
.flash-step .mono { font-family: var(--mono); font-size: 12px; }
.flash-ok { color: var(--accent); font-size: 11px; }

.flash-note { color: var(--muted); font-size: 12px; }
a.flash-note { color: var(--accent); }
.flash-note.warn { color: var(--warn); }
.flash-note.err  { color: var(--danger); }

.flash-progress { display: flex; align-items: center; gap: 10px; padding: 6px 0 2px; }
.flash-progress .bar {
  flex: 1; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden;
}
.flash-progress .fill {
  height: 100%; background: var(--accent); transition: width 120ms linear;
}
.flash-progress .mono {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Its own log, not the app's: none of this goes over BLE, and interleaving
   SWD chatter with SMP traffic would make both harder to read. */
.flash-log {
  margin: 12px 0 0; padding: 10px 12px; max-height: 26vh; overflow: auto;
  border: 1px solid var(--line); border-radius: 4px; background: var(--card);
  font-family: var(--mono); font-size: 11px; line-height: 1.55;
  color: var(--muted); white-space: pre-wrap;
}
.flash-log .err  { color: var(--danger); }
.flash-log .ok   { color: var(--accent); }
.flash-log .warn { color: var(--warn); }

/* --- device log viewer ------------------------------------------ */

#log-overlay {
  position: fixed; inset: 0; z-index: 30;
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(3px);
  display: grid; place-items: center; padding: 24px;
}
/* Wider and taller than the config modal — a log is read by scanning, and a
   narrow column turns every line into two. */
#log-overlay .cfg-modal { width: min(1000px, 100%); height: min(800px, 100%); }

.logv-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 16px; border-bottom: 1px solid var(--line);
  background: var(--card); flex: none;
}
.logv-bar .grow { flex: 1; }
.logv-bar select, .logv-bar input {
  font: inherit; font-family: var(--mono); font-size: 12px;
  padding: 3px 7px; border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg); color: var(--fg); outline: none;
}
.logv-bar select:focus, .logv-bar input:focus { border-color: var(--accent); }
.logv-find { flex: 0 1 22ch; min-width: 10ch; }

.logv-status {
  padding: 6px 16px; border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  background: var(--bg); flex: none;
}
.logv-status .err  { color: var(--danger); }
.logv-status .warn { color: var(--warn); }

.logv-body {
  flex: 1; margin: 0; padding: 10px 16px; overflow: auto;
  background: var(--bg); color: var(--muted);
  font-family: var(--mono); font-size: 11px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}
/* Timestamp and module are context, not content — dim them so the message
   itself is what the eye lands on. */
.logv-body .ts  { opacity: 0.45; margin-right: 8px; }
.logv-body .mod { opacity: 0.7; margin-right: 8px; }
.logv-body .lvl {
  display: inline-block; min-width: 3ch; margin-right: 8px;
  text-transform: uppercase; font-size: 10px; opacity: 0.9;
}
.logv-line.lv-err  { color: var(--danger); }
.logv-line.lv-wrn  { color: var(--warn); }
.logv-line.lv-inf  { color: var(--fg); }
.logv-line.lv-dbg  { opacity: 0.75; }
/* Unparsed output — a fault dump, a raw printk, a truncated tail. Usually the
   line you opened the log to find. */
.logv-line.lv-raw  { color: var(--fg); opacity: 0.9; }

/* ================= the scanner ===================================
   Shares .cfg-modal / .cfg-head / .cfg-body with the config editor for the
   same reason the flash dialog does. The table copies the file listing's
   layout deliberately — it is the same kind of object (a list of things with
   a name, a property and per-row actions) and inventing a second visual
   language for it would make the app feel like two apps. */
#scanner-overlay {
  position: fixed; inset: 0; z-index: 30;
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(3px);
  display: grid; place-items: center; padding: 24px;
}

.scan-state {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.08em;
}
/* A quiet pulse, so "scanning" differs from "idle" at a glance without
   another line of text. Dropped for anyone who asked for less motion — the
   word beside it already carries the meaning. */
/* The word still says it, but the tab's own icon is now the primary signal —
   it is where the eye already is, and it says *which* radio is sweeping,
   which the word cannot. */
.scan-state.live { color: var(--accent); animation: scan-pulse 1.6s ease-in-out infinite; }
.scan-tabs button.on .icon-cycle { color: var(--accent); }
@keyframes scan-pulse { 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) { .scan-state.live { animation: none; } }

/* One radio at a time is a constraint of the device, not a preference, so it
   gets tabs rather than a filter control that would imply both could be on. */
.scan-tabs { display: flex; gap: 4px; padding: 12px 0 4px; }
.scan-tabs button {
  border-color: transparent; color: var(--muted);
  border-bottom: 2px solid transparent; border-radius: 6px 6px 0 0;
  display: inline-flex; align-items: center; gap: 6px;
}
.scan-tabs button.on {
  color: var(--fg); border-bottom-color: var(--accent); background: var(--card);
}

.scan-legend {
  color: var(--muted); font-size: 12px; line-height: 1.55;
  margin: 10px 0; max-width: 70ch;
}
.scan-legend .band { font-weight: 600; }
.scan-legend .band.excellent { color: var(--ok); }
.scan-legend .band.good      { color: var(--warn); }
.scan-legend .band.poor      { color: var(--danger); }

.scan-filter {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--fg); margin: 0 0 10px;
}

/* font-size is stated, not inherited. It used to come from layout.css's bare
   `table { font-size: 13px }`, which was scoped to .file-table once the
   listing's rules turned out to be styling this table too — correct, but it
   left this one taking the body size, a point larger, which is what pushed
   the fixed columns below over their widths on a phone. Every em length in
   the narrow-screen block is measured against this number. */
.scan-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.scan-table th {
  text-align: left; font-size: 11px; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.08em;
  padding: 6px 8px; border-bottom: 1px solid var(--line);
}
/* Vertical padding is deliberately small, because it is not what sets the row
   height — the Flash button is. base.css floors every button at --minhit for a
   thumb (28px here via .small, and the full 36/44px on a coarse pointer), and
   8px of cell padding on top of that made a 44px row for one line of 13px
   text. Trimming the padding leaves the tap target exactly as large as it was
   and takes the airiness out of the table, which is the one screen that is
   meant to be scanned down. */
.scan-table td {
  padding: 4px 8px; border-bottom: 1px solid var(--line); vertical-align: middle;
}
.scan-table td.empty { color: var(--muted); text-align: center; padding: 20px; }
.scan-table td.actions { text-align: right; white-space: nowrap; }

/* Signal is a glyph *and* a colour. Colour alone would exclude anyone who
   cannot separate the red from the green, on the one screen whose whole
   output is a judgement about quality — so the bar count carries the same
   information independently. */
/* inline-flex, and on a <span> inside the cell rather than on the <td>. A
   table cell given display:flex is no longer a table-cell: it stops
   stretching to the row height and stops taking the column's width, so its
   bottom border drew higher than the rest of the row's and stopped short of
   the next column. */
.scan-sig { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
/* Tabular figures: this number updates while somebody moves an antenna, and
   it must not jitter sideways as it changes. */
.scan-dbm { font-family: var(--mono); font-size: 12px; font-variant-numeric: tabular-nums; }
.scan-row.excellent .scan-sig { color: var(--ok); }
.scan-row.good      .scan-sig { color: var(--warn); }
.scan-row.poor      .scan-sig { color: var(--danger); }

/* The rows an automatic run would have gone for. A left edge and a tinted
   ground rather than a colour on the text: the signal columns already own
   colour on this screen, and a second meaning for it would make both harder
   to read. */
.scan-row.hit td { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.scan-row.hit td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.scan-name { font-weight: 600; }
.scan-badge {
  font-size: 10px; font-weight: 700; letter-spacing: 0.08em;
  padding: 2px 6px; border-radius: 4px; margin-left: 6px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
}
.scan-badge.lock { background: none; padding: 0; }
.scan-id { color: var(--muted); font-size: 12px; }
.scan-id code { font-family: var(--mono); font-size: 11px; }

.scan-pick-row td { background: var(--card); }
.scan-file {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  text-align: left; width: 100%; margin-bottom: 6px;
}
.scan-file-name { font-family: var(--mono); font-size: 12px; }

.scan-auto {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--fg);
}
.cfg-foot .scan-auto + button { margin-left: 4px; }

@media (max-width: 640px) {
  #scanner-overlay { padding: 0; }
  #scanner-overlay .cfg-modal {
    width: 100%; height: 100%; max-height: 100%; border-radius: 0; border: none;
  }
  /* The address is the first thing worth dropping on a narrow screen: it is
     the longest column and the least useful for telling two rows apart.

     **Collapsed to zero width, not `display: none`.** Removing a header cell
     removes its *column*, and the table still has a row that spans all four —
     the file picker a Flash press opens. That colspan then invents the fourth
     column back, as an auto-width one placed after the others, and it takes an
     equal share of whatever the sized columns left over: measured at 369px,
     the name column got 88px instead of 175 and every ordinary row stopped
     87px short of the picker beneath it. The row looked like it was not using
     the full width because it was not.

     visibility rather than a colour, so nothing is announced to a screen
     reader either, and overflow because a 0-width cell would otherwise paint
     its content across the neighbour. */
  .scan-table th:nth-child(3), .scan-table td.scan-id {
    width: 0; padding-left: 0; padding-right: 0;
    overflow: hidden; visibility: hidden;
  }

  /* It stays a table here — three columns fit on a phone once the address is
     gone, and the whole value of this screen is comparing rows down a column.
     Stated explicitly because it did not used to be: the listing's own narrow
     rules were written against a bare `tbody tr` and turned every cell in
     here into its own full-width row. They are scoped now (see layout.css),
     and these give the columns their proportions rather than leaving three
     variable-width cells to fight over the line. */
  .scan-table { table-layout: fixed; }
  /* Fixed rather than auto, because these names have no spaces in them —
     "BLESmart_0000025128FFB2" is one word as far as a browser is concerned, so
     auto layout widens the table to fit it and the whole panel scrolls
     sideways. Fixed pins the two known-width columns and lets the name wrap
     inside what is left. Wrapping and not an ellipsis: two devices in a room
     often differ only in their last few characters. */
  .scan-table td { padding: 8px 6px; }
  .scan-table td.scan-name { overflow-wrap: anywhere; }

  /* The two fixed columns, sized to their content with room around it rather
     than to the smallest number that fits. Both were too tight: the table
     overflowed the panel by a few pixels — enough for a horizontal scrollbar
     — and the Flash button ended up against the signal reading on one side
     and the edge of the screen on the other.

     **In px, and that is not a style choice.** With table-layout: fixed the
     column widths are taken from the *first row* — the header — so an `em`
     here resolves against the `th`'s font-size, which is 11px, and not
     against the 13px the cells below are set in. Written as 9em the actions
     column came out 99px rather than the 117px intended, the 89px button plus
     its 14px offset overflowed the cell by 4px, and the button ended up
     8px from the edge of the screen instead of lining up with the panel's
     12px gutter. Measured, not guessed: 18 + 6 + three mono digits is 50px,
     and icon + gap + "Flash…" inside 6/12px of padding is 89px. */
  .scan-table th:nth-child(2), .scan-table td.scan-sig-cell { width: 64px; }
  .scan-table th:last-child, .scan-table td.actions { width: 106px; }

  /* The right edge of the table carries no padding of its own, so the
     button's right edge lines up with everything else against the panel's
     12px gutter. The gap the button needs from the signal column comes from
     the cell's left padding, where it is one number and not the sum of two.

     The *left* edge keeps a little, and 9px is not a rounding of 8: a matched
     row draws its accent stripe as a 3px inset shadow on this cell, so
     anything under about 6px puts the stripe under the first letter of the
     device name. At padding 0 — which is what lining the column up with the
     lede above it asked for — it clipped the "X" of XIAO_NRF52_OTA, on the
     one row in the table that matters most. Same arrangement as the file
     listing, where the name is indented past its own stripe. */
  .scan-table th:first-child, .scan-table td.scan-name { padding-left: 9px; }
  .scan-table th:last-child, .scan-table td.actions {
    padding-left: 14px; padding-right: 0;
  }
}

/* --- touch / small screens -------------------------------------- */

/* A modal centred with 24px of padding wastes a third of a phone screen and
   pushes the footer buttons under the browser chrome. Go full-bleed instead. */
@media (max-width: 640px) {
  #cfg-overlay, #flash-overlay, #log-overlay { padding: 0; }
  #cfg-overlay .cfg-modal,
  #flash-overlay .cfg-modal,
  #log-overlay .cfg-modal {
    width: 100%; height: 100%; max-height: 100%; border-radius: 0; border: none;
  }
  .cfg-head, .cfg-foot { padding: 10px 12px; }
  .cfg-body { padding: 4px 12px 20px; }

  /* Label above control rather than beside it — 28ch of monospace input does
     not share a line with a title on a 360px screen. */
  /* Label above control rather than beside it — 28ch of monospace input does
     not share a line with a title on a 360px screen.

     The control keeps the right edge it has on a desktop, where it is the
     second grid column. Dropping it to the left was the obvious thing to do
     when it moved onto its own line and it was wrong: the eye then has to
     start again at the left margin for every row, and the values stop forming
     a column you can run down. Stretched and end-justified, so a checkbox or
     a number sits under the right margin and a text field, which has nothing
     to align with, takes the whole line. */
  /* The config.txt key leaves the title line. A title, a monospace key and an
     (i) is three things competing for one line at 360px, and the key is the
     one a reader does not need in order to recognise the setting — it matters
     when you go to edit the file by hand, which is not what this screen is
     for. It moves into the disclosure rather than disappearing: dropping it
     would leave no way at all to learn a key from a phone, and the (i)
     button's tooltip does not exist on a touch screen. */
  .cfg-row label .key { display: none; }
  .cfg-help .key-echo {
    display: block; font-family: var(--mono); font-size: 11px;
    color: var(--muted); margin-bottom: 3px;
  }

  .cfg-row { grid-template-columns: 1fr; gap: 8px; padding: 12px 0; }
  .cfg-row .ctl {
    grid-column: 1; justify-self: stretch; justify-content: flex-end;
    flex-wrap: wrap;
  }
  .cfg-row input[type=text] { flex: 1 1 100%; width: 100%; max-width: none; }
  .cfg-row select { max-width: 100%; }
  .cfg-row input[type=number] { width: 8ch; }
  .cfg-row input[type=checkbox] { width: 22px; height: 22px; }

  /* A rule is one sentence — "name contains X → flash Y" — and on one line it
     reads as one. Stacked, it does not: the two connective words were left
     where a single-line layout put them, so "name contains" became a heading
     and the arrow ended up alone on a line of its own, pointing at nothing.
     Each field gets its own label above it instead, and the arrow — which only
     means anything *between* two things — is dropped for the word it stood
     for. */
  .map-rule { gap: 6px; }
  .map-rule .lead { flex: 1 1 100%; }
  .map-rule .arrow { display: none; }
  .map-rule .lead-file { display: block; }
  .map-rule .map-name, .map-rule .map-file { flex: 1 1 100%; }
  /* Right, with the values, for the same reason as .ctl above. */
  .map-rule .map-ops { margin-left: auto; }

  .flash-step { gap: 8px; }
  .logv-bar { padding: 8px 12px; }
  .logv-find { flex: 1 1 100%; }
  .logv-body { font-size: 12px; padding: 10px 12px; }
}

/* Selects and text inputs must not be under the 16px that makes iOS Safari
   zoom the page on focus — the zoom does not undo itself and leaves the user
   scrolled sideways. */
@media (pointer: coarse) {
  .cfg-row input, .cfg-row select,
  .map-rule input, .logv-bar input, .logv-bar select { font-size: 16px; }
}

/* Live-stream indicator. Pulses only while streaming, and only when the
   viewer allows motion — this can be on screen for the length of a transfer. */
.live-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted); flex: none;
}
.live-dot.on { background: var(--accent-fg); animation: livepulse 1.6s ease-out infinite; }
@keyframes livepulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-fg) 70%, transparent); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) { .live-dot.on { animation: none; } }

.logv-follow {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--muted); white-space: nowrap;
}
.logv-follow input { accent-color: var(--accent); width: 16px; height: 16px; }

/* --- update-over-Bluetooth ------------------------------------- */

/* A sub-heading inside a route, one step down from .cfg-section — the USB
   flow's own stages, now that Bluetooth and USB are the top-level split. */
.cfg-section-sub {
  font-size: 11px; letter-spacing: 0.06em; color: var(--muted);
  font-weight: 600; padding: 14px 0 4px;
}

.upd { padding-bottom: 4px; }
.upd-status { margin: 6px 0 0; color: var(--muted); font-family: var(--sans); }

/* ---- PinDialog -------------------------------------------------------
 *
 * Shares .cfg-modal / .cfg-head / .cfg-body / .cfg-foot with the config
 * editor, as the scanner and flash dialogs do. What is specific here is the
 * one input: it wants to be big enough to read back at a glance, because it
 * is transcribed from another device's screen against a clock and a
 * mistyped digit costs the whole pairing.
 */
#pin-overlay {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 16px; background: rgba(0, 0, 0, 0.5);
}
.pin-modal { max-width: 380px; width: 100%; }
.pin-lead { margin: 12px 0 14px; line-height: 1.5; }
.pin-input {
  width: 100%; box-sizing: border-box;
  font-family: var(--mono);
  /* 28px and letter-spaced: this is read off one screen and typed into
     another, which is exactly when a 6 and an 8 get confused. */
  font-size: 28px; letter-spacing: 0.32em; text-align: center;
  padding: 10px 8px;
}
.pin-err { color: var(--danger); margin: 10px 0 0; }
/* Not --accent: the accent is a user preference with seven settings, one of
   which is red, and a countdown that turns "urgent" in the colour it was
   already using says nothing. Same rule as the scanner's --ok token. */
.pin-clock { color: var(--muted); margin: 12px 0 0; font-size: 12px; }
.pin-clock.urgent { color: var(--danger); }
/* The retry ask has no clock — nothing is counting down — so this sits in the
 * same slot and says what the PIN is scoped to instead. */
.pin-note { color: var(--muted); margin: 12px 0 0; font-size: 12px; line-height: 1.5; }
.pin-note code { font-family: var(--mono); }
