/* The sketcher's page: the bars, the banner, the constraints window and the sheets.  The
 * canvas draws itself — nothing about the drawing is styled here. */
:root {
  --bg: #ffffff;
  --panel: #f4f4f5;
  --line: #d8d8dc;
  --text: #1b1b1f;
  --muted: #6b6b74;
  --accent: #1f77b4;
  --accent-soft: #e6f0f7;
  --danger: #b3001b;
  --danger-bg: #fdecea;
  --warn: #7a4a00;
  --warn-bg: #fff4e0;
  /* a claim the drawing proves is the one piece of good news the page has to report */
  --ok: #1e6b2e;
  --ok-bg: #eaf6ec;
  --row-sel: #fce4f3;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* the program's colours.  The core says what class a run of the source is; these say what the
     classes look like, and that is the whole of the division. */
  --syn-comment: #6a8759aa;
  --syn-word: #7d3fbf;
  --syn-type: #0f6f6f;
  --syn-label: #8a6d00;
  --syn-number: #a03000;
  /* a declared name.  Its own ink rather than the body's, since colour is the only thing a run
     here may carry — the weight that used to tell it apart moved the text out from under the
     caret */
  --syn-def: #0d3b66;
  /* two of them are colours the page already has, and are named rather than repeated: a relation
     is what the drawing accents and a claim is what a conflict is drawn in, so retuning the
     palette carries the program panel with it */
  --syn-relation: var(--accent);
  --syn-claim: var(--danger);
  /* a class names presentation, which is a different statement from what the drawing is — muted,
     because a sheet is a remark about the drawing and not part of it */
  --syn-class: #9c5d00;
  /* what an expression knows before the document says anything — `sqrt`, `pi`, `length(…)` */
  --syn-builtin: #3a5a99;
  /* a quoted string, which only a drawing (.svd) has */
  --syn-string: #2e7d32;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font: 13px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--text);
  background: var(--bg);
  overflow: hidden;
}
#app { display: flex; flex-direction: column; height: 100%; }
/* a phone's toolbars come and go over the page: the height left between them, not the largest */
@supports (height: 100dvh) { #app { height: 100dvh; } }

header { border-bottom: 1px solid var(--line); background: var(--panel); flex: none; }
.bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; padding: 5px 8px; }
.bar > * { flex: none; }        /* wrap onto a new row rather than shrinking to a sliver */
.bar + .bar { border-top: 1px solid var(--line); }
.bar .sep { width: 1px; height: 20px; background: var(--line); margin: 0 6px; }
.bar button { white-space: nowrap; }

/* A bar that folds its topics (`ui::fitBar`): one row while folding can make it one, and it
   wraps only where folding every topic cannot.  Open, a topic is its buttons standing on the bar;
   folded, a head named for it, dropping them as a menu does.  The head reads as pressed while a
   tool it holds is the one down, so a folded bar still says what the pointer will do. */
.bar.fitted { flex-wrap: nowrap; }
.bar.fitted.wrapping { flex-wrap: wrap; }
.topic { position: relative; display: flex; }
.topic-items { display: flex; align-items: center; gap: 4px; }
.topic > .topic-head { display: none; }
.topic.folded > .topic-head { display: block; }
.topic-head::after { content: ' ▾'; color: var(--muted); }
.topic-head[aria-expanded="true"] { background: var(--accent-soft); border-color: var(--accent); }
.topic.folded:has(> .topic-items > button[aria-pressed="true"]) > .topic-head {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600;
}
.topic.folded > .topic-items {
  display: none; position: absolute; top: calc(100% + 2px); left: 0; z-index: 20;
  flex-direction: column; align-items: stretch; min-width: 170px; padding: 4px; overflow-y: auto;
  background: var(--bg); border: 1px solid var(--line); border-radius: 7px;
  box-shadow: 0 8px 24px rgba(0,0,0,.18);
}
/* a list that would run off the right of the screen hangs from its head's right edge instead */
.topic.folded.flip > .topic-items { left: auto; right: 0; }
.topic.folded:has(> .topic-head[aria-expanded="true"]) > .topic-items { display: flex; }
.topic.folded > .topic-items > button {
  display: flex; justify-content: space-between; gap: 24px; text-align: left;
}
.topic.folded > .topic-items > .sep { width: auto; height: 1px; margin: 4px 2px; }
/* Every bar folded to its heads: they share one row under the menu bar (`ui::fitBars`). */
header.compact { display: flex; flex-wrap: wrap; }
header.compact > .menubar { flex: 1 0 100%; border-bottom: 1px solid var(--line); }
header.compact > .bar + .bar { border-top: 0; }
header.compact > .bar.fitted + .bar.fitted { padding-left: 0; }

button, select {
  font: inherit; color: inherit;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 3px 9px;
  cursor: pointer;
}
button:hover, select:hover { border-color: var(--accent); }
button[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }
button:disabled { opacity: .45; cursor: default; border-color: var(--line); }
button kbd { font: inherit; color: var(--muted); margin-left: 4px; }
label.check { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; color: var(--text); }

/* the menu bar.  It behaves like a desktop one: a click opens a menu, and while any menu
   is open the others take over on hover.  Which one is down is recorded once, in the top
   button's aria-expanded, and the list is dropped off that. */
