/* The FIRMWARE code editor: a file explorer and a read-only code pane, in the
   panel that used to say "FIRMWARE has not produced a mesh".

   The chrome (tree, bars, flash panel) follows the themed tokens like every
   other card. The CODE pane used to be a near-black --tile-dark-* well in BOTH
   themes (2026-08-24); since 2026-09-14 it follows the site theme too, so light
   mode is a light well. Syntax inks (--syn-*) consume the mode-resolving
   --acc-* family from tokens.css (600/700 on light, 400 on .dark) rather than
   400-tier literals locked to #171717. Tree icon tints and origin chips sit on
   the same chrome and use the same family. */

:root {
  --syn-key: var(--acc-indigo);
  --syn-typ: var(--acc-cyan);
  --syn-str: var(--acc-emerald);
  --syn-num: var(--acc-amber);
  --syn-com: var(--text-muted);
  /* Light: an inner well on the white editor card. Dark: the Atech-UI near-black
     tile, still a step up from the page. Hover must be OPAQUE — the gutter is
     sticky, and a translucent background lets scrolled code show through it. */
  --code-bg: var(--input-bg);
  --code-border: var(--border-light);
  --code-text: var(--text-primary);
  --code-muted: var(--text-muted);
  --code-hover: color-mix(in srgb, var(--code-text) 6%, var(--code-bg));
  --code-thumb: color-mix(in srgb, var(--code-text) 18%, transparent);
  --code-sel: color-mix(in srgb, var(--acc-indigo) 22%, transparent);
  /* The same hue family .ent.k-llm uses for "a model call" in the activity
     trail (indigo, var(--acc-indigo) after the port), so the tree and the
     trail agree about what a model touched without the reader having to learn
     a second key. The chip sits on themed chrome, hence the badge pair
     (info = indigo) rather than a dark-tile literal. */
  --og-model: var(--state-info);
  --og-model-bg: var(--state-info-bg);
}

.dark {
  --code-bg: var(--tile-dark-bg);
  --code-border: var(--tile-dark-border);
  --code-text: var(--tile-dark-text);
  --code-muted: var(--tile-dark-muted);
  --code-hover: color-mix(in srgb, var(--code-text) 8%, var(--code-bg));
  --code-thumb: rgba(255, 255, 255, 0.16);
  --code-sel: rgba(255, 255, 255, 0.25);
}

/* ── mount ────────────────────────────────────────────────────────────────
   The takeover shape every non-mesh stage uses (#handoff, #software, #wiring —
   the same rule set, in index.html): absolutely positioned over the viewport,
   shown by a class on #viewport. The !important is beating an INLINE style, not
   guessing at the cascade — #vp-empty carries a display the viewport
   code writes on the element itself, which would otherwise float the last mesh's
   dimensions over the code. The WebGL canvas is hidden, never torn down:
   renderFirmware must not touch state.vp, or returning to ELECTRONICS — the one
   stage that still draws a mesh — rebuilds the scene from nothing. */
/* overflow:hidden is load-bearing, not tidiness: the narrow-container rule
   below parks the file drawer at translateX(-101%), and without clipping that
   parked drawer renders OUTSIDE the panel, on top of the activity trail. */
#fw { position: absolute; inset: 0; display: none; flex-direction: column;
      min-height: 0; overflow: hidden; }
#viewport.code #fw,
#viewport.overview #fw { display: flex; }
/* Focused firmware hides the board chrome; overview keeps both cards painted,
   so these hides are scoped off `.overview` (2026-09-15 deck). */
#viewport.code:not(.overview) #vp-host, #viewport.code:not(.overview) #vp-empty,
#viewport.code:not(.overview) #gizmo, #viewport.code:not(.overview) #vp-tools {
  display: none !important;
}
/* No gradient behind code — a tinted page behind a code pane reads as a
   theme the syntax colours were not chosen against. */
#viewport.code:not(.overview) { background: var(--surface-primary); }

/* ── Code / Advanced tabs (2026-09-14) ─────────────────────────────────────
   Same chip vocabulary as the old Firmware/Software toggle: mono, quiet
   until selected. Flex-none so the panel below keeps the remaining height. */
#fw-tabs { flex: none; display: flex; gap: 2px; padding: 8px 10px 0;
           background: var(--surface-secondary); }
