/* Copyright 2026 Bill Schilit — SPDX-License-Identifier: Apache-2.0
 *
 * Shared design tokens and components for the Simble browser tool. The colours,
 * pills, panels, buttons, connection-setup panel, and the generic GATT viewer
 * are the same on every page. Every page imports this; `hrm` is the last one
 * still carrying inline copies of some rules. */

:root {
  color-scheme: light; /* opt out of browser auto-dark / forced-dark inversion */
  --bg: #ffffff; --panel: #f6f8fa; --panel2: #eaeef2; --border: #d0d7de;
  --text: #1f2328; --dim: #656d76; --accent: #0969da; --good: #1a7f37;
  --bad: #cf222e; --warn: #9a6700; --heart: #cf222e;

  /* Type scale. Sizes had drifted to about twenty values -- 0.68 through
     1.15rem plus a stray 11.5px -- with several separated by 0.02rem, which
     is 0.32px: too small to read as a deliberate difference, but enough that
     things meant to match never quite did. Six steps, chosen by role, so
     "what size is this" has an answer that is not a measurement. */
  --fs-display: 4rem;   /* the one big live readout on a page (Health's heart) */
  --fs-hero:  1.5rem;   /* standalone page h1 */
  --fs-title: 1.15rem;  /* section h1 */
  --fs-lead:  0.95rem;  /* nav and domain tabs */
  --fs-body:  0.85rem;  /* prose, buttons, inputs, selects */
  --fs-label: 0.8rem;   /* uppercase section labels, hints */
  --fs-meta:  0.72rem;  /* uuids, chips, badges, footnotes */
  --fs-code:  0.8rem;   /* code and log blocks */

  /* Content width. Every page had picked its own -- 52, 54, 60, 68, 72, 75 and
     82rem across ten pages -- so the content edge jumped on every tab change,
     which reads as the pages belonging to different sites. Two widths, each
     with a reason: a tool page needs room for side-by-side panels, a reading
     page wants a measure you can actually read. Pick one; do not invent a
     third. */
  --page-w: 72rem;      /* workspaces: panels, grids, live device views */
  --read-w: 54rem;      /* prose: landing, docs, explanations */
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Header + connection pill (shared shape across pages) */
header {
  display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap;
  padding: 1rem 1.5rem; border-bottom: 1px solid var(--border);
}
header h1 { font-size: var(--fs-title); margin: 0; }
header .sub { color: var(--dim); font-size: var(--fs-body); }
header a { color: var(--accent); text-decoration: none; font-size: var(--fs-body); }
.pill {
  font-size: var(--fs-meta); padding: 0.1rem 0.6rem; border-radius: 1rem;
  border: 1px solid var(--border); color: var(--dim); white-space: nowrap;
}
.pill.ok { color: var(--good); border-color: var(--good); }
.pill.bad { color: var(--bad); border-color: var(--bad); }

/* Panels */
.panel {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 8px; padding: 1rem;
}
.panel h2 { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--dim); margin: 0 0 0.75rem; }

/* Buttons + rows */
button, .btn {
  background: var(--panel2); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 0.35rem 0.9rem; font-size: var(--fs-body);
  cursor: pointer; text-decoration: none; display: inline-block;
}
button:hover, .btn:hover { border-color: var(--dim); }
button.primary, .btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button:disabled { opacity: 0.5; cursor: default; }
select {
  background: var(--panel2); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 0.35rem 0.6rem; font-size: var(--fs-body); cursor: pointer;
}
input[type=text], input[type=number] {
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 0.3rem 0.5rem; font-size: var(--fs-body);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin-top: 0.6rem; }
.hint { color: var(--dim); font-size: var(--fs-label); }
.error {
  color: var(--bad); font-family: ui-monospace, Menlo, monospace;
  font-size: var(--fs-code); white-space: pre-wrap; margin-top: 0.5rem;
}

/* Editor / code */
textarea.code {
  width: 100%; resize: vertical; background: var(--bg);
  color: var(--text); border: 1px solid var(--border); border-radius: 6px;
  font: 12.5px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  padding: 0.6rem; tab-size: 4;
}
pre.code, .mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Rhai syntax highlighting — GitHub-light family, harmonizing with the theme. */
:root {
  --syn-comment: #6e7781; --syn-string: #0a3069; --syn-number: #0550ae;
  --syn-keyword: #cf222e; --syn-path: #953800; --syn-fn: #8250df;
  --syn-const: #0550ae;
}
.tok-comment { color: var(--syn-comment); font-style: italic; }
.tok-string  { color: var(--syn-string); }
.tok-number  { color: var(--syn-number); }
.tok-keyword { color: var(--syn-keyword); }
.tok-path    { color: var(--syn-path); }
.tok-fn      { color: var(--syn-fn); }
.tok-const   { color: var(--syn-const); }