.menubar { gap: 2px; padding: 2px 6px; }
.menu { position: relative; }
.menu > button { background: none; border: 1px solid transparent; padding: 3px 10px; }
.menu > button:hover, .menu > button[aria-expanded="true"] {
  background: var(--accent-soft); border-color: transparent; color: var(--accent);
}
.menu ul {
  display: none; position: absolute; top: calc(100% + 2px); left: 0; z-index: 20;
  min-width: 200px; margin: 0; padding: 4px; list-style: none;
  background: var(--bg); border: 1px solid var(--line); border-radius: 7px;
  box-shadow: 0 8px 24px rgba(0,0,0,.18);
}
.menu:has(> button[aria-expanded="true"]) ul { display: block; }
.menubar .brand {
  padding: 2px 7px; margin-right: 4px; font-weight: 600; letter-spacing: .04em;
  background: none; color: var(--accent); border-color: var(--accent);
}
.menubar .brand:hover { background: var(--accent-soft); }
.menu li.sep { height: 1px; margin: 4px 2px; background: var(--line); }
.menu li > button {
  display: flex; width: 100%; gap: 24px; justify-content: space-between;
  background: none; border: 0; padding: 4px 8px; white-space: nowrap;
}
.menu li > button:hover { background: var(--accent-soft); color: var(--accent); }
.menu li.head { padding: 4px 8px 2px; font-size: 11px; color: var(--muted); }
/* a check box: its tick in the margin before the label, room kept for it while unticked */
.menu li > button[role="menuitemcheckbox"] { position: relative; padding-left: 24px; }
.menu li > button[role="menuitemcheckbox"][aria-checked="true"]::before {
  content: '\2713'; position: absolute; left: 8px;
}

#banner { display: none; align-items: center; gap: 10px; padding: 7px 12px; font-weight: 600; flex: none; }
#banner.conflict { display: flex; background: var(--danger-bg); color: #8a0000; border-bottom: 1px solid #d62728; }
#banner.over, #banner.unbuilt { display: flex; background: var(--warn-bg); color: var(--warn); border-bottom: 1px solid #e69500; }
#banner-text { flex: 1; min-width: 0; }

#clist li.claim-proved      { color: var(--ok); }
#clist li.claim-refuted     { color: #d62728; }
#clist li.claim-independent { color: #b06d00; }

/* A claim's verdict, washed behind the statement that made it (`app/program.ts::marks`).
   Background only — a border or a padding here would advance the copy's glyphs past the box's
   and put the caret beside the character it is on; see the four rules in `app/editor.ts`.

   Deliberately faint: this app is for sketching, and a claim is a remark in the margin, so a
   verdict should be legible when looked at and invisible when not.  `proved` is the faintest of
   the three because it is the expected answer and the one nobody needs to act on; `refuted` is
   the strongest because it is the only one that is news. */
.claim-proved      { background: rgba(46, 139, 66, 0.13); }
.claim-refuted     { background: rgba(214, 39, 40, 0.20); }
.claim-independent { background: rgba(230, 149, 0, 0.17); }
/* a claim about solids, judged in the worker: held as measured, or undecided where the faceting
   cannot say (refuted is a claim's own wash) */
.claim-held        { background: rgba(46, 139, 66, 0.13); }
.claim-undecided   { background: rgba(230, 149, 0, 0.17); }
/* An energy's verdict (#121): the extremum its statement asked for, or a saddle, the other one,
   or no verdict at all. */
.extremum-asked    { background: rgba(46, 139, 66, 0.13); }
.extremum-other    { background: rgba(230, 149, 0, 0.17); }

main { flex: 1; display: flex; min-height: 0; }
#canvas-wrap { flex: 1; position: relative; min-width: 0; }
canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: crosshair; }
/* The workspace's three.js canvas, under the sketch's and deaf to the pointer, so every gesture
   reaches the one the sketch is stroked on and whatever a gesture draws lands over the solids.
   A positioned element paints above an in-flow one whatever its z-index, so the sketch canvas is
   given a place in the order too, or the solids cover it. */
#box { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
#canvas { position: relative; z-index: 1; }
/* the plane being drawn on, in the viewport's upper right as a CAD tool keeps it: over the
   canvases, under the windows that float over the drawing */
#view-picks { position: absolute; top: 10px; right: 12px; z-index: 3;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
  max-width: calc(100% - 24px); pointer-events: none; }
#view-picks > * { pointer-events: auto; }
#view-picks .picks { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
#plane-pick, #scenario-pick {
  display: flex; align-items: center; gap: 6px; padding: 3px 6px;
  font-size: 12px; color: var(--muted); background: var(--bg);
  border: 1px solid var(--line); border-radius: 6px;
  box-shadow: 0 1px 4px rgba(0,0,0,.08); }
#scenario-pick[hidden] { display: none; }
#plane-pick select, #scenario-pick select { font: inherit; color: var(--text); }
#scenario-pick.failed select { color: var(--danger); }
/* the scenario drawn: its card, the exaggeration in its head, the legend, its lines */
#scenario-card { width: 320px; max-width: 100%; max-height: 55vh; display: flex;
  flex-direction: column; font-size: 12px; background: var(--bg);
  border: 1px solid var(--line); border-radius: 7px; box-shadow: 0 2px 10px rgba(0,0,0,.12); }
#scenario-card[hidden] { display: none; }
#scenario-card header { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px;
  padding: 6px 9px; border-bottom: 1px solid var(--line); color: var(--muted); }
#scenario-card header label { display: flex; align-items: center; gap: 4px; }
#scenario-exaggerate { flex: 1 1 100%; }
#scenario-exaggerate input { flex: 1; min-width: 80px; }
#scenario-scale-text { font-family: var(--mono); color: var(--text); min-width: 4.5em;
  text-align: right; }