.fw-tab { font-family: 'Geist Mono', ui-monospace, monospace; font-size: 10.5px;
          letter-spacing: .04em; padding: 5px 12px; border-radius: 6px 6px 0 0;
          border: 1px solid transparent; border-bottom: 0;
          background: transparent; color: var(--text-muted); cursor: pointer;
          line-height: 1.5; }
.fw-tab:hover { color: var(--text-primary); }
.fw-tab.on { background: var(--surface-primary); color: var(--acc-cyan);
             font-weight: 600; border-color: var(--border-light); }
.fw-tab:focus-visible { outline: 2px solid var(--acc-cyan); outline-offset: 1px; }
#fw-advanced { flex: 1; min-height: 0; display: flex; flex-direction: column;
               overflow: auto; background: var(--surface-primary);
               border-top: 1px solid var(--border-light); }
#fw-advanced[hidden], #fw-body[hidden] { display: none !important; }
.fw-adv-empty { padding: 24px 20px; max-width: 420px; font-size: 12.5px;
                line-height: 1.55; color: var(--text-secondary); }
.fw-adv-empty[hidden] { display: none !important; }

/* ── header bar left on 2026-09-14 ─────────────────────────────────────────
   PLATFORMIO PROJECT / version / BUILT strip / READ-ONLY sat in #fw-bar.
   The under-code verdict (#fw-why) left 2026-09-15 — live/fail prose lives in
   the chat trail and in #fw-statecard when nothing is on disk; version is on
   the editor crumb. */

/* ── body: editor only (file tree left 2026-09-14) ──────────────────────── */
#fw-body { flex: 1; min-height: 0; display: flex; flex-direction: column;
           position: relative; }

/* ── editor ──────────────────────────────────────────────────────────────── */
#fw-ed { flex: 1; display: grid;
         grid-template-rows: auto minmax(0, 1fr);
         min-width: 0; min-height: 0; background: var(--surface-primary); }
#fw-ed.state { display: block; overflow: auto; padding: 20px 22px; }
/* The state view (never run / failed) SWAPS what is visible; it does not
   replace the pane. #fw-ed.state used to be filled by writing over its
   innerHTML, which destroyed #fw-crumb, #fw-meta, #fw-code.
   Two things followed, and the second is the worse one: the next
   render threw because those nodes were gone, and wireFirmware()
   binds the crumb/meta/code listeners ONCE at boot — so even after a run
   produced a project, the browser they came back to was mute. Hiding costs
   nothing and keeps every element and every handler alive. */
#fw-statecard { display: none; }
#fw-ed.state > :not(#fw-statecard) { display: none; }
#fw-ed.state > #fw-statecard { display: block; }

#fw-crumb { display: flex; align-items: center; gap: 8px; padding: 7px 12px;
            font: 11px/1.3 'Geist Mono', ui-monospace, monospace;
            border-bottom: 1px solid var(--border-light); min-width: 0; }
.cr-path { display: flex; align-items: center; gap: 8px; min-width: 0;
           overflow: hidden; flex: 1; }
/* Same size, line-height and vertical padding as the chip/select so the
   glyphs share one box — flex center alone left 11px main.cpp beside a
   10px padded v1 looking a pixel off (2026-09-15). */
.cr-last { color: var(--text-primary); font-weight: 600; overflow: hidden;
           text-overflow: ellipsis; min-width: 0; line-height: 1;
           padding: 2px 0; display: inline-flex; align-items: center; }
.cr-right { margin-left: auto; display: flex; align-items: center; gap: 6px;
            flex: none; }
.cr-chip { font-size: inherit; line-height: 1; font-weight: 700;
           color: var(--text-secondary);
           background: var(--state-idle-bg); border-radius: 4px;
           padding: 2px 6px; flex: none;
           display: inline-flex; align-items: center; }
#fw-ver { font: inherit; line-height: 1; padding: 2px 4px; border-radius: 6px;
          background: var(--input-bg); color: var(--input-text);
          border: 1px solid var(--input-border); flex: none; }