/* Highlighted-editor overlay: a syntax-highlighted <pre> behind a transparent,
   caret-visible <textarea>. Box metrics are copied from the textarea in JS. */
.hl-editor { position: relative; display: block; }
.hl-editor .hl-layer {
  position: absolute; inset: 0; margin: 0; overflow: hidden;
  background: var(--bg); pointer-events: none; z-index: 0;
}
.hl-editor .hl-layer code { font: inherit; white-space: inherit; }
.hl-editor textarea.hl-input {
  position: relative; z-index: 1; display: block; overflow: auto;
  background: transparent;
}

/* Connection-setup panel (netsim not reachable) — standardized wording */
.setup { display: none; border-color: var(--warn); }
.setup.visible { display: block; }
.setup pre {
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 0.6rem; overflow-x: auto; font-size: var(--fs-code);
}
.setup code { color: var(--good); }
.setup a { color: var(--accent); }

/* AI suggestion chips: a grid rather than a ragged inline row, so every
   pill is the same size and the set reads as one control. */
.suggest { margin-top: 0.5rem; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); gap: 0.35rem; }
.suggest .chip {
  border: 1px solid var(--border); border-radius: 1rem;
  padding: 0.25rem 0.7rem; font-size: var(--fs-meta); text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer; background: var(--panel2); color: var(--text);
}
.suggest .chip:hover { border-color: var(--accent); }
.suggest .chip.active { border-color: var(--accent); color: var(--accent); }
details { margin-top: 0.75rem; }
details summary { cursor: pointer; color: var(--dim); font-size: var(--fs-body); }
details pre {
  max-height: 18rem; overflow: auto; background: var(--bg); border: 1px solid var(--border);
  border-radius: 6px; padding: 0.6rem; font-size: var(--fs-code); white-space: pre-wrap;
}

/* ---------------------------------------------------------------------------
 * The device header (common/device-header.js)
 *
 * One shape for every simulated device on the site: state dot, name, kind and
 * live state, the pen that reveals the device's script, the run/stop toggle
 * (disabled, with the reason beside it, for a device whose lifetime is shared
 * with others), and the on-air address hard right.
 * ------------------------------------------------------------------------- */
/* A grid, not one wrapping flex row: the address is last in source order with
   `margin-left: auto`, so any overflow used to wrap it down and it ended up in
   a different place on almost every card. Row 1 is the device and its address;
   the "why" note, which is what usually caused the overflow, gets row 2. */
.dev-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline; column-gap: 0.75rem; row-gap: 0.3rem;
  padding: 0.5rem 0.7rem; margin: 0 0 0.7rem;
  border: 1px solid var(--border); border-radius: 8px; background: var(--panel2);
}
.dev-head .dev-main {
  grid-column: 1; grid-row: 1; min-width: 0;
  display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap;
}
.dev-head .dev-dot {
  width: 9px; height: 9px; flex: 0 0 auto; border-radius: 50%;
  background: var(--dim); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12);
}
.dev-head .dev-dot.on { background: var(--good); }
/* The start/stop pill: a visible verb beside the state. Green-tinted while
   running so the button and the dot agree without saying it twice. */
.dev-head .dev-run { font-size: 0.72rem; padding: 0.24rem 0.6rem;
  border: 1px solid var(--border); border-radius: 999px; cursor: pointer;
  background: var(--panel, transparent); color: var(--text); white-space: nowrap;
  display: inline-flex; align-items: center; gap: 0.32rem; line-height: 1; }
.dev-head .dev-run svg { display: block; }
.dev-head .dev-run:hover:not(:disabled) { border-color: var(--good); }
.dev-head .dev-run.running { color: var(--good); border-color: var(--good); }
.dev-head.accent-accent .dev-run.running { color: var(--accent); border-color: var(--accent); }
/* Filled when the device is actually doing its job — the pill is the state
   light now, so it inherits the dot's third state. */