#scenario-legend { display: flex; align-items: center; gap: 6px; padding: 6px 9px 0;
  font-family: var(--mono); color: var(--muted); }
#scenario-legend[hidden] { display: none; }
#scenario-ramp { flex: 1; height: 9px; border-radius: 3px; }
#scenario-status { margin: 0; padding: 4px 9px; color: var(--muted); white-space: pre-line; }
#scenario-status:empty { display: none; }
#scenario-status.failed { color: var(--danger); }
#scenario-lines { list-style: none; margin: 0; padding: 4px 0 6px; overflow: auto; }
#scenario-lines li { display: flex; gap: 6px; align-items: baseline; padding: 2px 9px;
  cursor: pointer; }
#scenario-lines li:hover, #scenario-lines li.lit { background: var(--accent-soft); }
#scenario-lines .glyph { flex: none; width: 1.2em; text-align: center; }
#scenario-lines code { font-family: var(--mono); white-space: pre-wrap; word-break: break-word; }
#scenario-lines .said { display: block; color: var(--muted); }
#scenario-lines li.kept .said { color: var(--ok); }
#scenario-lines li.provisional .said { opacity: 0.6; }
#scenario-lines li.refuted .said { color: var(--danger); }
/* the workspace's own buttons — orbit — in its lower right corner, under a thumb: a finger's
   only (below), since a mouse orbits with its right button */
#view-buttons { position: absolute; right: 12px; bottom: 12px; z-index: 3;
  display: none; gap: 6px; }
#view-buttons button { box-shadow: 0 1px 4px rgba(0,0,0,.08); }
#view-buttons button:not([aria-pressed="true"]) { background: var(--bg); }
.drawing-active :is(#view-picks, #view-buttons) { visibility: hidden; }
canvas.select { cursor: default; }

#drawing-view { position: absolute; inset: 0; z-index: 2; display: flex;
  flex-direction: column; padding: 16px; gap: 12px; background: var(--panel); }
#drawing-view[hidden] { display: none; }
.drawing-toolbar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
#drawing-viewport { position: relative; flex: 1; min-height: 0; overflow: hidden;
  cursor: grab; touch-action: none; user-select: none; }
#drawing-viewport.panning { cursor: grabbing; }
#drawing-viewport:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
#drawing-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;
  transform-origin: center; pointer-events: none; filter: drop-shadow(0 2px 3px #0002); }
#drawing-zoom { min-width: 4ch; text-align: center; font-variant-numeric: tabular-nums; }
#drawing-status { margin: 0; white-space: pre-wrap; }
#drawing-status:empty { display: none; }
.drawing-active #canvas, .drawing-active #box, .drawing-active #cpanel,
.drawing-active #measure, .drawing-active #banner, .drawing-active #component,
.drawing-active #stats { visibility: hidden; }
.drawing-active #bar-tools, .drawing-active #bar-constraints { opacity: .35; }

/* what holds the selected component: a small window over the drawing rather than a column
   beside it, so the page is the drawing.  It comes up where it was last put — its header is
   a handle — and only while something is picked, so it costs nothing when nothing is. */
#cpanel {
  position: absolute; top: 12px; right: 12px; z-index: 6;
  display: flex; flex-direction: column;
  width: 300px; max-width: calc(100% - 24px); max-height: calc(100% - 24px);
  border: 1px solid var(--line); border-radius: 7px;
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  box-shadow: 0 4px 18px rgba(0,0,0,.16);
}
#cpanel[hidden] { display: none; }
#cpanel h2 {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
  margin: 0; padding: 7px 10px 6px; border-bottom: 1px solid var(--line); flex: none;
  cursor: move; user-select: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#clist { list-style: none; margin: 0; padding: 0; overflow: auto; flex: 1; font-family: var(--mono); font-size: 12px; }
#clist li { padding: 3px 10px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#clist li:hover { background: #e9e9ed; }
#clist li[aria-current="true"] { outline: 1px solid var(--accent); outline-offset: -1px; background: var(--row-sel); }
#clist li.culprit { color: var(--danger); }
#clist li.violated { color: #d62728; }
#clist li.over { color: #b06d00; }
#clist li.implied { color: var(--muted); font-style: italic; }
#clist li.expr-error { color: #b06d00; text-decoration: underline dotted; }

footer {
  flex: none; border-top: 1px solid var(--line); background: var(--panel);
  padding: 5px 10px; font-family: var(--mono); font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow-x: auto;
}
footer #toast { color: var(--text); }
/* what is picked, said in words: the same line the constraints window is headed by */
footer #component { color: var(--text); }
/* the geometry on screen does not satisfy the constraints — the numbers in the status
   line are describing a sketch you are not actually looking at */
