/* portal-components.css - THE component vocabulary. Ten components, one file. (R2, 2026-07-26.)
   ============================================================================================
   WHAT THIS IS FOR. Before R2 the portal had no component layer: it had ~157 named things, 70 of
   which were also delegated-event hooks, each carrying its own padding, border, radius and colour,
   grown one session at a time. That is why a row looks different in three places and why "make the
   spacing consistent" was never a small job. This file is the ten shapes everything is actually made
   of, defined once, so R3 re-homes markup onto a vocabulary instead of inventing one per pane.

   HOW IT RELATES TO THE OTHER TWO LAYERS:
     portal.css              tokens (colour, the seven type steps, spacing, measure) + the base layer
     portal-components.css   THIS FILE - the ten shapes, built only from those tokens
     the sty-* twins         the existing look, split off its behavioural names at R2 so it can be
                             replaced piece by piece without touching a single event hook

   WHY sty-* AND pc-* BOTH EXIST RIGHT NOW, and how that ends. sty-* is the OLD look, mechanically
   detached from the behavioural names; pc-* is the NEW look. R3 migrates markup from the first to the
   second one component at a time, and a sty- rule with no remaining markup is dead and gets deleted.
   Nothing here overrides sty-*: they are separate classes, so a pane adopts a component by ADDING the
   pc- class and DROPPING its sty- one, which is a reviewable one-line change per element.

   NAMING. pc- for "portal component". Modifiers use --, per the existing convention in this codebase.
   NOTHING IN THIS FILE IS EVER MATCHED BY JAVASCRIPT. That is the whole point: a class in this file
   can be renamed, merged or deleted by a designer with no possibility of breaking a control. If you
   find yourself wanting to write querySelector('.pc-...'), the thing you want is a data attribute.
   ============================================================================================ */

/* ── 1. ROW ─────────────────────────────────────────────────────────────────────────────────────
   The workhorse: one subject, full bleed inside its container, separated from its neighbours by a
   hairline rather than by a border of its own. A stack of individually bordered cards, one per row,
   is the visual signature of the problem this redesign exists to fix - so a row has no border. */
