/* Page chrome: warm paper, terracotta, monospace (see family-budget.css,
   vendored from this owner's design system). The device panel itself stays
   pure device output, styled below outside that palette on purpose: it is
   a framebuffer, not UI.

   The panel is the subject of this page, everything else is furniture.
   Furniture stays quiet: smaller, lower contrast, terracotta reserved for
   genuine "this is active" state, never decorative. No control carries a
   sentence; anything longer than two words lives behind a title tooltip
   or a <details> disclosure.

   --panel-w / --panel-h are set from the firmware's own emu_device()
   descriptor at runtime (see main.ts, buildChrome): nothing here hardcodes
   a panel size, because this emulator is generic across devices. The
   values below are just a plausible fallback for the instant before the
   first module loads. */

:root {
  --panel-w: 240px;
  --panel-h: 240px;
  --bezel-pad: 18px;
  --sidebar-w: 280px;
}

/* height, not min-height: the sidebar's content is easily taller than the
   viewport, and without a definite height here .layout's flex children have
   nothing definite to stretch/shrink into, so the WHOLE PAGE grows to fit
   the sidebar instead of the sidebar scrolling on its own (.side.controls
   already has overflow-y:auto, but that is a no-op without this). */
#root { height: 100vh; display: flex; flex-direction: column; }

/* ---------- quiet chrome controls: text only, no border/fill until
   hovered, so the top and bottom bars do not compete with the device ---- */
.chrome-btn {
  font: inherit;
  font-size: 11px;
  background: none;
  border: none;
  color: var(--muted);
  padding: 4px 7px;
  border-radius: var(--radius-control);
  cursor: pointer;
}
.chrome-btn:hover { background: var(--row-hover); color: var(--ink-soft); }
.chrome-btn[disabled] { opacity: .4; cursor: default; }
.deck-btn { font-size: 13px; padding: 4px 10px; }
.icon-btn { display: inline-flex; align-items: center; justify-content: center; }
.chrome-btn.active { color: var(--accent-dark); background: var(--accent-bg); }

.divider { width: 1px; height: 16px; background: var(--border); margin: 0 4px; align-self: center; }

/* ---------- disclosure: the one mechanism for anything longer than two
   words (touch tuning, a gesture's prose) ---------- */
.disclosure { margin: 4px 0 8px; }
.disclosure summary {
  cursor: pointer;
  font-size: 11px;
  color: var(--muted);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 5px;
  user-select: none;
}
.disclosure summary::-webkit-details-marker { display: none; }
.disclosure summary::before { content: "\25B8"; font-size: 9px; display: inline-block; transition: transform .1s; }
.disclosure[open] summary::before { transform: rotate(90deg); }
.disclosure[open] summary { color: var(--ink-soft); }
.disclosure > *:not(summary) { margin-top: 6px; }

.topbar {
  display: flex; align-items: center; gap: 2px;
  padding: 8px 14px;
  background: var(--card);
  border-bottom: 1px solid var(--border-soft);
}
.topbar .spacer { flex: 1; }
.topbar b { font-size: 13px; }

.reload-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--border-dashed);
  display: inline-block;
  margin-left: 6px;
}
.reload-dot.connected { background: var(--positive); }

.filebtn { position: relative; overflow: hidden; }
.filebtn input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }

.wasm-error {
  margin: 12px 16px;
  padding: 12px 14px;
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid var(--danger);
  border-radius: var(--radius-card);
  font-size: 12px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}
.wasm-error-text { white-space: pre-wrap; word-break: break-word; }

/* ---------- engine-dead banner: deliberately NOT a copy of .wasm-error.
   That one means a module never came up (blank panel, nothing to look
   at). This means a module DID come up and something threw mid-tick --
   the panel below may still show a perfectly normal-looking last frame,
   which is exactly the state that reads as "my firmware is stuck" if this
   doesn't say otherwise, loudly. Hazard-striped border instead of a plain
   one, on purpose: same danger palette as .wasm-error but a different
   silhouette, so the two are never mistakable for each other even at a
   glance. See main.ts's paintDeadOverlay for the matching treatment
   painted directly on the panel itself. ---------- */
.engine-dead {
  margin: 12px 16px;
  padding: 12px 14px;
  background: var(--danger-bg);
  color: var(--danger);
  border: 3px solid transparent;
  border-image: repeating-linear-gradient(135deg, var(--danger) 0 8px, var(--danger-bg) 8px 16px) 3;
  border-radius: var(--radius-card);
  font-size: 12px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}
.engine-dead-title { font-weight: 700; }
.engine-dead-text { white-space: pre-wrap; word-break: break-word; font-size: 11px; }
.engine-dead-meta { font-variant-numeric: tabular-nums; opacity: .85; }
.engine-dead-hint { opacity: .85; max-width: 720px; }

.hidden { display: none !important; }

.layout { display: flex; flex: 1; min-height: 0; }