footer.unsolved { background: var(--danger-bg); border-top-color: #d62728; color: #a03b46; }
footer.unsolved #toast { color: #8a0000; }
/* the claims about solids being judged in a worker: refuted stays red */
footer #claims { float: right; margin-left: 16px; color: var(--text); }
footer #claims.failed { color: #a03b46; }
footer #claims.finished { color: var(--muted); }
/* the load scenarios' requirements, answered after each solve: one that fails stays red */
footer #scenarios { float: right; margin-left: 16px; color: var(--text); }
footer #scenarios.failed { color: #a03b46; }
footer #scenarios.finished { color: var(--muted); }

dialog {
  border: 1px solid var(--line); border-radius: 9px; padding: 0;
  max-width: min(94vw, 900px); max-height: 86vh; color: var(--text); background: var(--bg);
  box-shadow: 0 12px 40px rgba(0,0,0,.22);
}
dialog::backdrop { background: rgba(0,0,0,.28); }
dialog h3 { margin: 0; padding: 12px 16px; border-bottom: 1px solid var(--line); font-size: 14px; }
dialog .body { padding: 12px 16px; overflow: auto; max-height: 62vh; }
dialog pre { font-family: var(--mono); font-size: 12px; margin: 0; white-space: pre; }
dialog p { margin: 0 0 8px; }
dialog p:last-child { margin-bottom: 0; }
dialog a { color: var(--accent); text-decoration: none; }
dialog a:hover { text-decoration: underline; }
/* the About diagram: what the sketcher is made of, in the order it was made */
.dag { display: block; width: 360px; max-width: 100%; height: auto; margin: 12px auto; }
.dag rect { fill: var(--accent-soft); stroke: var(--accent); }
.dag text { fill: var(--text); font: 11px system-ui, -apple-system, sans-serif; }
.dag path { fill: none; stroke: var(--muted); stroke-width: 1.2; }
.dag marker path { fill: var(--muted); stroke: none; }
dialog .actions { display: flex; justify-content: flex-end; gap: 8px; padding: 10px 16px; border-top: 1px solid var(--line); }
dialog input[type=number], dialog input[type=text], dialog input[type=password] {
  font: inherit; width: 100%; padding: 6px 8px; border: 1px solid var(--line); border-radius: 5px;
}
dialog .choices { display: flex; flex-direction: column; gap: 4px; }
/* a settings sheet: one control per row, each taking effect as it is switched */
dialog .fields { display: flex; flex-direction: column; gap: 10px; }
dialog .fields label.check { display: flex; }
dialog .choices button { text-align: left; font-family: var(--mono); font-size: 12px; }
dialog .choices .described-choice {
  display: flex; flex-direction: column; gap: 5px; padding: 12px;
  font-family: inherit; font-size: 13px; white-space: normal;
}
dialog .choices button[hidden] { display: none; }
.described-choice strong { font-size: 14px; font-weight: 600; }
.described-choice span { color: var(--muted); line-height: 1.5; }
/* pinned to the top of the scrolling body; opaque, so the list passes under it.  A sticky box
   stops at the body's padding edge, so it is raised by the body's 12px to cover that strip too */
dialog .search-bar {
  position: sticky; top: -12px; z-index: 1; background: var(--bg); padding: 10px 0;
}
dialog input[type=search] {
  display: block; width: 100%; margin: 0; padding: 8px 10px;
  font: inherit; border: 1px solid var(--line); border-radius: 5px;
}

/* the loading modal: over the whole page, the bar a fraction where one is known and a sweep where
 * not — a transform, so it keeps moving while the page's thread is busy with a solve */
#loading {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center;
  background: rgba(0,0,0,.18);
}
#loading[hidden] { display: none; }
.loading-card {
  width: min(360px, calc(100vw - 32px)); padding: 14px 16px; border-radius: 8px;
  background: var(--bg); border: 1px solid var(--line); box-shadow: 0 8px 28px rgba(0,0,0,.18);
}
#loading-label {
  margin: 0 0 10px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.loading-bar { height: 4px; border-radius: 2px; background: var(--accent-soft); overflow: hidden; }
#loading-fill {
  height: 100%; width: 100%; background: var(--accent); transform-origin: left;
  transform: scaleX(var(--done, 0)); transition: transform .25s ease-out;
}
#loading.sweeping #loading-fill {
  width: 30%; transform: none; transition: none; animation: loading-sweep 1.1s ease-in-out infinite;
}
@keyframes loading-sweep { from { transform: translateX(-100%); } to { transform: translateX(340%); } }

/* the build bar (#672): along the workspace's bottom edge while any object is being built, a line
 * of the fraction done over the words; marked red by a refusal not yet seen.  Clicked, the list of
 * the jobs opens above it.  The page under it is never covered: only the bar takes the pointer */
#build-bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 4; display: flex;
  flex-direction: column; pointer-events: none; }
#build-bar[hidden] { display: none; }
#build-bar-head { pointer-events: auto; position: relative; display: flex; gap: 10px; align-items: center;
  width: 100%; height: 22px; padding: 0 10px; border: 0; border-top: 1px solid var(--line); border-radius: 0;
  background: color-mix(in srgb, var(--panel) 92%, transparent); color: var(--muted); font-size: 12px;
  text-align: left; cursor: pointer; }
#build-bar-head:hover { color: var(--text); }
#build-bar .build-track { position: absolute; left: 0; right: 0; top: -1px; height: 3px; overflow: hidden; }
#build-bar .build-fill { display: block; height: 100%; width: 100%; background: var(--accent);
  transform-origin: left; transition: transform .25s ease-out; }
#build-bar .build-text { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#build-bar .build-time { flex: none; font-variant-numeric: tabular-nums; }
#build-bar.marked .build-fill { background: #d62728; }
#build-bar.marked #build-bar-head { color: #a03b46; }
#build-bar .build-list { pointer-events: auto; margin: 0; padding: 4px 0; list-style: none; max-height: 40vh;
  overflow: auto; background: var(--bg); border-top: 1px solid var(--line); font-size: 12px; }
#build-bar .build-row { display: grid; grid-template-columns: minmax(80px, 1fr) 70px minmax(0, 2fr) 44px;
  gap: 10px; padding: 2px 10px; cursor: pointer; }