.dev-head .dev-run.on { background: var(--good); border-color: var(--good); color: #fff; }
.dev-head.accent-accent .dev-run.on { background: var(--accent); border-color: var(--accent);
  color: #fff; /* the accent .running rule out-specifies the base .on white,
  which painted accent text on an accent background — a totally blue pill */ }
.dev-head .dev-run:disabled { opacity: 0.55; cursor: not-allowed; }
/* An identity you can edit must look editable: the dashed underline is the
   affordance, and it firms up under the pointer. "Where did the name go?"
   is what an editable span with no affordance earns. */
.dev-head .dev-editable { border-bottom: 1px dashed var(--dim); }
.dev-head .dev-editable:hover, .dev-head .dev-editable:focus {
  border-bottom-color: var(--text); outline: none; background: var(--panel-2, rgba(127,127,127,0.08)); }
.dev-head.accent-accent .dev-dot.on { background: var(--accent); }
.dev-head.accent-warn .dev-dot.on { background: var(--warn); }
.dev-head .dev-name { font-weight: 700; }
.dev-head.accent-good .dev-name { color: var(--good); }
.dev-head.accent-accent .dev-name { color: var(--accent); }
.dev-head.accent-warn .dev-name { color: var(--warn); }
.dev-head .dev-kind { color: var(--dim); font-size: var(--fs-meta); }
.dev-head .dev-state { color: var(--dim); font-size: var(--fs-meta); }
.dev-head .dev-state.ok { color: var(--good); }
.dev-head .dev-state.bad { color: var(--bad); }
.dev-head .dev-state.warn { color: var(--warn); }
.dev-head .dev-icon {
  border: 1px solid var(--border); background: transparent; border-radius: 6px;
  cursor: pointer; font-size: var(--fs-body); line-height: 1; padding: 0.18rem 0.42rem;
  color: var(--dim);
}
.dev-head .dev-icon:hover:not(:disabled) { color: var(--text); border-color: var(--text); }
.dev-head .dev-icon[aria-pressed="true"] { color: var(--text); border-color: var(--text); }
.dev-head .dev-icon:disabled { opacity: 0.4; cursor: not-allowed; }
/* Why a device cannot be stopped on its own. Visible, not only a tooltip:
   the limitation is part of what these pages are explaining. */
.dev-head .dev-why { grid-column: 1 / -1; grid-row: 2;
  color: var(--dim); font-size: var(--fs-meta); font-style: italic; }
/* Pinned to the top row's right edge on every card, so two devices side by
   side can be compared by their addresses without hunting for them. */
.dev-head .dev-addr {
  grid-column: 2; grid-row: 1; justify-self: end; white-space: nowrap;
  font-family: ui-monospace, Menlo, monospace;
  color: var(--dim); font-size: var(--fs-meta);
}
.dev-head .dev-addr.none { font-style: italic; font-family: inherit; }
.dev-script { margin: 0 0 0.8rem; }
.dev-script textarea {
  width: 100%; height: 14rem; resize: vertical; background: var(--bg);
  color: var(--text); border: 1px solid var(--border); border-radius: 6px;
  padding: 0.6rem; tab-size: 4;
  font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.dev-script textarea[readonly] { background: var(--panel); }
.dev-script .hint { margin: 0.35rem 0 0; }

/* Generic GATT viewer */
.kv { display: grid; grid-template-columns: auto 1fr; gap: 0.15rem 0.9rem;
  font-size: var(--fs-body); margin-bottom: 0.75rem; }
.kv dt { color: var(--dim); } .kv dd { margin: 0; }
.svc { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 0.7rem;
  overflow: hidden; }
.svc-head { background: var(--panel2); padding: 0.4rem 0.7rem; font-size: var(--fs-body); }
.svc-head .u { font-family: ui-monospace, Menlo, monospace; color: var(--dim);
  font-size: var(--fs-meta); margin-left: 0.4rem; }
.chr { padding: 0.5rem 0.7rem; border-top: 1px solid var(--border); }
.chr.pulse { animation: pulse 0.9s ease-out; }
@keyframes pulse {
  0% { background: rgba(9,105,218,0.14); }
  100% { background: transparent; }
}
.chr-top { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.chr-name { font-weight: 500; }
.chr-uuid { font-family: ui-monospace, Menlo, monospace; color: var(--dim); font-size: var(--fs-meta); }
.prop {
  display: inline-block; border: 1px solid var(--border); border-radius: 4px;
  padding: 0 0.3rem; font-size: var(--fs-meta); color: var(--dim);
  font-family: ui-monospace, Menlo, monospace;
}
.prop.sub { color: var(--good); border-color: var(--good); }
.chr-val { margin-top: 0.25rem; font-size: var(--fs-body); }
.chr-val .decoded { font-weight: 600; }
.chr-val .raw { font-family: ui-monospace, Menlo, monospace; color: var(--dim);
  font-size: var(--fs-meta); margin-left: 0.4rem; }
.sub-note { color: var(--good); font-size: var(--fs-meta); margin-left: 0.4rem; }
/* "there is nothing here yet", in the two names the site had grown for it:
   `.viewer-empty` is what the GATT widget writes, `.empty` is what four pages
   were each declaring identically in their own <style> block. One rule. */
.viewer-empty, .empty { color: var(--dim); font-size: var(--fs-body); }

/* The client half of the shared widget (common/gatt-view.js): a discovered
   tree, nRF-Connect flavoured. The handle and the operation buttons appear
   only here — a client addresses attributes by handle and can read, write and
   subscribe; a server holding its own database can do none of those to
   itself. */
.gatt-view.client .chr-h {
  color: var(--dim); font-size: var(--fs-meta); margin-left: auto;
  font-family: ui-monospace, Menlo, monospace;
}
.gatt-view .chr-actions { margin-top: 0.35rem; display: flex; gap: 0.35rem; flex-wrap: wrap; }
.gatt-view .chr-actions button {
  background: var(--panel2); color: var(--text); border: 1px solid var(--border);
  border-radius: 5px; padding: 0.12rem 0.5rem; font-size: var(--fs-meta); cursor: pointer;
}
.gatt-view .chr-actions button:hover { border-color: var(--accent); }
.gatt-view .chr-actions button.on { color: var(--good); border-color: var(--good); }

footer { color: var(--dim); font-size: var(--fs-meta); padding: 0 1.5rem 1.5rem;
  max-width: var(--page-w); margin: 0 auto; }
footer code { color: var(--good); }

/* --- domain layout ------------------------------------------------------
   Every domain module lays its device panels out the same way, and seven of
   them had each grown a private copy of this grid. A module adds `domain`
   plus `one-up` or `two-up` to its own mount root, so the same rules apply
   whether it is mounted into the Devices shell's stage or into its own
   standalone <main> -- neither of which the module owns. */
.domain { display: grid; gap: 1.25rem; padding: 1.25rem 1.5rem; margin: 0 auto; }
/* One column, but still a workspace: --page-w, not --read-w. The controller
   bar above every domain is --page-w, so a narrower grid cannot line up with
   it -- which is exactly how this was noticed, the Broadcast cards sitting
   inset from the controller box while every other page's agreed. A domain
   whose content is genuinely prose should cap its own text measure rather
   than shrink the frame everything else aligns to. */
.domain.one-up { grid-template-columns: minmax(20rem, 1fr); max-width: var(--page-w); }
.domain.two-up { grid-template-columns: minmax(20rem, 1fr) minmax(20rem, 1fr);
  max-width: var(--page-w); }
/* Anything that spans the row: a backend selector, an intro paragraph. */
.domain > .full { grid-column: 1 / -1; }
@media (max-width: 60rem) { .domain { grid-template-columns: 1fr; } }

/* --- the "About this page" box -----------------------------------
   Every domain opens with one, foldable. See common/about-box.js. */
/* The same card as .panel -- it sits among them, so it should not look like
   a different kind of object -- with a left edge marking it as the one that
   folds. The stripe picks up the accent when open, so the chevron reads as a
   control rather than a stray glyph at the margin. */
.about { align-self: start; margin: 0; background: var(--panel); border: 1px solid var(--border);
  border-left: 3px solid var(--dim); border-radius: 8px;
  padding: 0.85rem 1.1rem 0.9rem 1.25rem; }
.about[open] { border-left-color: var(--accent); }
.about > summary::marker { color: var(--dim); }
/* The marker sits in its own space rather than crowding the left edge, and
   the label is set off from it. */
.about > summary { cursor: pointer; font-size: var(--fs-label); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--dim);
  list-style-position: outside; padding-left: 0.4rem; }
/* Was `var(--fg)`, a token that does not exist, so this hover never
   applied at all. The tab-strip rules had the same typo and got away
   with it because inheritance landed on --text regardless. */
.about > summary:hover { color: var(--text); }
.about[open] > summary { margin-bottom: 0.85rem; }
.about-body { font-size: var(--fs-body); }
.about-body p { margin: 0 0 0.7rem; }
.about-body p:last-child { margin-bottom: 0; }

/* --- the controller bar, in the Devices shell chrome --------------------- */
.controller-host { max-width: var(--page-w); margin: 0 auto; padding: 1.25rem 1.5rem 0; }
.controller-bar { background: var(--panel); border: 1px solid var(--border);
  border-radius: 8px; padding: 0.55rem 0.9rem; font-size: var(--fs-body); }
/* In the domain shell the bar sits in `.controller-host`, which supplies the
   gap below it. A standalone page drops the bar straight into its column, so
   it carries its own — one rule rather than the same margin copied into three
   pages' style blocks. */
.controller-bar.standalone { margin-bottom: 0.9rem; }
/* Row one is identical on every domain, so switching tabs does not reshape
   it; anything domain-specific is the sentence below. */
.controller-row { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; }
.controller-why { margin: 0.4rem 0 0; color: var(--dim); font-size: var(--fs-label); }
.controller-label { font-size: var(--fs-label); text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--dim); font-weight: 600; }
.controller-choice { display: inline-flex; align-items: center; cursor: pointer; }
.controller-pick { display: inline-flex; align-items: center; gap: 0.35rem;
  white-space: nowrap; }
/* An option this domain cannot honour stays visible, with its reason. */
.controller-choice.unusable { cursor: default; opacity: 0.65; }
.controller-help { margin-left: auto; font-size: var(--fs-label); }