.cr-right a { font: inherit; font-size: 10px;
    font-weight: 600; padding: 2px 9px; border-radius: 999px; cursor: pointer;
    text-decoration: none;
    background: var(--button-secondary-bg); color: var(--button-secondary-text);
    border: 1px solid var(--button-secondary-border); }
.cr-right a:hover {
    background: var(--button-secondary-bg-hover);
    border-color: var(--button-secondary-border-hover); }

/* Download moved onto the crumb (2026-09-14). The node stays so the
   state-card hide rule and wireFirmware's boot listeners keep a stable
   child list; an empty bar must not still occupy a grid row. */
#fw-meta { display: none; }

/* ── the code pane: ONE ROW PER LINE ──────────────────────────────────────
   Not a <pre> blob. Three things fall out of the row structure and none of
   them is achievable without it:
     - `position:sticky` on the number pins the gutter horizontally with zero
       scroll-sync JS, inside the one scroller;
     - wrapping is safe, because a number and its text wrap together and can
       never drift apart;
     - `#L41` is a real element to link to.
   It costs one node per line, bounded by the 6,000-line cap in app.js.

   The pane is an inset well on the editor surface (the radius literal matches
   --radius-tile in components.css; a literal here so this file cannot silently
   lose the shape if that sheet is absent). Every colour INSIDE it is a
   --code-* / --syn-* / --state-* token so light mode is a light well and dark
   mode keeps the near-black tile. */
#fw-code { overflow: auto; overscroll-behavior: contain;
           background: var(--code-bg);
           border: 1px solid var(--code-border);
           border-radius: 16px;
           margin: 10px 12px 12px;
           font: 12px/1.55 'Geist Mono', ui-monospace, monospace; tab-size: 2;
           padding: 8px 0 14px; }
#fw-code:focus-visible { outline: 1px solid var(--input-focus-ring);
                         outline-offset: -1px; }
#fw-code::-webkit-scrollbar-thumb { background: var(--code-thumb); }
#fw-code::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--code-text) 30%, transparent); }
#fw-code ::selection { background: var(--code-sel); }
.ln { display: flex; align-items: flex-start; width: max-content;
      min-width: 100%; }
.ln .no { position: sticky; left: 0; z-index: 1; flex: none; width: 38px;
          padding: 0 10px 0 12px; text-align: right; color: var(--code-muted);
          font-size: 10px; font-variant-numeric: tabular-nums; user-select: none;
          cursor: pointer; background: var(--code-bg);
          border-right: 1px solid var(--code-border); }
.ln .no:hover { color: var(--code-text); }
.ln .tx { white-space: pre; padding: 0 16px 0 12px; color: var(--code-text); }
#fw-code.wrap .ln .tx { white-space: pre-wrap; word-break: break-word; }
/* Opaque hover — the gutter is sticky, and a translucent background on a
   sticky element lets scrolled code show through it. */
.ln:hover { background: var(--code-hover); }
.ln:hover .no { background: var(--code-hover); }
.ln.hit { background: color-mix(in srgb, var(--acc-amber) 14%, var(--code-bg)); }
.ln.hit .no { background: color-mix(in srgb, var(--acc-amber) 14%, var(--code-bg));
              color: var(--acc-amber); font-weight: 700; }
.ln.err .no { color: var(--state-crit); font-weight: 700; }
.ln.more { color: var(--code-muted); font-style: italic; padding: 8px 0 0 50px; }
.ln.more a { color: var(--code-text); }

/* Syntax + log-line inks. --syn-* / --state-* resolve per mode. */
.t-key { color: var(--syn-key); }
.t-str { color: var(--syn-str); }
.t-num { color: var(--syn-num); }
.t-typ { color: var(--syn-typ); }
.t-com { color: var(--syn-com); font-style: italic; }
.t-err { color: var(--state-crit); }
.t-warn { color: var(--state-warn); }
.t-ok { color: var(--state-ok); }

/* ── failure report ──────────────────────────────────────────────────────
   Screens A–G in the spec. The one rule that governs all of them: nothing here
   may look like a browsable file. The planned manifest is a LIST, in grey, with
   no links — a dashed row in a file tree still reads as a file, and a dead link
   is worse than no link. */
#fw-fail { max-width: 720px; }
.fw-fh { display: flex; align-items: center; gap: 8px; margin: 0 0 8px;
         font-size: 13.5px; color: var(--text-primary); font-weight: 600; }