#build-bar .build-row:hover { background: var(--accent-soft); }
#build-bar .build-row > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#build-bar .build-state, #build-bar .build-detail, #build-bar .build-clock { color: var(--muted); }
#build-bar .build-clock { text-align: right; font-variant-numeric: tabular-nums; }
#build-bar .build-row.refused .build-state, #build-bar .build-row.refused .build-detail { color: #a03b46; }
#build-bar .build-row.waiting { opacity: .6; }

/* the measurement readout: pinned to the canvas's lower right, only while two things are picked */
/* the timeline (#398 L): a strip in the viewport's lower left, a row per parameter.  The track's
   solid part is what the march reached; past it, hatched, no drawing was followed, and the mark
   is the singular instant the march stopped at */
#timeline {
  position: absolute; left: 12px; bottom: 12px; z-index: 3;
  display: flex; flex-direction: column; gap: 4px;
  width: min(460px, calc(100% - 24px)); padding: 6px 9px;
  border: 1px solid var(--line); border-radius: 7px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  box-shadow: 0 2px 10px rgba(0,0,0,.08);
  font-size: 12px;
}
#timeline[hidden] { display: none; }
.drawing-active #timeline { visibility: hidden; }
.tl-row { display: flex; align-items: center; gap: 8px; }
.tl-row.tl-off { color: var(--muted); }
.tl-name { font-family: var(--mono); font-weight: 600; min-width: 1.5em; }
.tl-track { position: relative; flex: 1; height: 18px; display: flex; align-items: center;
  border-radius: 3px;
  background: repeating-linear-gradient(135deg, transparent 0 4px, var(--line) 4px 6px); }
.tl-reach { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px;
  background: var(--panel); }
.tl-singular { position: absolute; top: -2px; bottom: -2px; width: 3px; margin-left: -1.5px;
  background: var(--danger); border-radius: 2px; z-index: 1; }
.tl-singular[hidden] { display: none; }
.tl-track input[type="range"] { position: relative; z-index: 2; width: 100%; margin: 0;
  background: transparent; accent-color: var(--accent); }
.tl-at { width: 9em; font-family: var(--mono); font-size: 12px; padding: 1px 4px;
  border: 1px solid var(--line); border-radius: 4px; background: var(--bg); color: var(--text); }
.tl-row.tl-shown .tl-at { border-color: var(--accent); color: var(--accent); }

/* the section bar: a strip in the viewport's upper left while the workspace is cut by a plane */
#section-bar {
  position: absolute; left: 12px; top: 12px; z-index: 3;
  display: flex; align-items: center; gap: 8px;
  width: min(460px, calc(100% - 24px)); padding: 6px 9px;
  border: 1px solid var(--line); border-radius: 7px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  box-shadow: 0 2px 10px rgba(0,0,0,.08);
  font-size: 12px;
}
#section-bar[hidden] { display: none; }
.drawing-active #section-bar { visibility: hidden; }
.sb-name { font-weight: 600; }
#section-bar select { font-size: 12px; max-width: 10em; }
#section-bar input[type="range"] { flex: 1; min-width: 60px; margin: 0;
  accent-color: var(--accent); }
.sb-at { width: 6em; font-family: var(--mono); font-size: 12px; padding: 1px 4px;
  border: 1px solid var(--line); border-radius: 4px; background: var(--bg); color: var(--text); }
#section-bar button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }

#measure {
  position: absolute; right: 12px; bottom: 12px; display: none;
  padding: 7px 11px; border: 1px solid var(--line); border-radius: 7px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  box-shadow: 0 2px 10px rgba(0,0,0,.08);
  font-family: var(--mono); font-size: 12px; line-height: 1.5;
  pointer-events: none; white-space: pre; text-align: right;
}
#measure.on { display: block; }
#measure b { color: var(--muted); font-weight: 600; }

/* a dimension's number, edited where it is drawn: the box sits on the callout's own label,
   so what is being typed is in the place it will be read */
#canvas-wrap input.dim {
  position: absolute; transform: translate(-50%, -50%); z-index: 5;
  padding: 1px 3px; text-align: center;
  font-family: var(--mono); font-size: 12px; line-height: 1.2;
  color: var(--accent); background: var(--bg);
  border: 1px solid var(--accent); border-radius: 4px;
  box-shadow: 0 1px 6px rgba(0,0,0,.18);
}
#canvas-wrap input.dim:focus { outline: none; box-shadow: 0 0 0 2px var(--accent-soft); }
/* still being carried: the click that settles where it goes belongs to the canvas under it */
#canvas-wrap input.dim.carried { pointer-events: none; }

/* The program the drawing came from, shown beside it.  A column and not a floating window like
   #cpanel: the constraints window is a *remark* about what is picked and goes away with it, but
   the program is what the drawing IS — permanently present, and typed into.  A window big enough
   to edit a program in would cover the thing it describes. */
#ppanel {
  flex: none; width: 30%; min-width: 240px; max-width: 60%;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--line); background: var(--panel);
}
#ppanel[hidden] { display: none; }
/* One pane at a time, the one the head's chooser names (`program.ts::showSide`): the program —
   its name, its file bar, the code box and its diagnostics — the model outline, or a project's
   files.  The rest stay in the document, and only out of the layout; and a head's button belongs
   to the pane it acts on. */
#ppanel[data-side="program"] > :is(#project-pane, #outline-pane),
#ppanel[data-side="outline"] > :not(#ppanel-head, #outline-pane),
#ppanel[data-side="project"] > :not(#ppanel-head, #project-pane),
#ppanel:not([data-side="program"]) .side-program,
#ppanel:not([data-side="outline"]) .side-outline { display: none; }
#project-pane, #outline-pane { flex: 1; min-height: 0;
  display: flex; flex-direction: column; overflow: hidden; background: var(--bg); }