/* ---------- stage: the draggable device lives here ---------- */
.stage {
  position: relative;
  flex: 1;
  overflow: auto;
  background:
    radial-gradient(circle at 1px 1px, var(--border-fine) 1px, transparent 0) 0 0 / 22px 22px,
    var(--paper);
  min-height: 640px;
}

.device-wrap {
  /* Centred over the stage on first load by main.ts (centerDeviceOnce),
     once actual layout sizes are known; these are just a reasonable
     fallback for the instant before that runs. Dragging (device.ts,
     makeDraggable) overwrites left/top directly from then on. */
  position: absolute;
  left: 120px;
  top: 60px;
  touch-action: none;
}

.bezel {
  position: relative;
  width: calc(var(--panel-w) + 2 * var(--bezel-pad));
  height: calc(var(--panel-h) + 2 * var(--bezel-pad));
  padding: var(--bezel-pad);
  border-radius: 34px;
  background: linear-gradient(160deg, #3a3a3d, #1c1c1e 60%);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255, 255, 255, .08);
  cursor: grab;
  user-select: none;
}
.bezel:active { cursor: grabbing; }

.panel {
  display: block;
  width: var(--panel-w);
  height: var(--panel-h);
  background: #fff;
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .5);
  touch-action: none;
}

.overlay-canvas {
  position: absolute;
  top: var(--bezel-pad);
  left: var(--bezel-pad);
  width: var(--panel-w);
  height: var(--panel-h);
  pointer-events: none;
}

/* Buttons: shape and colour are fixed, position (edge + offset along it)
   is set inline per-button from the device descriptor (main.ts / device.ts,
   createButtonElement), because that position IS real device geometry.

   Pressing a button should never be too subtle to trust, and someone
   holding a button for a real gesture (a long-press threshold, or any
   power-off style hold) should never have to wonder whether the emulator
   noticed. Three layers of feedback here, deliberately redundant with each
   other rather than relying on any single cue:
     1. a physical depress: the button visibly shrinks toward the bezel
        along the axis it protrudes on, plus a brighter, inverted colour
        and an inset shadow, the instant it goes down;
     2. a fill (the ::after pseudo-element) that grows along the button's
        own length, its CSS transition-duration set to the button's real
        longPressMs (via --hold-ms, device.ts) - the fill's own animation
        IS the timer, not an approximation that could drift from it;
     3. on reaching the threshold, a distinct accent-coloured "reached"
        state (.long) with its own glow, so a completed hold looks nothing
        like an in-progress one. */
.dev-btn {
  position: absolute;
  overflow: hidden;
  background: linear-gradient(90deg, #55555a, #333336);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .4);
  border-radius: 5px;
  transition: transform 110ms ease-out;
}
.dev-btn.edge-left  { transform-origin: right center; }
.dev-btn.edge-right { transform-origin: left center; }
.dev-btn.edge-top    { transform-origin: center bottom; }
.dev-btn.edge-bottom { transform-origin: center top; }

.dev-btn.pressed {
  background: linear-gradient(90deg, #b4b4ba, #82828a);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, .55), 0 0 0 2px rgba(255, 255, 255, .14);
}
.dev-btn.edge-left.pressed,  .dev-btn.edge-right.pressed  { transform: scaleX(.6); }
.dev-btn.edge-top.pressed,   .dev-btn.edge-bottom.pressed { transform: scaleY(.6); }

.dev-btn.long {
  background: linear-gradient(90deg, var(--accent), var(--accent-dark));
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, .4), 0 0 0 3px rgba(196, 98, 31, .35);
}

/* The hold-progress fill. Growth direction runs along the button's own
   length (its long axis), not its thickness, so it reads as "how far
   through the hold" rather than "how hard pressed". No transition at all
   until .holding is added (so a plain tap never shows a flicker of it),
   and released before the threshold, it retracts on the fast default
   transition below rather than the slow one, an unmistakable "let go,
   did not count" snap. */
.dev-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, .5);
  transition: transform 140ms ease-out;
}
.dev-btn.edge-left::after,  .dev-btn.edge-right::after  { transform-origin: top;  transform: scaleY(0); }
.dev-btn.edge-top::after,   .dev-btn.edge-bottom::after { transform-origin: left; transform: scaleX(0); }
.dev-btn.holding.edge-left::after,  .dev-btn.holding.edge-right::after {
  transform: scaleY(1);
  transition: transform var(--hold-ms) linear;
}
.dev-btn.holding.edge-top::after,   .dev-btn.holding.edge-bottom::after {
  transform: scaleX(1);
  transition: transform var(--hold-ms) linear;
}

/* ---------- bottom bar: view + time deck, and one diagnostics strip.
   Fixed to the viewport (not the stage's scroll/drag content), centred in
   the space left of the sidebar, so it stays put regardless of where the
   puck gets dragged. Quiet: a thin card, small text, no accent unless a
   control is genuinely active (segtog's own .active). ---------- */