.fw-fl { margin: 0 0 14px; font-size: 12px; line-height: 1.6;
         color: var(--text-secondary); }
.fw-sech { font-family: 'Geist Mono', ui-monospace, monospace; font-size: 9px;
           font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
           color: var(--text-muted); margin: 16px 0 6px; }
.fw-planned { display: flex; flex-direction: column; gap: 1px;
              font-family: 'Geist Mono', ui-monospace, monospace; font-size: 10.5px; }
.fw-planned div { color: var(--text-muted); padding: 2px 0 2px 9px;
                  border-left: 2px solid transparent; }
.fw-planned div.blamed { border-left-color: var(--state-crit);
                         color: var(--text-secondary); }
.fw-planned .bl { color: var(--state-crit); font-size: 9px; letter-spacing: .06em;
                  text-transform: uppercase; margin-left: 8px; }
.fw-diag { margin: 0; padding: 0; list-style: none; display: flex;
           flex-direction: column; gap: 3px; }
.fw-diag li { font-family: 'Geist Mono', ui-monospace, monospace; font-size: 10.5px;
              line-height: 1.5; color: var(--state-crit);
              background: var(--state-crit-bg); border-radius: 8px;
              padding: 5px 8px; word-break: break-word; }
#fw-fail details.src { margin-top: 14px; }
#fw-fail details.src pre { max-height: 240px; overflow: auto; margin: 6px 0 0;
                           font-family: 'Geist Mono', ui-monospace, monospace;
                           font-size: 10.5px; line-height: 1.5;
                           color: var(--text-secondary); white-space: pre-wrap;
                           background: var(--surface-secondary); border-radius: 8px;
                           padding: 9px 11px; }
.fw-newver { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
             font: inherit; font-size: 11px; padding: 3px 9px; border-radius: 999px;
             color: var(--state-ok); background: var(--state-ok-bg);
             border: 1px solid var(--state-ok); }

/* ── narrow ──────────────────────────────────────────────────────────────── */
@container (max-width: 420px) {
  .ln .no { width: 28px; padding: 0 6px 0 6px; }
  #fw-code { margin: 8px; }
}

/* ── Flash to board ────────────────────────────────────────────────────────
   The one control that reaches physical hardware. Its visual job is to make
   the difference between "observed" and "assumed" legible: the chip name and
   the byte counts are measurements and read as data (mono, foreground); the
   caveats read as prose. Nothing here animates on a timer — the progress bar
   is driven only by esptool's own reported byte counts, so a stalled write
   shows as a stalled bar, which is information. */
#fw-flash { flex: none; border-top: 1px solid var(--border-medium);
            background: var(--surface-secondary); padding: 10px 14px 12px;
            display: flex; flex-direction: column; gap: 9px; }
/* MANDATORY, same trap index.html documents at its [hidden] rules: the
   `hidden` attribute's UA `display:none` loses to ANY author display, and the
   rule above says flex — without this override the closed panel stayed fully
   visible, uninitialised, with no error in any console (seen on screen
   2026-08-24; app.js toggles `panel.hidden`, never a class). #fw-flashbtn has
   the same bug from index.html's `.btn` display: on a build with no flashable
   bytes syncFlashButton honestly sets `hidden` and the button kept rendering —
   a flash control over bytes that do not exist (measured 2026-08-24:
   hidden=true, computed display:flex). Both guarded here because #fw is this
   file's surface. */
#fw-flash[hidden], #fw-flashbtn[hidden], #fw-flash-dock[hidden], #ff-find[hidden] { display: none; }
#fw-flash .ff-head { display: flex; align-items: center; gap: 10px; }
#fw-flash .ff-head b { font-size: 12px; letter-spacing: .02em; }
#fw-flash .ff-head .btn { margin-left: auto; }
.ff-note { font-size: 12px; line-height: 1.5; color: var(--text-secondary); }
.ff-note.warn { color: var(--state-warn); }
.ff-note.crit { color: var(--state-crit); }
.ff-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
#ff-port { font-size: 11px; color: var(--text-secondary); }
/* Measured progress. The <i> width is set only from reportProgress bytes.
   Solid cyan = the accent vocabulary's "live/running", replacing the old
   cyan→indigo brand mark: use var(--aurora-gradient) / .aurora-text — never
   a second gradient. */