#program-name[hidden] { display: none; }
.project-disclosure { display: inline-block; flex: none; order: -1;
  width: 2.4em; height: 1.44em; fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
#project-tree details[open] > summary > .project-disclosure { transform: rotate(90deg); }
#ppanel #project-title { text-transform: none; letter-spacing: 0; }
/* A folder and a file are one row: the same padding, then an icon column of one width (a folder's
   disclosure, a file's glyph), then the name — so every name at one depth starts at one x, and a
   folder's children indent by exactly that column, putting their icons under its name. */
#project-tree { --tree-icon: 14px; --tree-gap: 6px; overflow: auto; flex: 1; min-height: 0;
  padding: 5px; font-size: 12px; }
#project-tree[hidden] { display: none; }
#project-tree summary, .project-file { display: flex; align-items: center; gap: var(--tree-gap);
  padding: 4px 8px; border-radius: 3px; white-space: nowrap; }
#project-tree summary { list-style: none; cursor: pointer; color: var(--muted); }
#project-tree summary::-webkit-details-marker { display: none; }
#project-tree summary > .project-disclosure { width: var(--tree-icon); height: var(--tree-icon); }
.project-folder { padding-left: calc(var(--tree-icon) + var(--tree-gap)); }
.project-file { width: 100%; border: 0; background: transparent; color: var(--text);
  text-align: left; font: inherit; }
.project-file::before { content: '◇'; flex: none; width: var(--tree-icon); text-align: center;
  color: var(--muted); }
.project-file.drawing-file::before { content: '▤'; }
.project-file.text-file::before { content: '¶'; }
.project-file[aria-current] { background: var(--accent-soft); color: var(--accent); }
/* The model outline (#435): the panel's second pane, styled as a project's tree is — one row
   per instance, copy, entity or object, an eye to hide it, its name to select it, and a way to its
   statement. */
.outline-item[aria-expanded="true"] > .outline-row > .outline-twist > .project-disclosure {
  transform: rotate(90deg); }
#outline-tree { --tree-icon: 14px; --tree-gap: 4px; overflow: auto; flex: 1; min-height: 0;
  padding: 5px; font-size: 12px; }
#outline-tree[hidden] { display: none; }
#outline-tree:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.outline-row { display: flex; align-items: center; gap: var(--tree-gap); border-radius: 3px;
  padding: 1px 4px 1px calc(4px + var(--depth, 0) * (var(--tree-icon) + var(--tree-gap)));
  white-space: nowrap; }
.outline-row > span { flex: none; width: var(--tree-icon); }
.outline-row button { border: 0; background: transparent; color: inherit; font: inherit;
  padding: 0; cursor: pointer; }
.outline-twist { flex: none; width: var(--tree-icon); height: var(--tree-icon);
  color: var(--muted); }
.outline-twist:disabled { visibility: hidden; }
.outline-twist .project-disclosure { width: var(--tree-icon); height: var(--tree-icon); }
.outline-eye { flex: none; width: var(--tree-icon); height: var(--tree-icon); color: var(--muted); }
.outline-eye svg { display: block; width: 100%; height: 100%; }
.outline-eye .lid { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;
  stroke-linejoin: round; }
.outline-eye .pupil { fill: currentColor; }
.outline-eye .faint { opacity: .3; }
.outline-eye[data-state="shut"] { opacity: .6; }
.outline-eye:hover { color: var(--accent); }
.outline-label { flex: 1; min-width: 0; display: flex; align-items: center; gap: 5px;
  text-align: left; padding: 3px 2px !important; }
.outline-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* what the row is — a point, a line, a plane, an object, an instance — drawn in the tree's ink */
.outline-icon { flex: none; width: var(--tree-icon); height: var(--tree-icon); color: var(--muted); }
.outline-row[aria-current] .outline-icon, .outline-row:hover .outline-icon { color: inherit; }
.outline-icon path { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;
  stroke-linejoin: round; }
.outline-icon .fill { fill: currentColor; stroke: none; }
.outline-icon .tint { fill: currentColor; fill-opacity: .25; }
.outline-icon .dots { stroke-width: 2.5; stroke-dasharray: 0 4; }
.outline-label.kind-instance, .outline-label.kind-block, .outline-label.kind-datums {
  color: var(--muted); }