.bottom-bar {
  position: fixed;
  left: 0;
  bottom: 12px;
  width: calc(100% - var(--sidebar-w));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  z-index: 5;
}

.stage-controls {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-tab);
  padding: 4px 8px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .08);
}
.tilt-slider { width: 64px; }

.diag-strip {
  font-size: 10.5px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  white-space: pre;
}

/* ---------- sidebar ---------- */
.side.controls { width: var(--sidebar-w); overflow-y: auto; padding: 14px 12px; }
.side.controls h3:first-child { margin-top: 0; }
.side.controls h3 { margin: 16px 0 6px; }

.slider-row { margin-bottom: 8px; }
.slider-row .row-head { display: flex; justify-content: space-between; font-size: 11px; }
.slider-row .row-head b { font-weight: 600; }
.slider-row .row-head span { color: var(--accent-dark); font-variant-numeric: tabular-nums; }
.slider-row input[type=range] { width: 100%; }

.toggle-row { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; font-size: 12px; }
.toggle-row input[type=checkbox] { margin: 0; }

.device-info { font-size: 11px; color: var(--muted); margin-bottom: 8px; }

.kbd {
  display: inline-block;
  min-width: 15px;
  text-align: center;
  font-size: 10px;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--chip-bg);
  color: var(--chip-text);
  border: 1px solid var(--border);
}

.shortcut-list { display: flex; flex-direction: column; gap: 3px; margin-bottom: 8px; }
.shortcut-row { font-size: 11px; display: flex; align-items: center; gap: 6px; }

.sensor-controls { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 4px; }
.sensor-btn { display: inline-flex; align-items: center; gap: 4px; }

/* Segmented toggle, a size down from the design system's default: these
   are secondary controls (rotation, contact size), not primary navigation. */
.segtog.seg-sm button { padding: 3px 8px; font-size: 11px; }

.contact-row { display: flex; align-items: center; gap: 5px; margin-bottom: 6px; }
.mm-input { width: 44px; padding: 3px 5px; font-size: 11px; }
.contact-row .unit { font-size: 11px; color: var(--muted); }
.contact-row .px-readout { margin-left: auto; font-size: 11px; color: var(--accent-dark); font-variant-numeric: tabular-nums; }

.gesture-detail summary { font-weight: 600; color: var(--ink-soft); }
.gesture-body { padding-left: 13px; font-size: 11px; }
.gesture-body .how { color: var(--ink-soft); margin: 0 0 6px; max-width: 240px; }
.gesture-actions { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.gesture-keys { display: flex; align-items: center; gap: 2px; color: var(--muted); }

.app-strip { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.app-strip-btn.active { background: var(--accent); color: #fff; }

.console-pane {
  height: 100px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  padding: 5px 7px;
  font-size: 10.5px;
  line-height: 1.5;
}
.console-line { white-space: pre-wrap; word-break: break-word; color: var(--hint); }
.console-line + .console-line { border-top: 1px dashed var(--border-fine); }

/* ---------- freeze annotation modal ---------- */
.freeze-modal { max-width: none; width: fit-content; }
.freeze-modal-title { margin-bottom: 10px; }
.freeze-canvas-wrap { position: relative; margin: 0 auto 10px; }
.freeze-img { position: absolute; top: 0; left: 0; image-rendering: pixelated; }
.freeze-ink { position: relative; touch-action: none; cursor: crosshair; }
.freeze-typetog { margin-bottom: 10px; }
.freeze-notes { max-height: 120px; overflow-y: auto; margin-bottom: 8px; }
.freeze-note-row { font-size: 12px; padding: 3px 0; border-bottom: 1px dashed var(--border-fine); }
.freeze-note-add { display: flex; gap: 6px; margin-bottom: 12px; }
.freeze-note-add input { flex: 1; }
.freeze-modal-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* ---------- hardware-free regression check: two controls (baseline,
   check), one status pill, and - only when something diverged - a modal
   with the baseline/current/diff frames themselves. No sentence under the
   images: a diverging pixel count is already in the pill, the pictures are
   the "show me where". See src/regression.ts and docs/harness.md. ---------- */
#regressionPill.status.fail { background: var(--danger-bg); color: var(--danger); }

.regression-modal { max-width: none; width: fit-content; max-height: 82vh; overflow-y: auto; }
.regression-modal-title { margin-bottom: 10px; }
.regression-row { margin-bottom: 12px; }
.regression-row .pill { margin-bottom: 6px; }
.regression-imgs { display: flex; gap: 10px; flex-wrap: wrap; }
.regression-thumb canvas { display: block; image-rendering: pixelated; border: 1px solid var(--border); border-radius: 4px; max-width: 200px; height: auto; }
.regression-thumb-label { font-size: 10px; color: var(--muted); text-align: center; margin-top: 3px; }
.regression-modal-actions { display: flex; justify-content: flex-end; margin-top: 4px; }