.ff-bar { position: relative; height: 6px; border-radius: 999px;
          background: var(--state-idle-bg); overflow: hidden; }
.ff-bar > i { position: absolute; inset: 0 auto 0 0; width: 0;
              background: var(--acc-cyan);
              transition: width .12s linear; }
.ff-bar > span { position: absolute; right: 6px; top: -16px; font-size: 10px;
                 color: var(--text-secondary); font-family: 'Geist Mono', ui-monospace, monospace; }
/* One row per image actually written, with its real offset and size. A build
   that produced only firmware.bin shows exactly one row — which is how a
   partial (app-only) flash stays visible rather than implied. */
.ff-images { display: flex; flex-direction: column; gap: 3px; }
.ff-img { display: flex; align-items: baseline; gap: 8px; font-size: 11px;
          font-family: 'Geist Mono', ui-monospace, monospace; }
.ff-img .n { color: var(--text-primary); }
.ff-img .o, .ff-img .s { color: var(--text-muted); }
.ff-img .st { margin-left: auto; }
.ff-img .st.ok { color: var(--state-ok); }
.ff-img .st.run { color: var(--acc-cyan); }
/* Terminal output on the same themed well as #fw-code (the log text itself is
   plain textContent — no syntax spans). */
.ff-log { max-height: 190px; overflow: auto; margin: 0; padding: 8px 10px;
          background: var(--code-bg); border-radius: 12px; font-size: 11px;
          border: 1px solid var(--code-border);
          line-height: 1.5; white-space: pre-wrap; word-break: break-word;
          font-family: 'Geist Mono', ui-monospace, monospace;
          color: var(--code-text); }
.ff-log::-webkit-scrollbar-thumb { background: var(--code-thumb); }
.ff-log ::selection { background: var(--code-sel); }

/* ── the board over WiFi ──────────────────────────────────────────────────
   Lives under the Advanced tab (2026-09-14). Same surface as the flash panel:
   what the board is saying right now. The pill carries the only state that
   is not text — and it has four states, because "connecting", "open but the
   board has not spoken", "the board is here" and "the gateway refused us"
   are four different facts and collapsing any two of them would be the lie
   this product is built against. */
#fw-wifi { flex: 1; min-height: 0; background: var(--surface-secondary);
           padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 9px; }
/* The same trap #fw-flash documents above: `hidden` loses to an author
   display, so a pane for a USB project would render fully open. */
#fw-wifi[hidden] { display: none !important; }
#fw-wifi .ff-head b { font-size: 12px; letter-spacing: .02em; }
#fw-wifi .ff-head .btn { margin-left: auto; }
#fw-wifi .ff-log { flex: 1; min-height: 120px; max-height: none; }
.fwl-pill { font-size: 11px; padding: 2px 8px; border-radius: 999px;
            font-family: 'Geist Mono', ui-monospace, monospace;
            background: var(--state-idle-bg); color: var(--text-secondary); }
.fwl-pill.live { color: var(--acc-cyan); }
.fwl-pill.warn { color: var(--state-warn); }
.fwl-pill.crit { color: var(--state-crit); }
.fwl-in { flex: 1 1 120px; min-width: 0; font-size: 11px; padding: 4px 8px;
          border-radius: 8px; border: 1px solid var(--border-medium);
          background: var(--surface-primary); color: var(--text-primary);
          font-family: 'Geist Mono', ui-monospace, monospace; }
.fwl-wire { font-size: 10px; color: var(--text-muted); word-break: break-all; }

#ff-wifi[hidden], #ff-wifi-form[hidden], #ff-wifi-update[hidden] { display: none; }
#ff-wifi { border-top: 1px solid var(--border-medium); padding-top: 8px; }
#ff-wifi-state { margin: 0 0 8px; }
#ff-wifi-form { display: flex; flex-wrap: wrap; align-items: end; gap: 8px; }
#ff-wifi-form label { display: grid; gap: 4px; flex: 1 1 180px; }
#ff-wifi-form input { min-width: 0; width: 100%; box-sizing: border-box; }
#ff-wifi-form small { flex-basis: 100%; }