.outline-source { flex: none; visibility: hidden; color: var(--muted); }
.outline-row:hover .outline-source, .outline-source:focus-visible { visibility: visible; }
.outline-open { flex: none; visibility: hidden; color: var(--muted); font-size: 11px; }
.outline-row:hover .outline-open, .outline-open:focus-visible,
.outline-row.opened .outline-open { visibility: visible; }
.outline-row.opened .outline-open { color: var(--accent); }
.outline-row:hover { background: var(--accent-soft); }
.outline-row[aria-current] { background: var(--accent-soft); color: var(--accent); }
/* the row a right-click's Show in model viewer went to, lit a moment so the eye finds it */
.outline-row.found { animation: outline-found 1.6s ease-out; }
@keyframes outline-found {
  from { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
}
.outline-row.operand .outline-label { font-style: italic; color: var(--muted); }
/* hidden, and under a hidden row: the name stays, faded, so it can be found and shown again */
.outline-row.hidden .outline-label, .outline-row.dimmed .outline-label { opacity: .45; }
/* an object being built (#672), and one whose build was refused, which says why on hover */
.outline-row.building .outline-name::after { content: ' · building…'; color: var(--muted); font-style: italic; }
.outline-row.unbuilt .outline-name { color: #a03b46; }
.outline-row.unbuilt .outline-name::after { content: ' · not built'; font-style: italic; }
.outline-group[hidden] { display: none; }
#program-name { padding: 6px 10px; font: 12px var(--mono); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; border-bottom: 1px solid var(--line); }
/* The partition.  `min-width`/`max-width` above are the whole of how far it may be dragged —
   `program.ts` reads them off the computed style rather than keeping its own pair, so the limit
   is stated once and in the place that already enforces it.  It is wider than it looks: the
   border it sits against is one pixel and a one-pixel target is not a target. */
#psplit {
  flex: none; width: 7px; margin-right: -7px; z-index: 1;
  cursor: col-resize; background: transparent;
}
#psplit:hover, #psplit:focus-visible, #psplit.dragging { background: var(--accent-soft); }
#psplit:focus-visible { outline: none; }
#psplit[hidden] { display: none; }
/* The panel's head: the chooser of its pane, what the program's state is, and the shown pane's
   buttons — and on a narrow screen, first, the fold. */
#ppanel-head { flex: none; display: flex; align-items: center; gap: 6px; padding: 4px 6px;
  border-bottom: 1px solid var(--line); }
#ppanel-fold { display: none; flex: none; align-items: center; justify-content: center;
  padding: 0 2px; border: 0; border-radius: 3px; background: transparent; color: var(--muted);
  cursor: pointer; }
#ppanel-fold:hover { background: var(--accent-soft); }
#ppanel-fold[aria-expanded="true"] .project-disclosure { transform: rotate(90deg); }
#ppanel-side { flex: none; min-width: 0; padding: 2px 4px; font: inherit; font-size: 12px;
  font-weight: 600; color: var(--text); background: var(--bg);
  border: 1px solid var(--line); border-radius: 4px; }
#ppanel-state { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: 11px; color: var(--muted); }
#ppanel.dirty #ppanel-state { color: var(--accent); }
#program-compile, #outline-isolate, #outline-show-all { flex: none; padding: 1px 8px;
  font-size: 12px; }
#ppanel h2 {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
  margin: 0; padding: 7px 10px 6px; border-bottom: 1px solid var(--line); flex: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; user-select: none;
}
#program-files {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  font-size: 12px; color: var(--muted); border-bottom: 1px solid var(--line);
}
#program-files[hidden] { display: none; }
#program-file {
  min-width: 0; flex: 1; padding: 5px; font: inherit; color: var(--text);
  background: var(--bg); border: 1px solid var(--line); border-radius: 4px;
}
/* The code box (`app/editor.ts`): the source drawn twice, the coloured copy behind and the box
   somebody types in over it with its own glyphs invisible.  Every rule that decides where a
   character lands is therefore shared, and written once, on both.

   Sharing the declarations is necessary and not sufficient, because the two layers do not lay the
   *same* text out: the box is one uninterrupted run per line, the copy is one element per coloured
   run.  Hence the two rules below that look like fussiness and are not.

   `line-height` is a whole number of pixels.  1.45 of 12px is 17.4, and a textarea's internal
   layout and a `<pre>`'s round a fractional line box independently — a fifth of a pixel a line,
   invisible at the top of a file and a whole line out at the bottom.

   Kerning and ligatures are off.  Both form across a *run*, and the runs differ, so `//` or `==`
   set as a pair on one layer and as two glyphs on the other puts the rest of that line out of
   step.

   The third rule cannot be written here because it is about every class further down: a run may
   change the colour and never the face.  See `app/editor.ts`. */
/* the host clips; the copy is moved inside it and is therefore free to be as big as the text */
.code-host { position: relative; flex: 1; min-height: 0; overflow: hidden; background: var(--bg); }
.code-copy, .code-box {
  position: absolute; top: 0; left: 0; margin: 0; border: 0; padding: 8px 10px;
  font: 400 12px/17px var(--mono); tab-size: 2;
  letter-spacing: normal; word-spacing: normal; white-space: pre;
  font-kerning: none; font-variant-ligatures: none;
}
/* Moved by script, from the box in front of it — never scrolled.  It has no scrollbars where the
   box has them, so its scroll *range* is shorter and an assignment would clamp at the bottom of a
   long file; a transform has no range.  See `follow()` in app/editor.ts.  It sizes to its text
   rather than to the host, which is what gives the transform something to reveal. */
.code-copy {
  width: max-content; min-width: 100%; height: max-content; min-height: 100%;
  color: var(--text); will-change: transform;
}
.code-box {
  right: 0; bottom: 0; resize: none; outline: none; overflow: auto;
  background: transparent; color: transparent; caret-color: var(--text);
}
.code-box:focus { box-shadow: inset 2px 0 0 var(--accent); }
/* Colour only.  A weight or a style here picks a different font face, whose advances the regular
   face makes no promise about — and the box, which has no spans to style, stays regular
   throughout, so the text would slide out from under the caret on every line that used one. */
.code-copy .comment { color: var(--syn-comment); }
/* The statement whose element or constraint is picked on the drawing.
   It reads as bold and is **not** bold: the box in front of this copy is one face throughout, so
   a heavier run here would advance its glyphs further than the box advances the same characters,
   and every character after it on the line would sit beside the one the caret is really on.
   `-webkit-text-stroke` thickens the drawn outline and leaves the advance width alone, which is
   the one way to say "bold" that `npm run overlay` still agrees with. */