.pc-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3, 12px);
  min-height: 56px;
  padding: var(--sp-3, 12px) var(--sp-4, 16px);
  border-bottom: 1px solid var(--pv2-border-lt);
  font-size: var(--t-body);
  color: var(--pv2-dark);
}
.pc-row:last-child { border-bottom: 0; }
.pc-row--interactive { cursor: pointer; }
.pc-row--interactive:hover { background: var(--pv2-bg); }
.pc-row--selected { background: #e8eeee; box-shadow: inset 3px 0 0 var(--portal-accent); }
.pc-row-label { font-weight: 500; min-width: 0; }
/* The meta line is the pressure valve for the one-chip rule below: a second thing worth saying about
   a row goes here as words, not as another chip. */
.pc-row-meta { display: block; font-size: var(--t-small); color: var(--pv2-ink-3); font-weight: 400; }
.pc-row-value { margin-left: auto; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A field row is a labelled control. Stacked on a phone, side by side once there is room. */
.pc-row--field { flex-direction: column; align-items: stretch; gap: var(--sp-1, 4px); }
@media (min-width: 900px) {
  .pc-row--field { flex-direction: row; align-items: center; gap: var(--sp-3, 12px); }
  .pc-row--field > .pc-row-label { flex: 0 0 220px; }
}
@media (max-width: 899px) {
  /* 60px, not 56px: a thumb needs more than a mouse does, and this is the surface Luke uses in a
     workshop. Paired with the 44px hit floor below. */
  .pc-row { min-height: 60px; }
}

/* ── 2. CARD ────────────────────────────────────────────────────────────────────────────────────
   A bounded container for ONE subject. Never wraps a single row, never nests more than one level: a
   card inside a card means the inner thing is a section, so it takes .pc-section instead. NO SHADOW,
   ever - depth is reserved for things that float over the page (dialog, sticky bar). */
.pc-card {
  background: var(--pv2-surface);
  border: 1px solid var(--pv2-border);
  border-radius: 10px;
  padding: var(--sp-5, 24px);
  margin-bottom: var(--sp-6, 32px);
}
@media (max-width: 899px) { .pc-card { padding: var(--sp-4, 16px); } }
.pc-card-head {
  display: flex; align-items: baseline; gap: var(--sp-3, 12px);
  margin-bottom: var(--sp-3, 12px);
}
.pc-card-title { font-size: var(--t-lead); font-weight: 600; color: var(--pv2-dark); }
.pc-card-action { margin-left: auto; }
/* The one-level-deep escape hatch: a hairline-separated section inside a card. */
.pc-section { border-top: 1px solid var(--pv2-border-lt); padding-top: var(--sp-4, 16px); margin-top: var(--sp-5, 24px); }
.pc-section-title { font-size: var(--t-lead); font-weight: 600; margin-bottom: var(--sp-2, 8px); }

/* ── 3. TABLE ───────────────────────────────────────────────────────────────────────────────────
   Repeating records with three or more comparable columns where ALIGNMENT CARRIES MEANING. Not for
   the attributes of one thing - that is field rows plus a sentence. A flat many-column table mounted
   inside a per-item explanation is a table used as a paragraph, which is the misplacement Tim named. */
.pc-table-wrap { overflow-x: auto; }
.pc-table { width: 100%; border-collapse: collapse; font-size: var(--t-small); font-variant-numeric: tabular-nums; }
.pc-table th {
  text-align: left; font-size: var(--t-micro); font-weight: 600; letter-spacing: 0.4px;
  text-transform: uppercase; color: var(--pv2-ink-3); background: var(--pv2-bg);
  padding: var(--sp-2, 8px) var(--sp-3, 12px); white-space: nowrap;
  border-bottom: 1px solid var(--pv2-border);
}
.pc-table td { padding: var(--sp-3, 12px); border-bottom: 1px solid var(--pv2-border-lt); vertical-align: middle; }
.pc-table tr:last-child td { border-bottom: 0; }
.pc-table td.pc-num, .pc-table th.pc-num { text-align: right; }

/* ── 4. PICKER ──────────────────────────────────────────────────────────────────────────────────
   Three shapes and no others, chosen by option count and by whether an option needs explaining.
   No custom dropdown widgets, no comboboxes, no hover-revealed option text. */
/* (a) Segmented control - two or three mutually exclusive VIEWS. */
.pc-seg { display: flex; background: var(--pv2-bg); border-radius: 8px; padding: 3px; gap: 2px; }
.pc-seg-btn {
  flex: 1; min-height: 44px; border: 0; background: none; border-radius: 6px;
  font-size: var(--t-body); color: var(--pv2-ink-3); cursor: pointer;
}
/* Ink on a pale accent fill, never accent text on an accent fill: the second reads as a disabled
   button on every low-brightness screen. */
.pc-seg-btn--on { background: #e8eeee; color: var(--pv2-dark); font-weight: 600; box-shadow: inset 0 -2px 0 var(--portal-accent); }
/* (b) Native select - four or more options where the label alone identifies the option. Native so a
   phone gives its own wheel. */
.pc-select {
  min-height: 48px; padding: 0 var(--sp-3, 12px);
  border: 1px solid var(--pv2-border); border-radius: 8px;
  background: var(--pv2-surface); color: var(--pv2-dark); font-size: var(--t-body);
  max-width: 100%;
}
@media (max-width: 899px) { .pc-select { width: 100%; } }
/* (c) Option list - radio rows, when an option needs disambiguating text. The text lives INSIDE the
   option, never in a tooltip. */
.pc-option { display: flex; gap: var(--sp-3, 12px); align-items: flex-start; padding: var(--sp-3, 12px); border: 1px solid var(--pv2-border); border-radius: 8px; margin-bottom: var(--sp-2, 8px); cursor: pointer; }
.pc-option--on { border-color: var(--portal-accent); background: #e8eeee; }
.pc-option-why { display: block; font-size: var(--t-small); color: var(--pv2-ink-3); margin-top: 2px; }

/* ── 5. CHIP ────────────────────────────────────────────────────────────────────────────────────
   Two kinds, never mixed in one row, MAXIMUM ONE STATE CHIP PER ROW. If a row earns two, the second
   becomes a meta line. A chip never repeats a value the row already shows, and a chip is never
   clickable - if it is clickable it is a button. */
.pc-chip {
  display: inline-block; font-size: var(--t-micro); font-weight: 600; line-height: 1;
  padding: 4px 8px; border-radius: 6px; vertical-align: middle; white-space: nowrap;
}
/* Data chip: a unit, a count, a reference. Neutral, no border, carries no state. */
.pc-chip--data { background: var(--portal-neutral-bg); color: var(--pv2-ink-3); }
/* State chips. Each is a text colour, a pale fill and a hairline, and each is ALWAYS accompanied by
   its word - the hue never carries the meaning alone. */
.pc-chip--problem { color: var(--portal-error);  background: var(--portal-error-bg);  border: 1px solid var(--portal-error-bd); }
.pc-chip--advise  { color: var(--portal-amber);  background: var(--portal-amber-bg);  border: 1px solid var(--portal-amber-bd); }
.pc-chip--draft   { color: var(--portal-violet); background: var(--portal-violet-bg); border: 1px solid var(--portal-violet-bd); }
.pc-chip--shared  { color: var(--portal-blue);   background: var(--portal-blue-bg);   border: 1px solid var(--portal-blue-bd); }
.pc-chip--ok      { color: var(--portal-green);  background: var(--portal-green-bg);  border: 1px solid var(--portal-green-bd); }
/* POA is NEUTRAL in this authoring surface and italic red inside a rendered customer quote. That is a
   deliberate boundary between two rendering systems; the reasoning lives at the chip builder in
   editor-styles.js, where a future one-source-of-truth pass would be tempted to merge them. */
.pc-chip--poa     { color: var(--pv2-ink-3); background: var(--portal-neutral-bg); border: 1px solid var(--pv2-border); }

/* ── 6. DISCLOSURE ──────────────────────────────────────────────────────────────────────────────
   The default home for anything not needed to make the decision in front of you. The closed label
   must SUMMARISE - "6 logged jobs", "How this is priced" - never "Details". Never nested inside
   another disclosure, and never containing the primary action. */
.pc-disclosure { border-top: 1px solid var(--pv2-border-lt); }
.pc-disclosure > summary {
  display: flex; align-items: center; gap: var(--sp-2, 8px);
  min-height: 48px; padding: var(--sp-2, 8px) 0;
  font-size: var(--t-body); font-weight: 500; color: var(--pv2-dark);
  cursor: pointer; list-style: none;
}
.pc-disclosure > summary::-webkit-details-marker { display: none; }
.pc-disclosure > summary::after { content: "\203A"; margin-left: auto; color: var(--pv2-ink-3); transform: rotate(90deg); transition: transform 120ms ease; }
.pc-disclosure[open] > summary::after { transform: rotate(-90deg); }
.pc-disclosure-body { padding: 0 0 var(--sp-4, 16px); }
@media (min-width: 900px) { .pc-disclosure-body { padding-left: var(--sp-4, 16px); } }
/* Help sits behind a "?" on the block heading it explains, never as prose on screen. */
.pc-help-btn {
  width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--pv2-border);
  background: var(--pv2-surface); color: var(--pv2-ink-3); font-size: var(--t-micro); cursor: pointer;
}
.pc-help-body { font-size: var(--t-small); color: var(--pv2-ink-3); max-width: var(--measure-help); }

/* ── 7. DIALOG ──────────────────────────────────────────────────────────────────────────────────
   A decision that must be made before continuing AND that carries a consequence. Never for
   information only, never for a reversible action, never confirming a staged edit (the footer is
   already that confirmation), never two deep. */
.pc-dialog-scrim { position: fixed; inset: 0; background: rgba(26,26,26,.35); display: flex; align-items: center; justify-content: center; z-index: 100; }
.pc-dialog {
  background: var(--pv2-surface); border-radius: 14px; max-width: 520px; width: calc(100% - 32px);
  padding: var(--sp-5, 24px); box-shadow: 0 12px 40px rgba(26,26,26,.18);
}
.pc-dialog-title { font-size: var(--t-title); font-weight: 600; margin-bottom: var(--sp-3, 12px); }
/* The body names the concrete consequence with real names and counts, never a category. */
.pc-dialog-body { font-size: var(--t-body); color: var(--pv2-mid); max-width: var(--measure-prose); }
.pc-dialog-actions { display: flex; gap: var(--sp-2, 8px); justify-content: flex-end; margin-top: var(--sp-5, 24px); }
@media (max-width: 899px) {
  /* Bottom sheet, radius on the top corners only, buttons stacked and full width above the safe area. */
  .pc-dialog-scrim { align-items: flex-end; }
  .pc-dialog { width: 100%; border-radius: 14px 14px 0 0; padding-bottom: calc(var(--sp-5, 24px) + env(safe-area-inset-bottom)); }
  .pc-dialog-actions { flex-direction: column-reverse; }
  .pc-dialog-actions > * { width: 100%; min-height: 48px; }
}

/* ── 8. STICKY FOOTER (the save bar) ────────────────────────────────────────────────────────────
   The visible instrument of the staged-save model and NOTHING else. It never carries Publish - that
   belongs to the draft bar at the top, and the separation is deliberate, because mixing "save this
   edit" with "push everything live" invites the wrong click. It never carries navigation, and it
   never persists when clean. One per screen. */
.pc-savebar {
  position: sticky; bottom: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--sp-3, 12px);
  min-height: 68px; padding: var(--sp-3, 12px) var(--sp-4, 16px);
  background: var(--pv2-surface); border-top: 1px solid var(--pv2-border);
  box-shadow: 0 -2px 16px rgba(26,26,26,.10);
}
/* Left: what is pending, in plain words. After a partial failure: "2 saved, 1 failed". */
.pc-savebar-status { font-size: var(--t-body); }
.pc-savebar-actions { margin-left: auto; display: flex; gap: var(--sp-2, 8px); }
@media (max-width: 899px) {
  .pc-savebar { min-height: 64px; padding-bottom: calc(var(--sp-3, 12px) + env(safe-area-inset-bottom)); }
}
/* The scroll container reserves room so the last row is never covered by the bar. */
.pc-scroll-pad { padding-bottom: 64px; }

/* ── 9. EMPTY STATE ─────────────────────────────────────────────────────────────────────────────
   Every empty state names what is missing AND carries the control that fills it. An empty state with
   no control is a dead end, which is how the portal previously reported "not set" on a row a person
   could not then set. */
.pc-empty {
  padding: var(--sp-5, 24px); border: 1px dashed var(--pv2-border); border-radius: 10px;
  background: var(--pv2-bg); text-align: center;
}
.pc-empty-text { font-size: var(--t-body); color: var(--pv2-ink-3); margin-bottom: var(--sp-3, 12px); max-width: var(--measure-help); margin-left: auto; margin-right: auto; }

/* ── 10. BUTTON ─────────────────────────────────────────────────────────────────────────────────
   Three ranks and no more. A structural action is ALWAYS a button with a verb, never a bare input -
   that is how the eye tells an immediate write from a staged edit. */
.pc-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2, 8px);
  min-height: 44px; padding: 0 var(--sp-4, 16px);
  border-radius: 8px; border: 1px solid transparent;
  font-size: var(--t-body); font-weight: 600; cursor: pointer;
}
.pc-btn--primary { background: var(--portal-accent); color: #fff; }
.pc-btn--primary:hover { background: var(--portal-accent-dk); }
.pc-btn--ghost { background: var(--pv2-surface); color: var(--portal-accent); border-color: var(--pv2-border); }
.pc-btn--ghost:hover { background: var(--pv2-bg); }
.pc-btn--quiet { background: none; color: var(--pv2-ink-3); }
.pc-btn--danger { background: var(--portal-error); color: #fff; }
.pc-btn[disabled] { opacity: .5; cursor: not-allowed; }

/* ── The accessibility floor, as numbers rather than intentions ─────────────────────────────────
   44px on every interactive target in both axes, a visible focus ring that is not the browser's
   default outline-on-a-rounded-corner, and no affordance that only exists on hover (the drag handle
   that appears on hover today does not exist at all on a touch screen). */
.pc-hit { min-width: 44px; min-height: 44px; }
.pc-row :focus-visible, .pc-btn:focus-visible, .pc-select:focus-visible,
.pc-seg-btn:focus-visible, .pc-option:focus-visible, .pc-disclosure > summary:focus-visible {
  outline: 2px solid var(--portal-accent);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .pc-disclosure > summary::after { transition: none; }
}

/* ── 11. STRUCTURE MENU (R3) ────────────────────────────────────────────────────────────────────
   An element's structural verbs - Rename, Move, Remove - in ONE place, in the block-1 heading.
   Before R3 they sat in three different places on every screen: the heading, mid-pane, and the page
   foot. Closed by default, because none of them is the thing you came to the page to do.

   This overrides the delete-coverage session's deliberate bottom-of-page placement for Remove. That
   distance was a second, weaker layer over the real protection, which is the REFUSALS: a group with
   contents already refuses and names them, a referenced item already refuses and names what uses it,
   and both re-check at click time. Those are untouched - the button moved, the guards did not. */
.cte2-structmenu { position: relative; margin-left: auto; flex: none; }
/* IT SIZES TO ITS CONTENT (2026-08-13, Tim: the label and caret overflow the border).

   THE CAUSE IS A LEFTOVER FROM WHEN IT WAS AN ICON. This was `width: 36px; height: 36px` with
   `justify-content: center`, which is the correct box for the single U+22EF glyph it used to hold.
   Brief Task 11 replaced that glyph with the WORD "Edit" plus a caret and did not touch the CSS, so
   a two-element label was being centred inside a 36px square it could not fit: "Edit" pressed
   against the left border and the caret sat on or past the right one.

   A WIDTH IS NOT THE FIX, and that is the whole point of doing it here. The menu will not always
   say "Edit", and any fixed width that fits today is the same bug again the first time the label
   changes. So the fixed box goes entirely and padding does the work: the button is as wide as what
   is in it, plus equal padding on both sides, at every pane width.

   min-height keeps the 36px hit target the icon box gave it, so nothing about the row's rhythm or
   the touch target changes - only the width, and only when the content needs it.

   The caret's clearance is a `gap` here rather than the caret's own margin-left, because a gap is a
   property of the ROW and cannot be inherited by, or lost from, one of its children. */
.cte2-structmenu > summary {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 36px; padding: 0 12px; border-radius: 8px;
  border: 1px solid var(--pv2-border); background: var(--pv2-surface);
  color: var(--pv2-ink-3); font-size: var(--t-lead); line-height: 1;
  white-space: nowrap;
  cursor: pointer; list-style: none; user-select: none;
}
.cte2-structmenu > summary::-webkit-details-marker { display: none; }
.cte2-structmenu > summary:hover { background: var(--pv2-bg); color: var(--pv2-dark); }
.cte2-structmenu > summary:focus-visible { outline: 2px solid var(--portal-accent); outline-offset: 2px; }
.cte2-structmenu[open] > summary { background: var(--pv2-bg); color: var(--pv2-dark); }
.cte2-structmenu-body {
  position: absolute; right: 0; top: 42px; z-index: 30; min-width: 320px; max-width: 420px;
  background: var(--pv2-surface); border: 1px solid var(--pv2-border); border-radius: 10px;
  padding: var(--sp-4, 16px); box-shadow: 0 12px 40px rgba(26,26,26,.18);
}
.cte2-structmenu-body > .cte2-block:last-child { margin-bottom: 0; }
/* ADD SECTION of the toolbar menu (spec: "Add buttons live in the item toolbar, not in the pane
   body"). Sits below Name and Belongs to behind a rule, because those change what the item IS while
   these change what it HAS. Full-width rows rather than a button row: they read as menu items, which
   is what they are now, and it stops a long label like "Add conditional quantity" wrapping oddly. */
.cte2-structmenu-sep {
  border-top: 1px solid var(--pv2-border); margin: var(--sp-3, 12px) 0 var(--sp-2, 8px);
}
.cte2-structmenu-adds { display: flex; flex-direction: column; align-items: stretch; gap: 2px; }
.cte2-structmenu-add {
  display: block; width: 100%; text-align: left;
  background: none; border: none; border-radius: 6px;
  padding: 7px 8px; cursor: pointer;
  font-size: var(--t-body); font-family: inherit; color: var(--pv2-dark);
}
.cte2-structmenu-add:hover { background: var(--pv2-bg); }
/* THE ADD SECTION LEADS THE MENU (2026-09-30, THE QUEUE): one quiet word over the boxes a pane can
   gain, then the rule, then the verbs that were already there. */
.cte2-structmenu-addhead {
  margin: 0 0 2px; padding: 0 8px;
  font-size: var(--t-micro); font-weight: 600; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--pv2-ink-3);
}
/* AN OPTIONAL BOX CARRIES AN x IN THE TOP-RIGHT OF ITS TITLE ROW (2026-09-30). It clears the setting
   and removes the box. Quiet at rest, the pane's own destructive colour on hover, the same pair the
   Edit menu's Remove uses, so no new colour is introduced. The title makes room for it. */
.cte2-md-detail .cte2-grid-row > .cte2-grid-cell--box { position: relative; }
.cte2-grid-cell--box > .pc-card-head,
.cte2-grid-cell--box .cte2-block-title { padding-right: 32px; }
.cte2-box-x {
  position: absolute; top: 10px; right: 10px;
  width: 28px; height: 28px; padding: 0; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: 6px; background: transparent;
  color: var(--pv2-ink-3); font-size: 18px; font-family: inherit; cursor: pointer;
}
.cte2-box-x:hover { color: var(--portal-error, #c0392b); background: var(--portal-error-bg, #f7e6e3);
  border-color: var(--portal-error-bd, #e0b3ab); }
.cte2-box-x:focus-visible { outline: 2px solid var(--portal-accent); outline-offset: 1px; }
.cte2-box-blocker { color: var(--portal-error, #c0392b); margin-top: var(--sp-2, 8px); }
/* A BOX WHOSE CONTENT IS ONE FACT ROW OR THE HELP TEXT has no title row to pad (2026-09-30, phase
   B), so its content makes room for the x instead. */
.cte2-md-detail .cte2-grid-row > .cte2-grid-cell--box > .cte2-box-body,
.cte2-md-detail .cte2-grid-row > .cte2-grid-cell--box > [data-block-name] { padding-right: 32px; }
/* A box's "Unsaved" note wraps inside the box rather than running under its x. */
.cte2-grid-cell--box > .cte2-box-body .cte2-unsaved-badge { white-space: normal; }
/* An open editor inside a box (the help text's toolbar) must not paint over the x. */
.cte2-grid-cell--box > .cte2-box-x, .cte2-optbox > .cte2-box-x { z-index: 2; }
/* AN ANSWER'S BOXES (2026-09-30, phase B): inside an open option card, each optional part is a small
   framed box with its x, and what is not drawn is offered in one quiet Add row. The x and its colours
   are the pane boxes' own. */
.cte2-optbox {
  position: relative; border: 1px solid var(--pv2-border); border-radius: 6px;
  padding: 8px 40px 8px 10px;
}
.cte2-optbox > .cte2-box-x { top: 4px; right: 4px; }
.cte2-optbox-title {
  margin: 0 0 4px; font-size: var(--t-micro); font-weight: 600; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--pv2-ink-3);
}
.cte2-optbox-adds { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.cte2-optbox-addhead {
  font-size: var(--t-micro); font-weight: 600; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--pv2-ink-3); margin-right: 2px;
}
.cte2-structmenu-add:focus-visible { outline: 2px solid var(--portal-accent); outline-offset: -2px; }
@media (max-width: 899px) {
  /* No room to float on a phone: the menu opens in flow rather than over the content it explains. */
  .cte2-structmenu { position: static; }
  .cte2-structmenu-body { position: static; min-width: 0; max-width: none; margin-top: var(--sp-2, 8px); box-shadow: none; }
  /* -- AND IN FLOW IT NEEDS ITS OWN LINE (2026-09-06, editor review rule 6) -----------------
     Tim: "When the screen is narrower, our 2 column grid should just become one column." The grid
     did. This did not, and it is the same fault one level down: dropping `position: absolute` put
     the menu body into the header's FLEX ROW, where min-width 0 let it shrink to 81px at 375px
     while the "Remove this field" button inside it stayed 162px. Measured, 79px past the pane's
     right edge at 375 AND at 768, which is the tell that it was the container and not the width.
     The header wraps and the menu takes the whole line under it, which is what "in flow" was
     always meant to mean. */
  .cte2-detail-head { flex-wrap: wrap; }
  /* `1 1 100%` and not `1 0 100%`: the header is a flex row with a gap, so a basis of the full
     container width plus that gap put the menu 5px past the pane. Allowed to shrink, it lands on
     the line exactly. Measured both ways. */
  .cte2-structmenu { flex: 1 1 100%; min-width: 0; }
  .cte2-structmenu-body { width: 100%; box-sizing: border-box; }
  /* AND THE LABELS INSIDE IT WRAP. "Remove from library" is 15px wider than a 375px pane's block
     at this type size, and a button that cannot wrap cannot shrink. `white-space: nowrap` on the
     summary is right at every width where the pane has room and is the last 2px here. */
  .cte2-structmenu > summary,
  .cte2-structmenu-body button { max-width: 100%; white-space: normal; }
}
/* A long unbroken name in the Used-by note had nowhere to break and ran 16px past the pane at
   375px. Nothing else on these panes needs this: it is the one place a stored KEY is printed. */
.cte2-usedby-when { overflow-wrap: anywhere; }

/* ── 12. BLOCK GROUP (R3) ───────────────────────────────────────────────────────────────────────
   The seven blocks are pc-cards, so they need no chrome of their own. What they DO need is to stop
   the blocks they contain from double-carding: a .cte2-block inside a block group is a section of
   that block, not a card in its own right. This is the "a card inside a card means the inner thing
   is a section" rule from the component vocabulary, applied to the re-homed markup. */
.cte2-blockgrp > .cte2-block { border: 0; padding: 0; margin-bottom: var(--sp-4, 16px); }
.cte2-blockgrp > .cte2-block:last-child { margin-bottom: 0; }
.cte2-blockgrp .cte2-block-title {
  font-size: var(--t-body); font-weight: 600; color: var(--pv2-dark); margin: 0 0 var(--sp-2, 8px);
}

/* ── 13. NAVIGATOR FOOT (R4) ────────────────────────────────────────────────────────────────────
   Calculations and Problems are WHOLE-PRODUCT destinations, not tree nodes. They always sat at the
   foot of the navigator, but flush against the tree, so they read as two more rows of it. A rule and
   a quiet heading is the whole fix: the navigator still lists everything you can look at, and this
   group says which of those things is about the product rather than about one item in it. */
.cte2-nav-foot {
  margin-top: var(--sp-4, 16px);
  padding-top: var(--sp-2, 8px);
  border-top: 1px solid var(--pv2-border);
}
.cte2-nav-foot-title {
  font-size: var(--t-micro); font-weight: 600; color: var(--pv2-ink-3);
  margin: 0 0 var(--sp-1, 4px); padding: 0 var(--sp-3, 12px);
}

/* ── THE LINE-ITEM FORMULA ROW IS GONE (2026-08-13, round 3 Task H) ──────────────────────────────
   Its input, status, save button, note and job-preview rules went with it. Writing a formula belongs
   in Variables, not on a line item; the reasoning is at the deletion in editor-detail.js.
   .cte2-formula-summary SURVIVES because the POA "use a condition instead" <details> borrows it
   (editor-detail.js, _poaConditionBlock). It is not a formula class any more, only a name. */
.cte2-formula-summary {
  cursor: pointer; font-size: var(--t-small); color: var(--portal-amber);
  list-style: none; display: inline-block; padding: 2px 0;
}
.cte2-formula-summary::-webkit-details-marker { display: none; }
.cte2-formula-summary::before { content: '+ '; }

.cte2-fnpick { position: relative; display: inline-block; }
.cte2-fnpick-open {
  background: none; border: 1px dashed #c9b79a; border-radius: 6px;
  padding: 5px 10px; cursor: pointer; font-size: var(--t-small); color: var(--portal-amber);
  white-space: nowrap;
}
.cte2-fnpick-panel {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0;
  width: min(420px, 90vw); background: #fff;
  border: 1px solid #ecd9bf; border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.12);
  padding: 8px;
}
.cte2-fnpick-search {
  width: 100%; padding: 5px 8px; border: 1px solid #c9b79a; border-radius: 6px;
  font-size: var(--t-small); margin-bottom: 6px;
}
/* The list scrolls, the search box does not: a client filtering a long list must never lose the
   box they are typing into. */
.cte2-fnpick-scroll { max-height: 320px; overflow-y: auto; }
.cte2-fnpick-grouphead {
  font-size: var(--t-small); color: var(--pv2-mid); text-transform: uppercase;
  letter-spacing: .04em; margin: 6px 0 3px;
}
.cte2-fnpick-list { list-style: none; margin: 0; padding: 0; }
.cte2-fnpick-insert {
  display: block; width: 100%; text-align: left; background: none; border: none;
  border-radius: 6px; padding: 5px 6px; cursor: pointer;
}
.cte2-fnpick-insert:hover, .cte2-fnpick-insert:focus-visible { background: #f6efe4; }
.cte2-fnpick-label { display: block; font-size: var(--t-small); color: var(--pv2-dark); }
.cte2-fnpick-sig {
  display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--t-small); color: var(--portal-amber); margin-top: 1px;
}
.cte2-fnpick-desc { display: block; font-size: var(--t-small); color: var(--pv2-mid); margin-top: 2px; }
.cte2-fnpick-note { display: block; font-size: var(--t-small); color: var(--pv2-mid); font-style: italic; }

/* ── THE DIAGRAM SELECTOR (2026-08-20, queue Task H) ─────────────────────────────────────────────
   One row per shape, one line per dimension it declares. The binding reads as a sentence rather
   than a form, because "drawn from the answer to width" is what a client needs to check, and a bare
   dropdown does not say it. */
.cte2-shapes { margin-top: 12px; }
.cte2-shape-row {
  border: 1px solid #ecd9bf; border-radius: 8px; padding: 8px 10px;
  margin-bottom: 8px; background: #fbf6ef;
}
.cte2-shape-head { display: flex; align-items: center; gap: 8px; }
.cte2-shape-remove {
  margin-left: auto; background: none; border: 1px solid #cda; border-radius: 5px;
  padding: 2px 8px; cursor: pointer; color: var(--portal-amber); font-size: var(--t-small);
}
/* ── REMOVING A CONSTANT (2026-08-23, THE QUEUE) ─────────────────────────────────────────────
   The same control as the shape's Remove above, because it is the same control: a word rather than
   an x, in the row's own action column. A new class faces off against the one that already exists,
   and this one differs only in not needing the `margin-left: auto` that pushes the shape's to the
   end of a flex header - a table cell is already where it belongs. */
.cte2-const-remove {
  background: none; border: 1px solid #cda; border-radius: 5px;
  padding: 2px 8px; cursor: pointer; color: var(--portal-amber); font-size: var(--t-small);
}
.cte2-shape-bind {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 6px; font-size: var(--t-small); color: var(--pv2-mid);
}
.cte2-shape-dim { min-width: 132px; color: var(--pv2-dark); display: flex; align-items: baseline; gap: 7px; }
/* ── THE VIEW TICKS (2026-08-23, THE QUEUE "The Cube") ───────────────────────────────────────
   SHOW [x] Front [x] Side [ ] Top, in one line, in the catalogue's own view order. They sit in the
   same slot a dimension's value control does, so the row reads like every other row on the card
   rather than like a new kind of block. Each label is its own click target, tick included, because
   a two-word label with a separate 13px box is a control people miss. */
.cte2-shape-views { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.cte2-shape-view {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  color: var(--pv2-dark); white-space: nowrap;
}
.cte2-shape-view input { margin: 0; cursor: pointer; }
/* ── THE LETTER LEADS (2026-08-17, queue Task H1) ────────────────────────────────────────────
   The spec: a shape declares its dimensions as A, B, C, D against a diagram, because manufacturers do
   not agree on the words. So the letter is the thing the eye lands on and the name is the gloss beside
   it - which is the reverse of how this row read before. Monospace and fixed width so the letters form
   a column that lines up with a diagram, rather than shifting with the length of each name. */
.cte2-shape-ltr {
  flex: 0 0 auto; min-width: 1.1em; text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700; font-size: var(--t-small); color: var(--portal-amber);
  border: 1px solid var(--portal-amber-bd); border-radius: 4px; padding: 0 4px;
  background: var(--portal-amber-bg);
}
/* An unset dimension is a WARNING, not a neutral blank: the shape silently does not draw, which is
   indistinguishable from a working drawing in a screenshot. */
.cte2-shape-unset { color: #b06; font-style: italic; }
/* ── ONE STATUS LINE, AT THE FOOT (2026-08-17, queue Task H1) ────────────────────────────────
   The card carried up to four stacked italic muted lines and the one that mattered - whether this
   shape draws at all - was third of four. It is now one line, at the bottom, where a person looks for
   an outcome, and it is NOT italic: a verdict is not an aside. */
.cte2-shape-status {
  margin: 8px 0 0; padding-top: 6px; border-top: 1px solid #e8dcc8;
  font-size: var(--t-small); color: var(--pv2-mid);
}
.cte2-shape-status--unset { color: #b06; }
.cte2-shape-add { margin-top: 10px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* The heading weight the POA condition <details> reads with. It was written for the line-item
   formula row, which was deleted on 2026-08-13 (round 3 Task H); only the summary is still drawn. */
.cte2-formula-summary {
  font-size: var(--t-micro); font-weight: 700; color: var(--portal-amber);
  text-transform: uppercase; letter-spacing: .04em; margin-bottom: 4px;
}

/* The function library on Variables > Calculations (2026-08-20, Task L): the screen for mathjs, the
   six geometry functions and the sandbox, none of which had one. */
.cte2-fnlib { margin-top: 14px; }
.cte2-fnlib-group {
  text-align: left; padding-top: 10px; font-size: var(--t-micro);
  text-transform: uppercase; letter-spacing: .04em; color: var(--pv2-mid);
}
/* ── THE PICKER PRINTED TEXT OVER TEXT (2026-09-09, THE QUEUE) ────────────────────────────────
   Tim: "Also it is just a mess as you can see with text over text."

   TWO nowraps in a three column table with no widths. A signature like
   `area_box_5face_openfront_mm2(width, height, depth)` cannot break, so the cell grew past its
   share and ran straight into the description beside it - and this is the first thing a client
   sees when choosing a function.

   THE FIX IS TO LET THE LONG COLUMN BREAK AND TO GIVE THE THREE A SHARE. `table-layout: fixed`
   makes the widths the ones stated here rather than whatever the longest word demands, which is
   what stops one long name deciding the whole table.

   THE LABEL KEEPS ITS NOWRAP: it is short, it is the thing a client scans down, and wrapping "Area
   of a flat rectangle" into two lines is what made the rows hard to count in the first place. */
.cte2-fnlib table { table-layout: fixed; width: 100%; }
.cte2-fnlib-label { white-space: nowrap; color: var(--pv2-dark); width: 26%; }
.cte2-fnlib-sig {
  color: var(--portal-amber);
  /* BREAK INSIDE THE NAME IF IT HAS TO. `anywhere` rather than `break-all` so it prefers the commas
     and brackets it already has, and only splits a bare identifier as a last resort. */
  overflow-wrap: anywhere;
  white-space: normal;
}
.cte2-fnlib td:nth-child(2) { width: 34%; }
.cte2-fnlib-desc { color: var(--pv2-mid); overflow-wrap: anywhere; }