.code-copy .lit {
  background: var(--accent-soft);
  -webkit-text-stroke: 0.4px currentColor;
}
/* The name under the caret, and every other place the text names it (`app/occurrences.ts`):
   faint where it is read, stronger where it is given — the two inks every editor uses for reads
   and writes.  Background only, as every mark here (`app/editor.ts`), and after `.lit` so a name
   inside the picked statement still reads as one. */
.code-copy .occurrence { background: rgba(87, 87, 87, 0.16); }
.code-copy .occurrence-def { background: rgba(14, 99, 156, 0.22); }
.code-copy .word { color: var(--syn-word); }
.code-copy .type { color: var(--syn-type); }
.code-copy .relation { color: var(--syn-relation); }
.code-copy .def { color: var(--syn-def); }
.code-copy .label { color: var(--syn-label); }
.code-copy .number { color: var(--syn-number); }
/* a seed is where a solve starts and a claim is what it must honour: the one difference in the
   language a reader most needs to see at a glance */
.code-copy .seed { color: var(--muted); }
.code-copy .claim { color: var(--syn-claim); }
/* a class is presentation, which is a different statement from what the drawing is — so it
   reads as one, and not as the geometry it qualifies */
.code-copy .class { color: var(--syn-class); }
.code-copy .builtin { color: var(--syn-builtin); }
.code-copy .string { color: var(--syn-string); }

#pdiags {
  flex: none; max-height: 34%; overflow: auto; list-style: none; margin: 0; padding: 0;
  border-top: 1px solid var(--line); font-family: var(--mono); font-size: 11px;
}
#pdiags:empty { display: none; }
#pdiags li { padding: 3px 10px; cursor: pointer; white-space: pre; }
#pdiags li.error { color: var(--danger); background: var(--danger-bg); }
#pdiags li.warn { color: var(--warn); background: var(--warn-bg); }

/* -- a narrow screen ------------------------------------------------------------------------

   The drawing takes the width, and the panel is a drawer under it, above the footer: its head
   folds it (`program.ts::bindDrawer`), and it opens folded.  The partition has nothing to divide
   across, and a width the handle set on a wide screen is the column's, not the drawer's. */
@media (max-width: 720px) {
  main { flex-direction: column; }
  #canvas-wrap { min-height: 0; }
  #psplit { display: none; }
  #ppanel { width: auto !important; min-width: 0; max-width: none; height: 45%;
    border-left: 0; border-top: 1px solid var(--line); }
  #ppanel-fold { display: flex; }
  #ppanel.folded { height: auto; }
  #ppanel.folded > :not(#ppanel-head) { display: none; }
  #ppanel.folded #ppanel-head { border-bottom: 0; }
  #plane-pick > span, #scenario-pick > span { display: none; }
}

/* -- a finger -------------------------------------------------------------------------------

   A control a fingertip can land on: every button and chooser grows to a tap's height, and the
   keyboard chips go, since a touch screen has no keys to name.  The sizes stay constant: a bar
   short of room folds its topics (`ui::fitBar`) rather than shrink them. */
@media (pointer: coarse) {
  #view-buttons { display: flex; }
  button, select { min-height: 36px; padding: 6px 12px; }
  .bar { gap: 6px; }
  .menu > button { padding: 6px 12px; }
  .menu li > button, .topic.folded > .topic-items > button { padding: 10px 12px; }
  .menubar .brand { padding: 6px 10px; }
  #program-compile, #outline-isolate, #outline-show-all { min-height: 32px; padding: 4px 12px;
    font-size: 13px; }
  #ppanel-fold { min-height: 40px; min-width: 40px; }
  #pdiags li, #clist li { padding: 8px 10px; }
}
@media (hover: none) and (pointer: coarse) {
  button kbd { display: none; }
}

/* What a ⌘-click had under it, to choose among (`choose.ts`): a row each, the one the pointer or
   the keyboard is on lit where it is drawn — and a right-click's menu, drawn the same */
#canvas-wrap .chooser {
  position: absolute; z-index: 7;
  max-width: min(420px, calc(100% - 8px)); max-height: 50vh; overflow-y: auto;
}
.chooser {
  display: flex; flex-direction: column; min-width: 200px; padding: 4px; font-size: 12px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 7px;
  box-shadow: 0 4px 16px rgba(0,0,0,.18);
}
.chooser .chooser-head { color: var(--muted); padding: 2px 8px 4px; }
.chooser button {
  display: flex; gap: 10px; align-items: baseline; text-align: left;
  border: none; background: none; padding: 4px 8px; border-radius: 4px;
}
.chooser button:hover, .chooser button:focus, .chooser button.active {
  background: var(--accent-soft); color: var(--accent); outline: none;
}
.chooser .kind { color: var(--muted); min-width: 4.5em; }
.chooser .verb { min-width: 4.5em; }
.chooser button:focus .kind { color: inherit; }
.chooser .name { font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; }
.chooser .note { margin-left: auto; color: var(--muted); font-style: italic; }
/* The program editor's right-click menu (`code-menu.ts`), drawn as the workspace's: over the
   whole page, since it opens wherever the editor is.  The row the arrows are on is `.active`,
   since the menu never has the focus, which stays in the text */
.chooser.code-menu { position: fixed; z-index: 50; }
.code-menu .note { padding-left: 24px; font-style: normal; }
.code-menu button.active .note { color: inherit; }
.code-menu button[aria-disabled="true"] { color: var(--muted); background: none; cursor: default; }
.code-menu hr { border: none; border-top: 1px solid var(--line); margin: 4px 6px; }
