
:root{
  color-scheme:dark;
  /* ============================================================================
     AXD Intelligence — BASE tokens (the design language). See
     docs/design/axdi-reskin/. Monochrome; the only chromatics are the four status
     colors. Legacy SOP-Builder token names below are ALIASED onto these so the
     welded stylesheet keeps working while the whole shell turns monochrome.
     ========================================================================== */
  /* Figtree carries Latin, Cairo carries Arabic — one stack, no lang attribute (v1.5 §3). */
  --font-sans:"Figtree","Cairo",-apple-system,"SF Pro Text","Inter",sans-serif;
  --font-mono:"JetBrains Mono",ui-monospace,"SF Mono",monospace;
  --text-display:28px;--text-meta:12px;--text-nav:14px;   --weight-display:300;  --tracking-display:-0.01em;
  --text-section:16px;   --weight-section:500;
  --text-body-axd:14px;  --weight-body:400;     --leading-body:1.55;
  --text-eyebrow:11px;   --weight-eyebrow:500;  --tracking-eyebrow:0.12em;
  /* TABLE HEADER 11 -> 12 (owner call 2026-08-14). A DELIBERATE DEPARTURE, recorded so it does
     not later read as drift: the rulebook's Table-header rung is 11px/500/0.12em UPPERCASE, and
     11 is legitimate there because tracked caps survive below the 12px lowercase floor. The owner
     read them as too small on the workflow screen and chose 12. It stays on the type scale
     (28-16-14-12-11), stays uppercase, and keeps the 0.12em chrome-label tracking — only the rung
     moves. Changed at the TOKEN so all three tables in the app move together; scoping it to the
     workflow table would have made that one table the odd one out. */
  --text-th:12px;      --tracking-th:0.12em;
  --text-button:14px;    --weight-button:500;   --tracking-button:0.01em;

  --radius-scale:1;
  --radius-chip:calc(8px  * var(--radius-scale));
  --radius-control:calc(10px * var(--radius-scale));
  --radius-card:calc(16px * var(--radius-scale));
  --radius-modal:calc(20px * var(--radius-scale));

  /* color · dark (identity default) — true-black canvas per AXD-I rulebook (2026-07-25).
     Ladder RE-DERIVED for rulebook v1.7: containers no longer carry a hairline (§2 · the line
     language), so TONE does the separating and the step had to become a contract rather than an
     accident. canvas -> surface is now ΔL* 5.46 (was 2.74 at #0A0A0A — below both references:
     Claude ships 5.5, ChatGPT 12.7, which is why borderless dissolved on the old ladder). */
  --color-canvas:#000000;  --color-surface:#121212;  --color-surface2:#171717;
  --color-control:#1C1C1C; --color-hover:#212121;
  /* Overlay tier (rulebook): floating surfaces (modals, popovers, dropdowns, sheets) lift to
     #212121 + a linestrong hairline — shadows are invisible on true black, so depth is tone. */
  --color-overlay:#212121;  --color-overlay-hover:#2A2A2A;
  --color-line:rgba(255,255,255,.08);  --color-linestrong:rgba(255,255,255,.14);
  --color-ink:#F2F2F2;  --color-muted:#A3A3A3;
  /* v1.7: lifted from #8A8A8A so tertiary text clears 4.5:1 on EVERY rung of the raised
     ladder — it failed at 4.16 on overlay-hover before, and measures 4.73 now. */
  --color-faint:#949494;
  --color-primary:#FFFFFF;  --color-onprimary:#000000;
  --color-scrim:rgba(0,0,0,.6);  --color-focus:#F2F2F2;
  --color-success:#7FA287;  --color-warning:#AE9674;  --color-danger:#BC8F8B;  --color-info:#839CBC;
  --status-fill-alpha:14%;  --status-border-alpha:35%;
  --shadow-overlay:none;

  /* ---- legacy aliases -> AXD-I (this is what re-skins the whole app) ---- */
  --primary:var(--color-primary);
  --secondary:var(--color-linestrong);
  --tertiary:var(--color-overlay);
  --neutral:var(--color-ink);
  --surface-light:var(--color-surface);
  --on-surface:var(--color-ink);
  --background:var(--color-canvas);
  --text:var(--color-ink);
  --border:var(--color-line);
  --error:var(--color-danger);

  --bg:var(--color-canvas);
  --paper:var(--color-surface);
  --paper2:var(--color-surface2);
  --paper3:var(--color-control);
  --surface-dark:var(--color-control);
  --text-strong:var(--color-ink);
  /* A TOKEN NAMED "SOFT" THAT WAS THE BRIGHTEST VALUE IN THE PALETTE (g445).
     This alias block maps the pre-reskin names onto the AXD-I ladder, and the old scheme had four
     text rungs (text / text-soft / muted / muted2) where the book has three (ink / muted / faint).
     Three of the four landed correctly. `--text-soft` collapsed UPWARD into ink, so 116 rules that
     explicitly ask for softer-than-primary text have been rendering at maximum brightness. That is
     the cause behind two faults already fixed locally: the Contents list had no tonal hierarchy at
     rest (g437) and the tour card drew its counter, title and body at one identical tone (g440).
     Both were patched by naming --color-muted directly; this fixes the source.
     Landing on --color-muted makes it a synonym for `--muted` below, which is correct: collapsing
     four rungs into three means two must share, and the middle is the honest place to do it. */
  --text-soft:var(--color-muted);
  --muted:var(--color-muted);
  --muted2:var(--color-faint);
  --line:var(--color-line);
  --line2:var(--color-linestrong);
  --nav:var(--color-canvas);
  --toolbar:var(--color-canvas);
  /* accent ramp is DELETED as a hue — active states become tone/ink, not navy */
  --accent:var(--color-surface2);
  --accent-strong:var(--color-ink);
  /* THE LINE THAT SAYS "THIS ONE" (g438). `--accent-strong` was the navy accent before the
     reskin; it now resolves to --color-ink, and it is used as a BORDER COLOUR in 18 places. A
     1px line is a hairline by width and a shout by tone: pure white on a near-black ground is
     ΔL* ~88 against the surface it sits on, which is why the owner reads the editing chrome as
     "a big, thick outline" when it is in fact 1px. Line weight was never the problem.
     v1.7 says line means STATE, and a state line belongs on the line tier, not the text tier.
     This is that tier, named for the job so the remaining --accent-strong borders can be moved
     onto it one surface at a time instead of in one unverifiable sweep. */
  --line-state:var(--color-linestrong);
  --accent-muted:var(--color-surface2);
  --legend-icon-default:var(--color-muted);
  --hover:var(--color-hover);
  --fill:rgba(255,255,255,.035);
  --shadow:none;

  --radius-none:0px;
  /* sm/md/lg are a legacy SIZE scale; chip/control/card/modal is the AXD-I ROLE scale.
     They do not line up 1:1, so each alias maps to the role it is actually USED on
     (verified against all 130 var(--radius-sm|md|lg) rules, not against the names):
       sm -> buttons, inputs, selects, .nav-button, .toc-toggle, .cloud-btn, note boxes
       md -> .card, .role-panel, .table-wrap, the inline editors, .tmpl-card, .cns-card
       lg -> .cloud-card, .cloud-modal, .cns-win, .tmpl-modal
     Mapping sm->chip would drop 500 controls to the chip tier; it is the wrong rung. */
  --radius-sm:var(--radius-control); /* 10 — controls: buttons, inputs, small note boxes */
  --radius-md:var(--radius-card);    /* 16 — cards, panels, inline editors, popovers */
  --radius-lg:var(--radius-modal);   /* 20 — modals, windows */
  --radius-xl:var(--radius-modal);   /* 20 — modals */
  --radius-full:9999px;              /* dots + the approved diagram stadium terminals */

  --toc-open:240px;
  --toc-closed:64px;
  --bar-h:56px; /* full-width top app-bar height; JS re-measures into this var so columns offset correctly when it wraps */

  --font-body:var(--font-sans);

  --fs-label-sm:11px;
  --fs-meta:12px;
  --fs-body:14px;
  --fs-body-lg:16px;
  --fs-title:var(--text-display);
  --fs-headline:28px;

  --fw-body:400;
  --fw-control:500;
  --fw-title:300; /* AXD-I display weight */
}

/* AXD-I MODE = light (static; no JS derivation). Overriding --color-* cascades
   through every legacy alias above, so the whole shell flips monochrome-light. */
:root[data-mode="light"]{
  color-scheme:light;
  --color-canvas:#F4F4F4;  --color-surface:#FFFFFF;  --color-surface2:#FAFAFA;
  --color-control:#ECECEC; --color-hover:#E6E6E6;
  --color-overlay:#FFFFFF;  --color-overlay-hover:#E6E6E6;
  --color-line:rgba(15,15,15,.10);  --color-linestrong:rgba(15,15,15,.18);
  --color-ink:#141414;  --color-muted:#5F5F5F;  --color-faint:#6F6F6F;
  --color-primary:#0F0F0F;  --color-onprimary:#FFFFFF;
  --color-scrim:rgba(0,0,0,.45);  --color-focus:#141414;
  --color-success:#5D7E65;  --color-warning:#8A7352;  --color-danger:#976C68;  --color-info:#617897;
  --shadow-overlay:0 8px 32px rgba(15,15,15,.12);
  --fill:rgba(20,24,30,.035);
}
@media (prefers-reduced-motion:reduce){
  :root{ --dur-state:1ms; }
}
*{box-sizing:border-box}
html,body{min-height:100%;background:var(--bg);color:var(--text)}
/* v1.5 §3: Body/Section/Meta all track at 0 — tracking is reserved for eyebrows, table
   headers and nav rails, which set it themselves. A global .01em put a low-grade
   deviation on nearly every string in the app. */
body{margin:0;font-family:var(--font-body);font-size:var(--fs-body);font-weight:var(--fw-body);line-height:1.55;letter-spacing:0;-webkit-font-smoothing:antialiased;text-rendering:geometricPrecision}
button,input,select,textarea{font:inherit}
button{cursor:pointer}
.hidden{display:none!important}

/* overflow-x:clip (not hidden) clips horizontal overflow WITHOUT making this a scroll
   container — so the sticky toolbar inside .main can pin to the page scroll. */
.app-shell{position:relative;display:grid;grid-template-columns:var(--toc-open) minmax(0,1fr);min-height:100vh;min-height:100dvh;width:100%;max-width:100vw;overflow-x:clip;transition:grid-template-columns .26s cubic-bezier(.4,0,.2,1)}
/* chatRoot hosts the fixed chat fab + panel; display:contents keeps it out of the grid flow */
#chatRoot{display:contents}
/* the chat panel sits between the TOC and the main column; reserve its width by pushing
   main's left margin (margin-left transitions cleanly — animating a middle grid track with
   a trailing 1fr mis-interpolates in Chrome and gets stuck at 0). Desktop only — on ≤1100
   the chat is an overlay drawer, so main is not pushed. */
@media(min-width:1101px){.app-shell.chat-open .main{margin-left:var(--chat-open,380px)}}
/* right column reserves the chat panel's width so the centre pushes/shrinks like the sidebar (desktop only — on ≤1100 the shell is block and the chat is a drawer) */
.app-shell.chat-open{--chat-gap:var(--chat-open,380px)}
body.chat-resizing .app-shell{transition:none}
/* drag handle to resize the TOC rail (desktop only) */
/* z-index must sit above the sidebar (170) AND the open chat panel (160) — both straddle the
   handle's 9px hit zone (sidebar covers its left 4px, chat the right 5px), so a lower handle
   gets fully buried and undraggable whenever the chat is open. Stays below the toolbar (200). */
.toc-resizer{position:absolute;top:0;bottom:0;left:calc(var(--toc-open) - 4px);width:9px;z-index:180;cursor:col-resize;background:transparent;touch-action:none}
/* the always-visible separator is the sidebar's border-right (matches the chat edge
   and animates with the toggle); this handle just brightens on hover/drag to resize. */
.toc-resizer::after{content:"";position:absolute;top:0;bottom:0;left:4px;width:2px;background:transparent;transition:background .12s}
.toc-resizer:hover::after,body.toc-resizing .toc-resizer::after{background:var(--color-ink)}
body.toc-resizing{cursor:col-resize;user-select:none}
body.toc-resizing .app-shell,body.toc-resizing .sidebar,body.toc-resizing .main{transition:none!important}
.app-shell.toc-collapsed .toc-resizer{display:none}
@media(max-width:1100px){.toc-resizer{display:none}}
.app-shell.toc-collapsed{grid-template-columns:var(--toc-closed) minmax(0,1fr)}

.sidebar{background:var(--bg);color:var(--text);padding:10px;position:sticky;top:0;z-index:170;height:100vh;height:100dvh;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid var(--line);width:var(--toc-open);max-width:var(--toc-open);min-width:0;scrollbar-width:none;transition:width .26s cubic-bezier(.4,0,.2,1),max-width .26s cubic-bezier(.4,0,.2,1),padding .26s cubic-bezier(.4,0,.2,1)}
/* Only the workflow list scrolls — logo, nav (Legend/Structure/Check) and search stay pinned. */
.sidebar-body{display:flex;flex-direction:column;min-height:0;flex:1 1 auto}
/* Header parts keep their size; only the workflow list flexes/scrolls (so the search
   and nav don't get squashed by the flex column on short/mobile viewports). */
.sidebar>.brand,.sidebar-body>.nav-actions,.sidebar-body>.search{flex:0 0 auto}
#tocWorkflowList{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-width:none}
#tocWorkflowList::-webkit-scrollbar{display:none}
.sidebar::-webkit-scrollbar{display:none}
.app-shell.toc-collapsed .sidebar{width:var(--toc-closed);max-width:var(--toc-closed);padding:8px}
.sidebar *{max-width:100%}

.brand{display:flex;align-items:center;gap:10px;min-width:0;width:100%;max-width:100%;overflow:hidden;margin:0 0 10px}
.brand-text{min-width:0;overflow:hidden}
.app-shell.toc-collapsed .brand-text{display:none}
.brand h1{font-size:16px;line-height:24px;margin:0 0 2px;font-weight:var(--fw-control);color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.02em}
.brand small{color:var(--muted);font-size:var(--fs-label-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;line-height:16px;font-weight:var(--fw-body)}

.toc-toggle,.mobile-toc-toggle{width:36px;height:36px;min-width:36px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--line2);background:transparent;color:var(--text);border-radius:var(--radius-sm);padding:0;font-weight:var(--fw-control)}
.toc-toggle:hover,.mobile-toc-toggle:hover{background:var(--paper2);border-color:var(--text-soft)}
.mobile-toc-toggle{display:none}

.nav-actions{display:flex;gap:8px;margin:8px 0 10px}
.nav-button{height:34px;flex:1;border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--text-soft);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--fs-meta);font-weight:var(--fw-control);line-height:18px;display:flex;align-items:center;justify-content:center}
.nav-button:hover{border-color:var(--line2);background:var(--hover)}
.nav-button.active{border-color:rgba(255,255,255,.22);background:var(--accent-muted);color:var(--text)}

.search{width:100%;height:34px;padding:0 13px;border-radius:var(--radius-control);border:1px solid var(--line);background:var(--paper2);color:var(--text);margin:6px 0 10px;outline:none;font-size:var(--fs-body);font-weight:var(--fw-body)}
.search::placeholder{color:var(--muted2)}
.search:focus{border-color:rgba(255,255,255,.35)}

.section-block{border-top:1px solid var(--color-linestrong);padding-top:16px;margin-top:16px;min-width:0}
.section-title{width:100%;height:25px;text-align:left;border:0;background:transparent;color:var(--text);border-radius:var(--radius-sm);padding:0 4px;font-size:var(--fs-meta);font-weight:var(--fw-control);display:flex;align-items:center;line-height:18px;min-width:0;overflow:hidden;white-space:nowrap;letter-spacing:.04em}
/* No hover. The section heading is a LABEL, not a control: it was a <button> carrying
   data-section-open, but nothing in the app ever read that attribute — clicking it did nothing,
   while cursor:pointer and a hover fill both promised it would. It is now a div, so this hover
   is not just unnecessary but actively wrong: hover is how this language says "this responds".
   Also removes a dead tab stop — a keyboard user used to land on every section heading and press
   Enter for no result. Only .workflow-link is selectable in the TOC. */
.section-title{cursor:default}
.section-title span,.workflow-label{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.workflow-link{width:100%;height:25px;text-align:left;border:0;background:transparent;color:#B9C0CA;border-radius:var(--radius-sm);padding:0 4px;font-size:var(--fs-label-sm);font-weight:var(--fw-body);display:flex;align-items:center;gap:6px;line-height:17px;min-width:0;overflow:hidden;white-space:nowrap}
.workflow-link:hover{background:var(--fill);color:var(--text)}
.workflow-link.active{background:var(--accent-muted);color:var(--text);box-shadow:inset 2px 0 0 var(--color-ink)}
.workflow-number{font-weight:var(--fw-control);color:var(--text);flex:0 0 auto}

.main{padding:0 40px 42px;min-width:0;max-width:calc(100vw - var(--toc-open));overflow-x:clip;transition:max-width .26s cubic-bezier(.4,0,.2,1),margin-left .26s cubic-bezier(.4,0,.2,1)}
.app-shell.toc-collapsed .main{max-width:calc(100vw - var(--toc-closed))}
@media(min-width:1101px){
  .app-shell.chat-open .main{max-width:calc(100vw - var(--toc-open) - var(--chat-open,380px))}
  .app-shell.chat-open.toc-collapsed .main{max-width:calc(100vw - var(--toc-closed) - var(--chat-open,380px))}
}

/* full-width top app-bar: pinned across the whole viewport, ABOVE the TOC/chat/main columns,
   so the Contents/Assistant toggles sit far-left at one consistent size in every state. */
.toolbar{position:fixed;top:0;left:0;right:0;z-index:200;min-height:60px;box-sizing:border-box;background:var(--bg);border-bottom:1px solid var(--color-line);padding:8px 24px;display:flex;align-items:center;gap:16px;max-width:100vw;overflow:visible;flex-wrap:wrap;row-gap:8px}
.toolbar-title{font-weight:var(--fw-control);font-size:16px;line-height:24px;color:var(--text);white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:10px;flex:0 1 auto;letter-spacing:.02em}
.toolbar-actions{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap;min-width:0;flex:0 1 auto;margin-left:auto}
/* the Contents/Assistant toggles live in the app-bar, far-left, in EVERY state/size */
.toolbar-toggles{display:flex;align-items:center;gap:8px;flex:0 0 auto;order:0}
.toolbar-tog{display:inline-flex!important;align-items:center;gap:7px;width:auto!important}
.toolbar-tog .tog-ico{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;flex:0 0 auto;font-size:16px;line-height:1}
.toolbar-tog .tog-ico .ai-glyph{width:16px;height:16px}

.btn,label.btn{border:1px solid var(--color-linestrong);background:transparent;color:var(--text);border-radius:var(--radius-control);min-height:42px;padding:7px 15px;box-shadow:none;font-size:var(--text-button);font-weight:var(--fw-control);line-height:20px;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;text-decoration:none;margin:0;letter-spacing:var(--tracking-button);white-space:nowrap;text-align:center}
/* buttons never wrap mid-text; a genuinely long label that must break grows to a clean 2-line
   height (min-height + balanced padding) instead of clipping. */
.btn.btn-wrap,label.btn.btn-wrap{white-space:normal}
.btn:active,label.btn:active{transform:scale(.98)}
.btn:hover,label.btn:hover{border-color:var(--color-linestrong);background:var(--color-hover);color:var(--text)}
/* Primary = ink fill / onprimary text (white on dark, ink on light) */
/* §6 "The primary earns its white" (v1.8). The primary RESTS at the top of the tone ladder —
   `overlay-hover` fill, `ink` text, unmistakably the heaviest control present — and turns
   `primary` white only on hover/focus. It was permanently white: "a permanently white button is
   the brightest object on a black screen, and it stays that way whether or not anyone is going
   to press it. Reaching for it is what lights it." */
.btn.primary{background:var(--color-overlay-hover);color:var(--color-ink);border-color:transparent}
.btn.success{background:transparent;color:var(--text);border-color:var(--color-linestrong)}
.btn.danger{background:transparent;color:var(--color-danger);border-color:var(--color-danger)}
.btn.warning{background:transparent;color:var(--text);border-color:var(--color-linestrong)}
/* Ghost — the rulebook's fourth action variant: "no border, no fill, muted text; hover fill,
   ink text. Toolbars, card headers, row actions, tertiary." It existed only scoped to
   .modal-actions, so anything outside a modal had to pick between Primary and Secondary and
   every tertiary action ended up looking as loud as a real one. Promoted to a global variant. */
.btn.ghost{background:transparent;border-color:transparent;color:var(--color-muted)}
.btn.ghost:hover,.btn.ghost:focus-visible{background:var(--color-hover);color:var(--color-ink);border-color:transparent}
.btn.small{height:32px;min-height:32px;padding:0 10px;font-size:var(--text-button);line-height:16px}
/* Variant buttons re-set background/border, which (at equal specificity, defined after .btn:hover)
   swallowed the generic hover — so give each variant its own hover so EVERY button reacts on hover. */
/* Hover/focus is where it turns white — the reach lights it. This replaces the old rule, which
   dimmed an already-white button 8% toward black: the wrong direction on the wrong resting state. */
.btn.primary:hover,label.btn.primary:hover,.btn.primary:focus-visible,label.btn.primary:focus-visible{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-onprimary)}
.btn.success:hover,label.btn.success:hover,.btn.warning:hover,label.btn.warning:hover{background:var(--color-hover);border-color:var(--color-linestrong);color:var(--text)}
.btn.danger:hover,label.btn.danger:hover{background:var(--color-hover);border-color:var(--color-danger);color:var(--color-danger)}
.btn[disabled]{opacity:.35;pointer-events:none}

.card{background:var(--paper);border-radius:var(--radius-md);box-shadow:none}
.page-card{padding:18px;margin-bottom:18px}
.workflow-header{display:flex;flex-wrap:wrap;justify-content:space-between;gap:18px;align-items:flex-start;border-bottom:1px solid var(--color-linestrong);padding-bottom:18px;margin-bottom:18px}
/* Keep the workflow title on its own line; when the (longer, edit-mode) action buttons don't
   fit beside it, they wrap below instead of squeezing the title into two lines. */
.workflow-header>div:first-child{flex:1 1 300px;min-width:0}
.workflow-header .head-actions{flex:0 1 auto}
/* v1.5 §3: the section kicker is an Eyebrow — 11 / 500 / 0.12em UPPERCASE, not Meta. */
.kicker{text-transform:uppercase;letter-spacing:.12em;color:var(--muted);font-size:var(--text-eyebrow);line-height:20px;font-weight:500}
.workflow-header h2{font-size:var(--fs-title);margin:6px 0 0;line-height:30px;color:var(--text);font-weight:var(--fw-title);letter-spacing:var(--tracking-display)}
.head-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}

.role-panel,.legend-panel{padding:16px;margin-bottom:18px;background:var(--paper2);border-radius:var(--radius-md)}
.role-panel h3,.legend-panel h3{margin:0 0 12px;color:var(--text);font-size:var(--text-section);font-weight:var(--weight-section);line-height:24px}
.role-chip-row,.pill-row{display:flex;flex-wrap:wrap;gap:8px}
.role-chip{display:inline-flex;align-items:center;justify-content:center;padding:6px 10px;min-height:30px;border-radius:var(--radius-chip);border:1px solid rgba(255,255,255,.16);font-weight:var(--fw-control);font-size:var(--fs-meta);line-height:18px;cursor:pointer;box-shadow:none;white-space:nowrap}
.role-chip.active{outline:1px solid #fff;transform:none;box-shadow:none}
.role-chip[draggable="true"],.drag-item[draggable="true"],.step-row[draggable="true"]{cursor:grab}
.drag-over{outline:1px dashed #fff;outline-offset:4px}
.filter-bar{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:10px;color:var(--muted);font-size:var(--fs-meta);line-height:20px;font-weight:var(--fw-body)}

.table-wrap{overflow:auto;border-radius:var(--radius-md);max-width:100%}
table{width:100%;min-width:920px;border-collapse:separate;border-spacing:0;background:var(--paper);font-size:var(--fs-body);font-weight:var(--fw-body)}
th,td{border-bottom:1px solid var(--color-line);padding:12px 12px;vertical-align:top}
th{background:transparent;color:var(--color-faint);text-align:left;font-size:var(--text-th);font-weight:var(--weight-eyebrow);line-height:16px;letter-spacing:var(--tracking-th);text-transform:uppercase;white-space:nowrap}
tr:last-child td{border-bottom:none}
.step-row{transition:opacity .18s ease,filter .18s ease,background .18s ease}
.role-filter-active .step-row.dimmed{opacity:.30;filter:grayscale(.76)}
/* THE ROLE FILTER JOINS THE PALETTE, AND FOLLOWS THE BRAND (g439)
   Owner: "when I filter by step roles I get a blue overlay… it should change when I change the
   appearance as well." Both halves were true. The fill was `#17191D` — a hardcoded blue-grey
   whose blue channel is the highest of the three, left over from the pre-reskin navy scheme.
   That is why it read as blue on a neutral canvas, and why nothing about it moved when a brand
   was chosen.
   `--sel-fill` is the token built for exactly this in g421: `--color-control` with no brand set,
   and `color-mix(brand 24%, control)` once there is one. A filtered row IS a selection, so it
   drinks from the same source as every other engaged state and re-tints with Appearance for free.
   The bar goes 3px -> 1px. §5 is unambiguous — hairlines are always 1px, and 2px exists only as
   the focus ring — so 3px was off-spec at any colour. It takes `--color-primary` for the same
   reason as the fill: it should answer to the brand.
   The light-mode twin further down is deleted with this change rather than retuned. It existed
   because the old fill was a hardcoded DARK value that showed as a black patch on white, and a
   token defined in both modes leaves an override nothing to correct. */
.role-filter-active .step-row.highlighted{background:var(--sel-fill);box-shadow:inset 1px 0 0 var(--color-primary)}

.step-type{display:inline-flex;align-items:center;min-height:30px;padding:6px 10px;border-radius:var(--radius-chip);font-size:var(--fs-meta);font-weight:var(--fw-control);line-height:18px;border:1px solid rgba(255,255,255,.14);white-space:nowrap}
.step-type.start{background:rgba(55,65,81,.22);color:#fff}
.step-type.process{background:rgba(55,65,81,.22);color:#fff}
.step-type.decision{background:rgba(24,37,64,.48);color:#fff;border-color:rgba(255,255,255,.20)}
.step-type.end{background:rgba(180,35,24,.16);color:#F1C4C0;border-color:rgba(180,35,24,.30)}
.step-type.document{background:rgba(55,65,81,.22);color:#fff}

.svg-icon{--icon:#AEB5C1;display:inline-flex;width:32px;height:32px;min-width:32px;align-items:center;justify-content:center;border-radius:var(--radius-sm);margin:0 6px 6px 0;color:var(--icon);background:color-mix(in srgb,var(--icon) 24%,var(--paper2));border:1px solid color-mix(in srgb,var(--icon) 52%,transparent);vertical-align:middle}
.svg-icon svg{width:19px;height:19px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.svg-icon.purple{--icon:#B9B4CA}.svg-icon.mint{--icon:#B7C7BD}.svg-icon.red{--icon:#D58D87}.svg-icon.orange{--icon:#C4B89C}.svg-icon.blue{--icon:#AEBBCB}.svg-icon.db{--icon:#AEBBCB}.svg-icon.system{--icon:#C4C7CC}.svg-icon.tracker{--icon:#B7C7BD}.svg-icon.pay{--icon:#C4C7CC}.svg-icon.sign{--icon:#C4C7CC}.svg-icon.person{--icon:#C4C7CC}.svg-icon.continue{--icon:#C4B89C}.svg-icon.start{--icon:#B7C7BD}.svg-icon.auto{--icon:var(--legend-icon-default)}.svg-icon.custom{--icon:var(--custom-icon,var(--legend-icon-default))}
.icon-label-row{display:flex;align-items:center;gap:10px}

.note-box,.subpath-box{margin-top:10px;padding:10px 12px;border-radius:var(--radius-sm);background:var(--paper2);border:1px dashed rgba(55,65,81,.86);color:var(--text-soft);font-size:var(--fs-meta);line-height:20px}
.branch-panel{margin-top:10px;border-left:2px solid var(--color-ink);border-radius:var(--radius-md);background:var(--paper2);overflow:hidden}
.branch-panel>summary{list-style:none;padding:12px 14px;display:flex;justify-content:space-between;gap:12px;align-items:center;cursor:pointer;color:var(--text);font-weight:var(--fw-control);font-size:var(--fs-body);line-height:22px}
.branch-panel>summary::-webkit-details-marker{display:none}
.branch-content{padding:12px 14px;border-top:1px solid var(--line)}
.nested-table{border-radius:var(--radius-md);overflow:hidden}

.actions{display:flex;flex-wrap:wrap;gap:8px}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
.list{display:grid;gap:10px}
.item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center;padding:12px;border-radius:var(--radius-md);background:var(--paper2)}
.item-title{font-weight:var(--fw-control);color:var(--text);font-size:var(--fs-body);line-height:22px}.item-meta{font-size:var(--fs-meta);line-height:19px;color:var(--muted);margin-top:3px}

select,input[type=text],input[type=password],input[type=file],textarea,input[type=color]{width:100%;border:1px solid var(--line);border-radius:var(--radius-sm);padding:14px 16px;background:var(--surface-light);color:var(--on-surface);outline:none;font-size:var(--fs-body);font-weight:var(--fw-body)}
input[type=color]{height:46px;padding:4px}
textarea{min-height:108px;resize:vertical}
label{display:block;font-size:var(--fs-meta);font-weight:var(--fw-control);color:var(--text-soft);margin:0 0 8px;line-height:18px;letter-spacing:.04em}
/* THE FORM GRID GETS A REAL TOKEN (g446).
   14 is on no ramp at all, and §5 is blunt about it: "If a design needs a distance that is not
   on this list, the design is wrong, not the list."
   ⚠️ KNOWING DIVERGENCE, recorded so a later audit can tell it from the book allowing it. §5
   gives `gap-field` as 20 desktop / 16 mobile, and the letter of that is 20. I have taken 16 on
   BOTH. This rule is inherited by every modal in the app, and the owner has called modal height
   a problem three separate times this cycle — the AI overlay fills 94.7% of a 900px viewport,
   Context Library did too. 20 would add ~6px per field row to every one of them to satisfy a
   number, which is the tail wagging the dog. 16 is on the ramp, it is `gap-field`'s own mobile
   value rather than an invention, and it costs 2px a row. Revisit if the height problem is ever
   solved structurally. */
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.form-grid .wide{grid-column:1/-1}

.modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.74);display:flex;align-items:flex-start;justify-content:center;padding:40px 16px calc(40px + env(safe-area-inset-bottom, 0px));z-index:200}
.modal{display:flex;flex-direction:column;width:min(820px,100%);max-height:calc(100vh - 80px);max-height:calc(100dvh - 80px);max-height:calc(100svh - 80px);overflow:auto;background:var(--tertiary);border-radius:var(--radius-md);border:1px solid var(--border);box-shadow:none;padding:18px;color:var(--text)}
.modal h2{margin:0 0 18px;color:var(--text);font-size:var(--fs-title);font-weight:var(--fw-title);line-height:30px}
/* Sticky action footer — the modal body scrolls, but the primary actions stay
   reachable (fixes buried Save/Cancel on tall modals, esp. mobile). Negative
   margins span the modal's 18px padding so the bar is full-width. */
.modal-actions{display:flex;justify-content:flex-end;gap:10px;position:sticky;bottom:-18px;z-index:1;margin:18px -18px -18px;padding:12px 18px;background:var(--tertiary);border-top:1px solid var(--line)}
.ollama-wizard p,.ollama-wizard pre,.ollama-wizard .ai-check-error,.ollama-wizard .ai-data-note,.ollama-wizard .ai-check-loading{margin:12px 0}
.ollama-wizard pre{max-width:100%;padding:14px 16px}
.ollama-wizard .modal-actions{margin-top:20px}
/* Destructive "Clear all" pushed to the far left so it can't be hit by mistake reaching for Close (bottom-right). */
.modal-actions .src-clear-all{margin-right:auto}
.color-presets{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.color-swatch{width:28px;height:28px;border-radius:var(--radius-chip);border:1px solid rgba(255,255,255,.25);box-shadow:none;padding:0}
/* g411 — the two logo blocks are siblings in a grid, so gap-item (16), not 14, which is on no
   ramp. And inside a block the label, the 118px preview and its button were 8px apart: gap-icon
   is for an icon and its label, not for a control sitting under a bordered box the size of a
   card. stack-heading (12) gives the button room off the preview's edge. */
.project-modal .logo-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.logo-upload-block{display:grid;gap:12px}
.logo-preview{min-height:118px;border-radius:var(--radius-sm);background:var(--fill);display:flex;align-items:center;justify-content:center;padding:14px}
.logo-preview img{max-width:100%;max-height:90px;object-fit:contain}
.logo-preview span{color:var(--muted2);font-size:var(--fs-meta)}

.notice{padding:12px 14px;border-radius:var(--radius-sm);background:var(--paper2);color:var(--text);margin-bottom:18px;font-size:var(--fs-body);line-height:22px}
.legend-top{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:18px}
.legend-top h2{color:var(--text);margin:0 0 4px;font-size:var(--fs-title);font-weight:var(--fw-title);line-height:30px}.manager-note{color:var(--muted);margin-top:0;font-size:var(--fs-body);line-height:22px;font-weight:var(--fw-body)}
.workflow-empty{padding:24px;text-align:center;color:var(--muted)}
.toc-backdrop{display:none}

@media(max-width:1100px){
  .app-shell,.app-shell.toc-collapsed{display:block;max-width:100vw}
  /* Open the drawer BELOW the toolbar (not behind it): the toolbar is sticky at z-index 200,
     so a top:0 drawer had its head (logo · Legend/Structure/Check · search) hidden under the
     toolbar. Anchoring to --bar-h (the measured toolbar height) keeps the whole TOC visible and
     leaves the toolbar tappable to close it. */
  .app-shell .sidebar{position:fixed;left:0;top:var(--bar-h,60px);height:calc(100vh - var(--bar-h,60px));height:calc(100dvh - var(--bar-h,60px));height:calc(100svh - var(--bar-h,60px));min-height:calc(100vh - var(--bar-h,60px))!important;min-height:calc(100dvh - var(--bar-h,60px))!important;min-height:calc(100svh - var(--bar-h,60px))!important;width:100vw;max-width:100vw;z-index:170;box-shadow:none;transform:translateX(0);transition:transform .18s ease;padding:16px 14px calc(10px + env(safe-area-inset-bottom, 0px))!important}
  .app-shell.toc-collapsed .sidebar{transform:translateX(-105%);width:min(84vw,330px);max-width:min(84vw,330px)}
  .app-shell.toc-collapsed .brand-text,.app-shell.toc-collapsed .sidebar-body{display:block}
  .toc-backdrop{display:block;position:fixed;inset:0;background:rgba(0,0,0,.58);z-index:110}
  .app-shell.toc-collapsed .toc-backdrop{display:none}
  .mobile-toc-toggle{display:inline-flex}
  .grid-2,.grid-3{grid-template-columns:1fr}
  .main,.app-shell.toc-collapsed .main{padding:0 18px 40px;max-width:100vw}
  .toolbar{position:sticky;display:flex}
  .toolbar-title{margin-bottom:0}
  .toolbar::after{display:none}
  .toolbar-actions{justify-content:flex-start}
  .workflow-header{flex-direction:column}.workflow-header>div:first-child{flex:0 0 auto}.head-actions{justify-content:flex-start}
}
@media(max-width:720px){
  th{display:none}table,tbody,tr,td{display:block;width:100%}table{min-width:0}.step-row{border-bottom:1px solid var(--line)}td{border-bottom:0;padding:10px 12px}.item{grid-template-columns:1fr}.form-grid{grid-template-columns:1fr}.nav-actions{grid-template-columns:1fr}.toolbar-actions{gap:8px}.btn,label.btn{min-height:38px;padding:6px 10px;font-size:var(--fs-meta)}
  .project-modal .logo-grid{grid-template-columns:1fr}
  .modal-backdrop{padding:14px 8px calc(14px + env(safe-area-inset-bottom, 0px))}
  .modal{max-height:calc(100svh - 28px)}
  .ollama-wizard .modal-actions{flex-wrap:wrap}
  .ollama-wizard .modal-actions>.btn{flex:1 1 100%;width:100%;max-width:100%;white-space:normal;overflow-wrap:anywhere;justify-content:center;text-align:center}
}


/* V44 BRAND POLISH + MOBILE TOUCH + PRINT/PDF VIEW */
:root{
  --toc-open:310px;
  --fs-meta:12px;
  --fs-body:var(--text-body-axd);
  --fs-body-lg:16px;
  --fs-title:24px;
}

.brand{
  align-items:flex-start;
  padding:8px 0 14px;
  margin:0 0 8px;
  min-height:78px;
}
.brand-lockup{
  display:flex;
  align-items:center;
  gap:12px;
  min-width:0;
}
.brand-mark{
  width:42px;
  height:42px;
  min-width:42px;
  border:1px solid rgba(255,255,255,.42);
  border-radius:4px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  color:var(--text);
  font-weight:500;
  font-size:16px;
  line-height:1;
  letter-spacing:.04em;
  background:var(--fill);
}
.brand-wordmark{
  min-width:0;
  overflow:hidden;
}
.brand-wordmark strong{
  display:block;
  color:var(--text);
  font-size:16px;
  font-weight:500;
  line-height:20px;
  letter-spacing:.03em;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.brand-wordmark span{
  display:block;
  color:var(--text-soft);
  font-size:12px;
  font-weight:400;
  line-height:16px;
  letter-spacing:.20em;
  text-transform:uppercase;
}
.app-shell.toc-collapsed .brand{
  padding:6px 0;
  min-height:0;
  align-items:center;
}
.app-shell.toc-collapsed .brand-lockup,
.app-shell.toc-collapsed .brand-wordmark,
.app-shell.toc-collapsed .brand-mark{
  display:none;
}

.sidebar{padding-top:10px}
.section-title{font-size:14px;height:28px}
.workflow-link{font-size:14px;height:28px}
.workflow-number{min-width:32px}
.nav-button{font-size:14px}
.search{font-size:16px;height:42px}

.toolbar-title{font-size:28px}
.btn,label.btn{min-height:44px;padding:8px 16px}
.btn.small{height:34px;min-height:34px}

.workflow-header h2{font-size:28px;line-height:34px}
.role-panel h3,.legend-panel h3{font-size:var(--text-section);line-height:24px}
.kicker{font-size:var(--text-eyebrow);line-height:20px}
table{font-size:16px}
th{font-size:12px}
.role-chip{min-height:31px;font-size:14px;background-blend-mode:multiply}
.role-panel .role-chip{
  border-color:rgba(255,255,255,.20);
}
.step-type{min-height:31px;font-size:14px}
.item-title{font-size:16px}

.svg-icon{
  width:34px;
  height:34px;
  min-width:34px;
  background:var(--color-control);
  border-color:color-mix(in srgb,var(--icon) 76%,transparent);
}
.svg-icon svg{
  width:20px;
  height:20px;
  stroke-width:1.5;
}
.svg-icon.purple{--icon:#8B6FF6}
.svg-icon.mint{--icon:#3FB394}
.svg-icon.red{--icon:#E0A09A}
.svg-icon.orange{--icon:#E59A45}
.svg-icon.blue{--icon:#C1CEE0}
.svg-icon.db{--icon:#4D8BFF}
.svg-icon.system{--icon:#E3E7EC}
.svg-icon.tracker{--icon:#7ED6CC}
.svg-icon.pay{--icon:#E8D3A8}
.svg-icon.sign{--icon:#D8DDE6}
.svg-icon.person{--icon:#D8DDE6}
.svg-icon.continue{--icon:#E8D3A8}
.svg-icon.start{--icon:#34D17A}

.role-chip{
  box-shadow:inset 0 0 0 999px rgba(255,255,255,.02);
}
/* g439 — the chip you pressed marks itself with the brand too, for the same reason as the rows
   it filters: this is a selection, and Appearance's own copy promises brand recolours selection.
   It was a hardcoded 80% white, which neither followed the brand nor sat on the palette. */
.role-chip.active{
  outline:1px solid var(--color-primary);
  outline-offset:2px;
}
.presentation-mode .head-actions,
.presentation-mode .actions,
.presentation-mode .filter-bar .btn{
  display:none!important;
}
.presentation-mode .role-panel{
  padding:12px 14px;
}
.presentation-mode .toolbar-actions .edit-sensitive{
  display:none!important;
}

@media(max-width:1100px){
  .brand{min-height:74px}
  .main,.app-shell.toc-collapsed .main{padding:0 18px 42px}
  /* Breathing room between the sticky toolbar and the workflow heading/kicker — mobile had
     none (desktop puts it on #mainView; this is the mobile counterpart). No sticky thead on
     mobile, so top padding here is safe. */
  .app-shell .main>#mainView{padding-top:18px}
  .toolbar-actions{gap:8px}
}

@media(max-width:720px){
  :root{
    --fs-meta:12px;
    --fs-body:var(--text-body-axd);
    --fs-title:24px;
  }
  body{font-size:16px;line-height:1.55}
  .sidebar{
    width:min(88vw,360px);
    max-width:min(88vw,360px);
    padding:12px;
  }
  .app-shell.toc-collapsed .sidebar{
    width:min(88vw,360px);
    max-width:min(88vw,360px);
  }
  .brand{
    min-height:82px;
    padding:8px 0 14px;
  }
  .brand-mark{
    width:46px;
    height:46px;
    min-width:46px;
  }
  .brand-wordmark strong{
    font-size:16px;
    line-height:22px;
  }
  .section-title,
  .workflow-link,
  .nav-button{
    min-height:44px!important;
    height:44px!important;
    font-size:16px;
    padding-left:8px;
    padding-right:8px;
  }
  .search{
    height:50px!important;
    font-size:16px;
  }
  .toolbar{
    padding:10px 0;
  }
  .toolbar-title{
    font-size:16px;
    line-height:24px;
  }
  /* Keep Workflow View, Undo and Redo directly in the toolbar; wrap controls into comfortable
     touch-sized rows instead of moving primary view/history actions into Settings. */
  .toolbar-actions{
    display:flex;
    flex-wrap:wrap;
    gap:8px;
    width:100%;
  }
  /* Equal share, not a 120px floor: a 120px basis forces 5 buttons to wrap onto
     3 rows at 320-390px, which cost 66% extra toolbar height (157px -> 261px,
     36% of a 320x720 viewport). */
  .toolbar-actions>.btn{flex:1 1 0;min-width:0;width:auto}
  .btn,label.btn{
    height:44px;
    min-height:44px;
    width:100%;
    padding:0 12px;
    font-size:14px;
  }
  .btn.small{
    height:40px;
    min-height:40px;
    padding:0 12px;
    font-size:14px;
  }
  .role-chip{
    min-height:40px;
    padding:8px 12px;
    font-size:14px;
  }
  .step-type{
    min-height:38px;
    padding:8px 12px;
    font-size:14px;
  }
  .svg-icon{
    width:40px;
    height:40px;
    min-width:40px;
  }
  .svg-icon svg{
    width:22px;
    height:22px;
  }
  .workflow-header h2{
    font-size:24px;
    line-height:30px;
  }
  .role-panel,.legend-panel,.page-card{
    padding:16px;
  }
  .actions{
    gap:8px;
  }
  .actions .btn{
    width:auto;
    min-width:88px;
  }
  td{
    font-size:16px;
    line-height:1.55;
  }
}

@media print{
  body{
    background:#fff!important;
    color:#111!important;
    font-family:"Figtree","Helvetica Neue",Arial,sans-serif!important;
  }
  .sidebar,
  .toolbar,
  .toc-backdrop,
  .head-actions,
  .actions,
  .filter-bar,
  .role-panel,
  #notice,
  #cloudBar{
    display:none!important;
  }
  .app-shell,
  .app-shell.toc-collapsed{
    display:block!important;
  }
  .main,
  .app-shell.toc-collapsed .main{
    max-width:none!important;
    padding:0!important;
  }
  .card,
  .page-card{
    border:0!important;
    box-shadow:none!important;
    background:#fff!important;
    color:#111!important;
    padding:0!important;
  }
  .workflow-header{
    border-bottom:1px solid #999!important;
    margin-bottom:16px!important;
    padding-bottom:12px!important;
  }
  .workflow-header h2,
  .kicker{
    color:#111!important;
  }
  .table-wrap{
    border:1px solid #999!important;
    overflow:visible!important;
  }
  table{
    min-width:0!important;
    width:100%!important;
    color:#111!important;
    background:#fff!important;
    font-size:11px!important;
  }
  th{
    background:#f0f0f0!important;
    color:#111!important;
  }
  td,th{
    border-color:#ccc!important;
    padding:6px!important;
  }
  .role-chip,.step-type,.svg-icon{
    color:#111!important;
    background:#fff!important;
    border:1px solid #999!important;
  }
}


/* V45 OFFICIAL LOGO INTEGRATION */
.brand{
  min-height:92px;
  align-items:flex-start;
  gap:10px;
}
.brand-logo-wrap{
  flex:1;
  min-width:0;
  display:flex;
  align-items:center;
  justify-content:flex-start;
  padding-top:2px;
}
.brand-logo-wide{
  width:210px;
  max-width:100%;
  height:auto;
  display:block;
  object-fit:contain;
}
.brand-logo-icon{
  display:none;
  width:38px;
  height:38px;
  object-fit:contain;
}
.app-shell.toc-collapsed .brand{
  min-height:46px;
  padding:6px 0;
  justify-content:center;
}
.app-shell.toc-collapsed .brand-logo-wrap{
  display:flex;
  justify-content:center;
  width:100%;
}
.app-shell.toc-collapsed .brand-logo-wide{
  display:none;
}
.app-shell.toc-collapsed .brand-logo-icon{
  display:block;
}
.app-shell.toc-collapsed .toc-toggle{
  position:absolute;
  top:8px;
  left:8px;
  opacity:.08;
}
.app-shell.toc-collapsed .toc-toggle:hover{
  opacity:1;
}

@media(max-width:1100px){
  .brand{
    min-height:96px;
  }
  .brand-logo-wide{
    width:230px;
  }
  .app-shell.toc-collapsed .brand-logo-wide{
    display:block;
  }
  .app-shell.toc-collapsed .brand-logo-icon{
    display:none;
  }
  .app-shell.toc-collapsed .toc-toggle{
    position:relative;
    top:auto;
    left:auto;
    opacity:1;
  }
}
@media(max-width:720px){
  .brand-logo-wide{
    width:240px;
  }
}


/* V46 BRANCH WORKFLOW + TOC REFINEMENT */

/* Softer overall geometry */
:root{
  --toc-open:318px;
  --toc-closed:44px;
  /* radius aliases removed — they are defined once, on the AXD-I role scale, at the top
     of this file. Redefining them here (6/12/16) and again in the AXI PASS block (10/14/18)
     put every radius in the app off the 8/10/16/20 scale by whichever block happened to be
     last. Do not reintroduce a local override; change the role tokens instead. */
  --fs-meta:12px;
  --fs-body:var(--text-body-axd);
  --fs-title:24px;
}

/* TOC: remove dead top gap, make logo placement intentional */
.sidebar{
  padding:6px 10px 10px;
}
.brand{
  min-height:58px!important;
  padding:0 0 8px!important;
  margin:0 0 6px!important;
  align-items:center!important;
  gap:8px!important;
}
.brand-logo-wrap{
  padding-top:0!important;
  align-items:center!important;
}
.brand-logo-wide{
  width:230px!important;
  max-height:46px!important;
  object-fit:contain!important;
  object-position:left center!important;
}
.toc-toggle{
  width:48px!important;
  min-width:48px!important;
  height:44px!important;
  font-size:16px!important;
}

/* Collapsed TOC should be a clean rail, not a half-visible hamburger */
.app-shell.toc-collapsed{
  grid-template-columns:var(--toc-closed) minmax(0,1fr)!important;
}
.app-shell.toc-collapsed .sidebar{
  width:var(--toc-closed)!important;
  max-width:var(--toc-closed)!important;
  padding:6px 4px!important;
  overflow:hidden!important;
}
.app-shell.toc-collapsed .brand{
  min-height:44px!important;
  padding:0!important;
  justify-content:center!important;
  margin:0!important;
}
.app-shell.toc-collapsed .toc-toggle{
  position:absolute!important;
  inset:auto!important;
  left:6px!important;
  top:54px!important;
  width:32px!important;
  height:32px!important;
  min-width:32px!important;
  opacity:1!important;
  border-color:rgba(255,255,255,.14)!important;
  background:transparent!important;
}
.app-shell.toc-collapsed .brand-logo-wrap{
  width:100%!important;
  justify-content:center!important;
}
.app-shell.toc-collapsed .brand-logo-wide{
  display:none!important;
}
.app-shell.toc-collapsed .brand-logo-icon{
  display:block!important;
  width:34px!important;
  height:34px!important;
  object-fit:contain!important;
}
.app-shell.toc-collapsed .brand-text{
  display:none!important;
}
/* Collapse: the TOC content wipes with a clip-path exactly like the Axi chat panel
   (desktop rules below, ~L3558). Here we only kill pointer events while it's hidden;
   the visual wipe + width-freeze (no reflow) live in the min-width:1101 block. On
   mobile the drawer slides via transform, so it never hits the clip path. */
.app-shell.toc-collapsed .sidebar-body{
  pointer-events:none;
}

/* TOC typography: larger, still clean and single-line */
.nav-actions{
  margin:6px 0 8px!important;
  gap:8px!important;
}
.nav-button{
  height:38px!important;
  min-height:38px!important;
  font-size:16px!important;
  line-height:20px!important;
}
.search{
  height:42px!important;
  font-size:16px!important;
  margin:6px 0 10px!important;
}
.section-block{
  padding-top:8px!important;
  margin-top:8px!important;
}
.section-title{
  height:32px!important;
  min-height:32px!important;
  font-size:16px!important;
  line-height:20px!important;
  padding:0 8px!important;
}
.workflow-link{
  height:32px!important;
  min-height:32px!important;
  font-size:14px!important;
  line-height:20px!important;
  padding:0 8px!important;
  gap:7px!important;
}
.workflow-number{
  min-width:36px!important;
}

/* Toolbar wording and layout */
.toolbar-title{
  font-size:28px!important;
  line-height:26px!important;
  letter-spacing:.01em!important;
}

/* Softer corners */
.card,.page-card,.role-panel,.legend-panel,.table-wrap,.branch-panel,.nested-table,.item,.modal,.notice{
  border-radius:14px!important;
}
/* the .btn/input border-radius patch that sat here is gone: an identical selector list,
   also top-level and also !important, restates it later in this file as
   var(--radius-sm)!important, so this one could never win. */
.role-chip,.step-type{
  border-radius:var(--radius-chip)!important;
}
.svg-icon{
  border-radius:10px!important;
}

/* Role palette: distinct but muted. Text remains auto-contrast from JS. */
.role-chip{
  border-color:rgba(255,255,255,.18)!important;
  box-shadow:inset 0 0 0 999px rgba(255,255,255,.035)!important;
}
/* Light mode: that white hairline border is invisible on a light surface, and the inset white
   overlay washes the role pastels toward grey (so every role looked alike). Use a subtle dark
   border and drop the overlay so each role's own tint stays visible. */
:root[data-mode="light"] .role-chip{
  border-color:rgba(20,24,30,.16)!important;
  box-shadow:none!important;
}
.role-panel .role-chip{
  opacity:.94;
}
.role-panel .role-chip:hover{
  opacity:1;
  outline:1px solid rgba(255,255,255,.30);
}

/* Step types: restore useful color distinction with subtle fill and clearer outline */
.step-type.start{
  background:rgba(47,74,67,.46)!important;
  color:#D9EFE5!important;
  border-color:rgba(123,170,150,.55)!important;
}
.step-type.process{
  background:rgba(24,37,64,.50)!important;
  color:#DCE5F5!important;
  border-color:rgba(91,113,152,.58)!important;
}
.step-type.decision{
  background:rgba(89,75,42,.52)!important;
  color:#F1E4BE!important;
  border-color:rgba(197,179,88,.62)!important;
}
.step-type.document{
  background:rgba(64,57,85,.48)!important;
  color:#E5DFF5!important;
  border-color:rgba(160,146,190,.55)!important;
}
.step-type.end{
  background:rgba(90,56,56,.48)!important;
  color:#F2CACA!important;
  border-color:rgba(190,116,110,.55)!important;
}

/* Icon visibility: same family, more contrast */
.svg-icon{
  background:color-mix(in srgb,var(--icon) 16%,transparent)!important;
  border-width:1.25px!important;
  filter:saturate(1.08);
}
.svg-icon svg{
  stroke-width:1.5!important;
}
.svg-icon.purple{--icon:#8B6FF6!important}
.svg-icon.mint{--icon:#3FB394!important}
.svg-icon.red{--icon:#F0B5B0!important}
.svg-icon.orange{--icon:#E59A45!important}
.svg-icon.blue{--icon:#D2DFF2!important}
.svg-icon.db{--icon:#4D8BFF!important}
.svg-icon.system{--icon:#E3E7EC!important}
.svg-icon.tracker{--icon:#7ED6CC!important}
.svg-icon.pay{--icon:#E8D3A8!important}
.svg-icon.sign{--icon:#D8DDE6!important}
.svg-icon.person{--icon:#D8DDE6!important}
.svg-icon.continue{--icon:#E8D3A8!important}
.svg-icon.start{--icon:#34D17A!important}

/* Branch language/visuals: branch is a sub-workflow, not just a decision note */
.branch-panel{
  border-left-width:3px!important;
}
.branch-panel>summary{
  color:#E9EDF5!important;
}
.branch-panel>summary::before{
  content:"Branch";
  font-size:11px;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--muted);
  border:1px solid rgba(255,255,255,.16);
  border-radius:var(--radius-chip);
  padding:3px 7px;
  margin-right:8px;
}

/* Mobile/touch: keep TOC fully usable and collapsed overlay behavior */
@media(max-width:1100px){
  .app-shell.toc-collapsed .sidebar{
    transform:translateX(-105%)!important;
    width:min(88vw,360px)!important;
    max-width:min(88vw,360px)!important;
  }
  .app-shell.toc-collapsed .sidebar-body{
    display:block!important;
  }
  .app-shell.toc-collapsed .brand-logo-wide{
    display:block!important;
  }
  .app-shell.toc-collapsed .brand-logo-icon{
    display:none!important;
  }
  .app-shell.toc-collapsed .toc-toggle{
    position:relative!important;
    top:auto!important;
    left:auto!important;
    opacity:1!important;
  }
  .brand{
    min-height:62px!important;
  }
  .brand-logo-wide{
    width:238px!important;
    max-height:52px!important;
  }
}

@media(max-width:720px){
  .section-title,.workflow-link,.nav-button{
    height:46px!important;
    min-height:46px!important;
    font-size:16px!important;
  }
  .search{
    height:48px!important;
  }
  .toc-toggle,.mobile-toc-toggle{
    width:44px!important;
    height:44px!important;
    min-width:44px!important;
  }
}

/* V47 EDITABILITY + TOC CLEANUP */
:root{
  --toc-closed:44px;
}

/* Desktop TOC uses available page height without its own unnecessary scroll wheel. */
@media(min-width:1101px){
  .sidebar{
    height:auto!important;
    min-height:100vh!important;
    overflow:visible!important;
    padding-top:4px!important;
  }
  .sidebar-body{
    overflow:visible!important;
  }
}

/* Remove the odd collapsed logo; collapsed desktop TOC is only the clean menu rail. */
.app-shell.toc-collapsed .brand{
  min-height:36px!important;
  height:36px!important;
  padding:0!important;
  margin:0!important;
  display:flex!important;
  justify-content:center!important;
  align-items:center!important;
}
.app-shell.toc-collapsed .brand-logo-wrap,
.app-shell.toc-collapsed .brand-logo-wide,
.app-shell.toc-collapsed .brand-logo-icon,
.app-shell.toc-collapsed .brand-text{
  display:none!important;
}
.app-shell.toc-collapsed .toc-toggle{
  position:static!important;
  opacity:1!important;
  width:32px!important;
  height:32px!important;
  min-width:32px!important;
  background:transparent!important;
  border-color:rgba(255,255,255,.22)!important;
}
.app-shell.toc-collapsed .sidebar{
  display:flex!important;
  align-items:flex-start!important;
  justify-content:center!important;
  padding:6px!important;
}

/* Mobile keeps the overlay drawer behavior and scrolls only when the drawer content truly needs it. */
@media(max-width:1100px){
  .sidebar{
    overflow-y:auto!important;
    overflow-x:hidden!important;
  }
  .app-shell.toc-collapsed .brand-logo-wrap,
  .app-shell.toc-collapsed .brand-logo-wide{
    display:flex!important;
  }
  .app-shell.toc-collapsed .brand-logo-icon{
    display:none!important;
  }
}

/* Drag affordances for Structure Manager and all Legend categories. */
.drag-handle{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:26px;
  height:26px;
  margin-right:8px;
  border:1px solid rgba(255,255,255,.14);
  border-radius:8px;
  color:var(--muted);
  font-size:14px;
  line-height:1;
  vertical-align:middle;
  cursor:grab;
}
.drag-item[draggable="true"]{
  user-select:none;
}
.legend-panel .item,
.page-card .item{
  transition:outline-color .14s ease, background .14s ease;
}
.drag-over{
  outline:2px dashed rgba(255,255,255,.72)!important;
  outline-offset:3px!important;
  background:var(--fill)!important;
}
/* An open subpath panel is a branch drop target so a step can be dragged into an empty subpath.
   Inset the outline (offset:-2px) so it reads as "drop inside here" rather than around the row. */
.subpath-panel.drag-over{outline-offset:-2px!important;border-radius:8px}
.sp-empty-hint{opacity:.7;font-size:.92em}


/* ============================================================
   V48 UI POLISH
   ------------------------------------------------------------
   - Clean collapsed rail (one menu button, no dead space)
   - Wider TOC, full labels (no wrap, no ellipsis)
   - Single page scroll on desktop (no independent sidebar scroll)
   - Drag handles hidden (whole row still drags)
   - Fixed-width steps table (descriptions wrap, never clipped)
   - Calmer toolbar
   - Subpath drill-in (breadcrumbs + sub-workflow pages)
   - Micro-interactions (button press, newly-added flash, toast)
   ============================================================ */
:root{
  --toc-open:424px;
  --toc-closed:46px;
}

/* --- Kill dead space: the inline icon-sprite SVG was rendering at its
       default 300x150 box at the top of the sidebar, pushing the logo down. --- */
#ab-icon-sprite{
  position:absolute!important;
  width:0!important;
  height:0!important;
  overflow:hidden!important;
}

/* --- Desktop sidebar: scrolls with the page, no inner scrollbar --- */
@media(min-width:1101px){
  .sidebar{
    position:sticky!important;
    top:0!important;
    align-self:start!important;
    height:auto!important;
    min-height:0!important;
    max-height:none!important;
    overflow:visible!important;
    padding:8px 12px 20px!important;
  }
  .sidebar-body{overflow:visible!important}
}

/* --- Expanded brand: logo at the top, no dead gap above it --- */
.brand{
  min-height:0!important;
  padding:2px 0 10px!important;
  margin:0 0 4px!important;
  align-items:center!important;
  gap:8px!important;
}
.brand-logo-wide{
  width:208px!important;
  max-height:42px!important;
}
/* Mobile: the Contents drawer is wider than the logo at 208px, so the wordmark looked small
   and lost in empty space. Two fixes (scoped to mobile so the desktop rail keeps 208×42):
   1) the .brand flex box collapsed to ~12px tall with overflow:hidden, CLIPPING the logo to a
      thin sliver (the real "squashed" look) — give it real height and stop clipping;
   2) let the logo scale up (height-driven, aspect preserved) so it fills the drawer legibly. */
@media(max-width:1100px){
  .brand{min-height:62px!important;overflow:visible!important;align-items:center!important}
  .brand-logo-wrap{overflow:visible!important;align-items:center!important}
  .brand-logo-wide{width:auto!important;max-width:100%!important;max-height:48px!important}
}

/* --- Collapsed desktop rail: a single clean menu button, nothing else --- */
.app-shell.toc-collapsed .sidebar{
  display:block!important;
  padding:10px 4px!important;
  overflow:hidden!important;
}
.app-shell.toc-collapsed .brand{
  display:flex!important;
  justify-content:center!important;
  align-items:flex-start!important;
  min-height:0!important;
  height:auto!important;
  padding:2px 0 0!important;
  margin:0!important;
}
.app-shell.toc-collapsed .brand-logo-wrap,
.app-shell.toc-collapsed .brand-logo-wide,
.app-shell.toc-collapsed .brand-logo-icon,
.app-shell.toc-collapsed .brand-text{
  display:none!important;
}
.app-shell.toc-collapsed .toc-toggle{
  position:static!important;
  inset:auto!important;
  margin:0!important;
  width:34px!important;
  height:34px!important;
  min-width:34px!important;
  opacity:1!important;
  background:transparent!important;
  border-color:rgba(255,255,255,.22)!important;
}

/* --- TOC labels: single line, full text, no ellipsis --- */
.nav-actions{flex-wrap:wrap!important}
.nav-button{flex:1 1 auto!important}
.section-title{
  height:auto!important;
  min-height:30px!important;
  padding:5px 8px!important;
  align-items:center!important;
}
.section-title span{
  overflow:visible!important;
  text-overflow:clip!important;
  white-space:nowrap!important;
}
.workflow-link{
  height:auto!important;
  min-height:30px!important;
  padding:5px 8px!important;
  align-items:center!important;
}
.workflow-label{
  flex:1 1 auto;min-width:0;
  overflow:hidden!important;
  text-overflow:ellipsis!important;
  white-space:nowrap!important;
}

/* --- Direct edit: the whole item drags (grab any empty part), and a pencil icon-button toggles
   its inline editor. No drag handle, no global edit mode. --- */
[data-drag-type][draggable="true"]{cursor:grab}
[data-drag-type][draggable="true"]:active{cursor:grabbing}
.edit-pen{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;min-width:32px;flex:0 0 auto;padding:0;border:1px solid var(--line);border-radius:8px;background:var(--paper2);color:var(--text-soft);cursor:pointer;transition:border-color .12s,background .12s,color .12s}
.edit-pen svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.edit-pen:hover{border-color:var(--line-state);color:var(--text)}
.edit-pen.active{background:var(--accent-strong);border-color:var(--line-state);color:var(--text)}
.item-right{display:flex;flex-direction:column;align-items:flex-end;justify-content:flex-end;gap:6px}
/* Step value + edit pen sit on one centred line when there's room, and the pen wraps
   underneath when the column is narrow — never overlapping (no absolute positioning). */
.pen-cell-inner{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px}
.step-pen-wrap{display:flex}
.next-cell{text-align:center}
/* --- Beta 2.2 · Comments & suggestions ------------------------------------------------
   Per-step comment button (lives in .step-tools beside the edit pen) + the thread modal.
   Themed entirely with surface vars so it tracks dark AND light (applyTheme rewrites the
   paper / line / text surface tokens per mode). --- */
.step-tools{display:flex;align-items:center;justify-content:center;gap:6px}
.cmt-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;min-width:32px;flex:0 0 auto;padding:0;border:1px solid var(--line);border-radius:8px;background:var(--paper2);color:var(--text-soft);cursor:pointer;transition:border-color .12s,background .12s,color .12s}
.cmt-btn svg{display:block;width:15px;height:15px}
.cmt-btn:hover{border-color:var(--line-state);color:var(--text)}
.cmt-btn.has{color:var(--text);border-color:var(--line-state)}
.cmt-n{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 4px;border-radius:9px;background:var(--color-primary);color:var(--color-onprimary);border:1px solid var(--paper);font-size:11px;font-weight:var(--fw-control);line-height:14px;display:inline-flex;align-items:center;justify-content:center}
/* Thread modal — narrower than the form modals; reuses .modal/.modal-backdrop. */
.cmt-modal{width:min(560px,100%)}
.cmt-list{display:flex;flex-direction:column;gap:10px;margin:4px 0 2px}
.cmt-thread{border-radius:var(--radius-md);background:var(--paper2);overflow:hidden}
.cmt-thread.resolved{opacity:.62}
.cmt-item{padding:11px 13px}
.cmt-item+.cmt-item{border-top:1px solid var(--line)}
.cmt-item.cmt-reply{margin-left:16px;border-left:2px solid var(--line);background:rgba(127,127,127,.05)}
.cmt-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:5px}
.cmt-author{font-weight:var(--fw-control);font-size:var(--fs-meta);color:var(--text)}
.cmt-sug{font-size:11px;line-height:16px;padding:0 7px;border-radius:var(--radius-chip);background:rgba(192,136,58,.16);color:#C0883A;border:1px solid rgba(192,136,58,.42)}
.cmt-time{margin-left:auto;font-size:var(--fs-label-sm);color:var(--muted)}
.cmt-body{font-size:var(--fs-body);color:var(--text-soft);white-space:pre-wrap;word-break:break-word;line-height:1.5}
.cmt-acts{display:flex;gap:6px;margin-top:9px;flex-wrap:wrap}
.cmt-replybox{padding:0 13px 11px 29px}
.cmt-replybox>div{margin-top:6px;display:flex;justify-content:flex-end}
.cmt-compose{margin-top:14px;border-top:1px solid var(--line);padding-top:14px}
.cmt-compose-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:9px;flex-wrap:wrap}
.cmt-sug-toggle{display:inline-flex;align-items:center;gap:7px;font-size:var(--fs-meta);color:var(--text-soft);cursor:pointer;user-select:none}
.cmt-sug-toggle input{width:auto;margin:0;padding:0}
.cmt-empty{font-size:var(--fs-meta);color:var(--muted);padding:16px 4px;text-align:center}
.cmt-loading{font-size:var(--fs-meta);color:var(--muted);padding:8px 4px}
.cmt-resolved-wrap{margin-top:2px}
.cmt-resolved-wrap>summary{cursor:pointer;font-size:var(--fs-meta);color:var(--muted);padding:7px 2px;list-style:none}
.cmt-resolved-wrap>summary::-webkit-details-marker{display:none}
.cmt-resolved-wrap>summary::before{content:"▸ ";font-size:11px}
.cmt-resolved-wrap[open]>summary::before{content:"▾ "}
.cmt-resolved-wrap[open]>summary{margin-bottom:8px}
.cmt-resolved-wrap .cmt-thread{margin-bottom:8px}
:root[data-mode="light"] .cmt-sug{color:#8a5a16;background:rgba(192,136,58,.14);border-color:rgba(192,136,58,.5)}
:root[data-mode="light"] .cmt-item.cmt-reply{background:rgba(20,24,30,.035)}
/* Legend panel header: title + actions on one row, actions right-aligned. */
.legend-panel-head{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:30px}
.legend-panel-head h3{margin:0!important}
.legend-panel-head .legend-panel-actions{margin:0}
@media(min-width:721px){.pen-cell{vertical-align:top}}

/* --- Steps table: fixed layout so descriptions wrap instead of clipping --- */
@media(min-width:721px){
  .table-wrap>table,.nested-table>table{
    table-layout:fixed!important;
    width:100%!important;
    min-width:0!important;
  }
  /* 46 -> 64 and nowrap. Deep nesting produces "13.b.i" — a subpath inside a subpath — which
     wrapped onto two lines and knocked the row off its rhythm. The column was sized for "13". */
  .table-wrap>table th:nth-child(1),.table-wrap>table td:nth-child(1),
  .nested-table>table th:nth-child(1),.nested-table>table td:nth-child(1){width:64px;white-space:nowrap}
  .table-wrap>table th:nth-child(2),.table-wrap>table td:nth-child(2),
  .nested-table>table th:nth-child(2),.nested-table>table td:nth-child(2){width:88px}
  .table-wrap>table th:nth-child(3),.table-wrap>table td:nth-child(3),
  .nested-table>table th:nth-child(3),.nested-table>table td:nth-child(3){width:100px}
  /* col 4 (Description) intentionally has no width -> it absorbs the slack */
  .table-wrap>table th:nth-child(5),.table-wrap>table td:nth-child(5),
  .nested-table>table th:nth-child(5),.nested-table>table td:nth-child(5){width:86px}
  .table-wrap>table th:nth-child(6),.table-wrap>table td:nth-child(6),
  .nested-table>table th:nth-child(6),.nested-table>table td:nth-child(6){width:132px}
  /* FOLLOWING STEP is the last column: its "Step N" chip + row icons are centred, so
     centre the header over them and give the column enough width that the long tracked-caps
     label doesn't run up against the table's rounded right corner. */
  .table-wrap>table thead th:nth-child(6),.nested-table>table thead th:nth-child(6){text-align:center}
  .table-wrap>table th:nth-child(7),.table-wrap>table td:nth-child(7),
  .nested-table>table th:nth-child(7),.nested-table>table td:nth-child(7){width:140px}
  td{overflow-wrap:anywhere}
  .note-add .btn,.subpath-add .btn,[data-action="add-branch"],[data-action="add-note"]{white-space:nowrap}
  .role-chip,.step-type{max-width:100%}
}
.table-wrap{overflow-x:auto}

/* --- Toolbar: title left, actions tucked right, calmly wrapping --- */
.toolbar::after{display:none!important}
.toolbar{gap:14px!important}
.toolbar-actions{
  justify-content:flex-end!important;
  flex-wrap:wrap!important;
}

/* --- Subpath drill-in: breadcrumbs + sub-workflow bars --- */
.breadcrumbs{
  display:flex;flex-wrap:wrap;gap:4px;align-items:center;
  margin:0 0 14px;font-size:var(--fs-meta);color:var(--muted);
}
.breadcrumbs .crumb{
  background:transparent;border:0;color:var(--muted);cursor:pointer;
  padding:3px 7px;border-radius:var(--radius-chip);line-height:18px;
}
.breadcrumbs .crumb:hover{color:var(--text);background:rgba(255,255,255,.05)}
.breadcrumbs .crumb.current{color:var(--text);cursor:default}
.breadcrumbs .crumb.current:hover{background:transparent}
.breadcrumbs .crumb-sep{color:var(--muted2);opacity:.7}

.subpaths{display:grid;gap:8px;margin-top:12px}
.subpath-bar{
  display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;row-gap:8px;
  padding:10px 12px;
  border-radius:10px;background:var(--paper2);
}
/* ---- Decision outcomes panel (Beta 2.4 redesign) ---- */
.decision-outcomes{margin-top:10px;border-radius:10px;background:var(--paper2);overflow:hidden}
.decision-outcomes .do-head{font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);padding:8px 12px;border-bottom:1px solid var(--line)}
.decision-outcomes .do-hint{text-transform:none;letter-spacing:normal;font-size:11px}
.do-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;row-gap:8px;padding:9px 12px}
.do-row+.do-row{border-top:1px solid var(--line)}
.do-row.is-main{background:var(--fill)}
.do-if{flex:0 0 auto;color:var(--muted);font-size:14px;white-space:nowrap}
.do-ans{color:var(--text)}
.do-ans-yes{color:#7fce9c}.do-ans-no{color:#e6b980}
:root[data-mode="light"] .do-ans-yes{color:#1f7a4d}:root[data-mode="light"] .do-ans-no{color:#a9701a}
.do-arrow{flex:0 0 auto;color:var(--muted)}
.do-dest-wrap{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.do-dest{font-size:14px}
.do-main-dest b{color:var(--text)}
.do-sub-dest{background:none;border:0;color:var(--link,var(--text));cursor:pointer;padding:2px 0;display:inline-flex;align-items:center;gap:6px;font-size:14px;text-align:left}
.do-sub-dest .sp-caret{color:var(--muted)}
.do-ctl{flex:0 0 auto;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.do-main-badge{font-size:12px;padding:2px 9px;border-radius:var(--radius-chip);border:1px solid rgba(80,170,120,.4);background:rgba(60,150,100,.14);color:#9FD8B6;white-space:nowrap}
:root[data-mode="light"] .do-main-badge{color:#1f7a4d}
/* Decision "Main path" switch — the single control that picks which answer stays inline */
.do-switch{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 12px;border-bottom:1px solid var(--line);background:var(--paper2)}
.do-switch-label{font-size:12px;color:var(--muted)}
.do-seg{display:inline-flex;border-radius:8px;overflow:hidden;background:var(--color-control)}
.do-seg-btn{appearance:none;-webkit-appearance:none;border:0;background:transparent;color:var(--muted);font:inherit;font-size:14px;padding:5px 18px;cursor:pointer}
.do-seg-btn:last-child{border-right:0}
.do-seg-btn:hover{background:var(--fill);color:var(--text)}
.do-seg-btn.on{font-weight:600}
.do-seg-btn.do-seg-yes.on{background:rgba(60,150,100,.18);color:#9FD8B6}
.do-seg-btn.do-seg-no.on{background:rgba(200,140,60,.18);color:#e6b980}
:root[data-mode="light"] .do-seg-btn.do-seg-yes.on{color:#1f7a4d}
:root[data-mode="light"] .do-seg-btn.do-seg-no.on{color:#a9701a}
/* Concept A — a decision shows just the compact switch, then the shared subpath card(s) */
.decision-block{margin-top:10px;display:flex;flex-direction:column;gap:8px}
.dec-switch{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:12px}
.dec-switch-label{color:var(--muted)}
.dec-switch-hint,.dec-switch-read{display:inline-flex;align-items:center;gap:6px;color:var(--muted)}
.dec-add{margin-top:1px}
.se-route-note{font-size:12px;line-height:1.45;padding:2px 0 2px}
/* when the row can't fit (e.g. a nested subpath in a narrow main while the chat is open),
   the action buttons wrap to a second line instead of being clipped by main's overflow:clip */
.subpath-bar .sp-main{display:flex;align-items:center;gap:10px;min-width:0;flex:1 1 auto}
.subpath-bar .sp-tag{
  font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);
  border:1px solid rgba(255,255,255,.16);border-radius:var(--radius-chip);padding:3px 8px;white-space:nowrap;
}
.subpath-bar .sp-label{font-weight:var(--fw-control);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:0 1 auto}
.subpath-bar .sp-meta{color:var(--muted);font-size:var(--fs-meta);white-space:nowrap}
.subpath-bar .sp-actions{display:flex;gap:8px;flex:0 0 auto}
.subpath-empty{color:var(--muted);font-size:var(--fs-meta);margin-top:10px}

/* --- Micro-interactions --- */
.btn,label.btn,.nav-button,.section-title,.workflow-link,.subpath-bar .btn{
  transition:transform .05s ease, background .14s ease, border-color .14s ease, color .14s ease;
}
.btn:active,label.btn:active,.nav-button:active,.workflow-link:active{
  transform:translateY(1px);
}
@keyframes sopFlash{
  0%{background:rgba(255,255,255,.14)}
  100%{background:transparent}
}
.just-added{animation:sopFlash 1.15s ease-out}
.just-added>td{animation:sopFlash 1.15s ease-out}
.notice{transition:opacity .2s ease}
.notice.flash{animation:noticePop .26s ease-out}
@keyframes noticePop{
  0%{transform:translateY(-4px);opacity:.2}
  100%{transform:translateY(0);opacity:1}
}

/* --- Linked Following Step + Continue-Workflow icon --- */
.step-link{
  display:inline-flex;align-items:center;gap:2px;
  background:rgba(255,255,255,.04);border:1px solid var(--line);
  color:var(--text-soft);cursor:pointer;font:inherit;
  padding:4px 9px;border-radius:var(--radius-chip);line-height:18px;white-space:nowrap;
}
.step-link:hover{
  color:var(--text);border-color:rgba(255,255,255,.42);background:rgba(255,255,255,.08);
}
.next-end{
  display:inline-flex;align-items:center;
  padding:4px 9px;border:1px solid transparent;line-height:18px;
  color:var(--muted);font-size:inherit;white-space:nowrap;
}
.icon-link{
  background:transparent;border:0;padding:0;margin:0;cursor:pointer;
  display:inline-flex;border-radius:10px;
}
.icon-link .svg-icon{cursor:pointer;position:relative}
.icon-link:hover .svg-icon{
  outline:1px solid rgba(255,255,255,.55);outline-offset:1px;
  background:rgba(255,255,255,.10)!important;
}

/* --- Edit boxes: theme-derived surface so inputs follow Dark/Light (was hardcoded dark) --- */
select,
input[type=text],
input[type=password],
input[type=file],
textarea{
  background:var(--paper3)!important;
  color:var(--text)!important;
  border:1px solid var(--line2)!important;
}
select:hover,
input[type=text]:hover,
input[type=password]:hover,
input[type=file]:hover,
textarea:hover{border-color:color-mix(in srgb,var(--line2) 60%,var(--text) 28%)!important}
select:focus,
input[type=text]:focus,
input[type=password]:focus,
input[type=file]:focus,
textarea:focus{
  border-color:var(--color-focus)!important;
  background:var(--paper2)!important;
  outline:none;
}
input[type=color]{
  background:var(--paper3)!important;
  border:1px solid var(--line2)!important;
}
::placeholder{color:var(--muted2)!important}
.modal label{color:var(--text-soft)!important}

/* Native form controls (<select> boxes, date pickers, scrollbars, spinners) follow color-scheme,
   not our CSS background. Without it they inherit the OS preference (often dark) and render dark
   even in light mode — e.g. the Provider/Model selects in the AI settings modal. Tie it to the theme. */
:root{color-scheme:dark}
:root[data-mode="light"]{color-scheme:light}

/* --- Light-mode icon visibility: the per-icon --icon colours are light (tuned for a dark
   canvas), so the glyphs nearly vanish on a light page. Darken the glyph toward ink and use an
   opaque pale tint of the same hue, so icons read clearly while keeping their colour identity. --- */
:root[data-mode="light"] .svg-icon{
  /* fallback (browsers without relative-colour syntax): darken the glyph, pale-tint the chip */
  color:color-mix(in srgb,var(--icon) 58%,#11151b)!important;
  background:color-mix(in srgb,var(--icon) 22%,#ffffff)!important;
  border-color:color-mix(in srgb,var(--icon) 42%,#c4c9d1)!important;
  /* preferred: keep each icon's HUE, ease its saturation a touch (so a strong dark-mode colour
     isn't jarring on light), and retarget LIGHTNESS for a light background — reversible, since
     dark mode uses the raw colour untouched. */
  color:hsl(from var(--icon) h calc(s * 0.9) 36%)!important;
  background:hsl(from var(--icon) h calc(s * 0.72) 92%)!important;
  border-color:hsl(from var(--icon) h calc(s * 0.6) 75%)!important;
}

/* --- Light-mode button corrections: variant text + hovers assumed a dark canvas --- */
:root[data-mode="light"] .btn.warning{color:var(--text)}
:root[data-mode="light"] .btn.danger{color:var(--color-danger)}
:root[data-mode="light"] .btn.success,:root[data-mode="light"] .btn.warning{border-color:rgba(20,24,30,.22)}
:root[data-mode="light"] .btn:hover,:root[data-mode="light"] label.btn:hover,
:root[data-mode="light"] .btn.success:hover,:root[data-mode="light"] label.btn.success:hover,
:root[data-mode="light"] .btn.warning:hover,:root[data-mode="light"] label.btn.warning:hover{background:rgba(20,24,30,.05);border-color:rgba(20,24,30,.34);color:var(--text)}
:root[data-mode="light"] .btn.danger:hover,:root[data-mode="light"] label.btn.danger:hover{background:rgba(180,35,24,.10);border-color:rgba(180,35,24,.5);color:#8f1f17}
/* danger menu items (reset to baseline / erase all) — light pink was too faint on light */
:root[data-mode="light"] .menu-item.danger{color:var(--color-danger)!important}
:root[data-mode="light"] .menu-item.danger:hover{color:#8f1f17!important}
/* disabled buttons (e.g. Export/Clear log with 0 entries) were nearly invisible at .35 on light */
:root[data-mode="light"] .btn[disabled]{opacity:.5}

/* --- Light-mode Check page: severity/validation badges + pills used light (dark-canvas) text
   that vanished on the tinted backgrounds; give them dark, saturated hue text. --- */
:root[data-mode="light"] .v-badge.error,:root[data-mode="light"] .ai-sev.high,:root[data-mode="light"] .v-pill.error{color:#a51f15}
:root[data-mode="light"] .v-badge.warn,:root[data-mode="light"] .ai-sev.medium,:root[data-mode="light"] .v-pill.warn{color:#85610c}
:root[data-mode="light"] .ai-sev.low{color:var(--color-info)}
:root[data-mode="light"] .ai-cat{background:rgba(20,24,30,.05);border-color:var(--line2);color:var(--text-soft)}
:root[data-mode="light"] .ai-scope-btn:hover:not([disabled]){border-color:rgba(20,24,30,.32);background:rgba(20,24,30,.04)}
/* Cards/panels: a dark-skin rule forces a white-alpha border (invisible on light), so boxes had
   no visible edge against the page. Give them a real line + a touch of lift in light mode. */
:root[data-mode="light"] .card,:root[data-mode="light"] .page-card,
:root[data-mode="light"] .role-panel,:root[data-mode="light"] .legend-panel,
:root[data-mode="light"] .table-wrap,:root[data-mode="light"] .nested-table{border-color:var(--line2)!important}
:root[data-mode="light"] .page-card{box-shadow:0 1px 2px rgba(20,24,30,.04)}
/* Steps table: the subpath depth-bands and the role-filter highlight use hardcoded DARK fills
   (tuned for the dark canvas) that showed as black patches in light mode. Retint them light so the
   gray↔lighter inversion still reads, and darken the nesting rail so it stays visible on white. */
:root[data-mode="light"] .subpath-row>td::before{background:rgba(20,24,30,.05)}
:root[data-mode="light"] .subpath-row.sp-tint>td::before{background:rgba(20,24,30,.018)}
/* light twin: rgba(20,24,30,.18) is already the light linestrong value — tokened so the two
   themes cannot drift apart the next time either is touched. */
:root[data-mode="light"] .subpath-panel::before{background:var(--color-linestrong)}
/* g439 — the role-filter light override is gone: the highlight is `--sel-fill` now, which is
   defined in both modes and carries the brand in both, so there is nothing left to correct. */
/* Success / context-weight indicators used light (dark-canvas) colours that faded on light. */
:root[data-mode="light"] .ai-check-clean,:root[data-mode="light"] .ai-applied,
:root[data-mode="light"] .ai-key-detect.ok,:root[data-mode="light"] .ai-weight.w-light{color:#1f7a4d}
:root[data-mode="light"] .ai-weight.w-medium{color:#6b7320}
:root[data-mode="light"] .ai-weight.w-heavy{color:#85610c}
:root[data-mode="light"] .ai-weight.w-too-heavy{color:#a51f15}

/* --- Inline collapsible subpaths (expand in place, no separate page) --- */
.subpaths{display:grid;gap:8px;margin-top:12px}
.subpath-bar{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:9px 12px;
  border-radius:10px;background:var(--paper2);cursor:default;
  min-width:0;max-width:100%;overflow:hidden;
}
.subpath-bar.open{
  border-left-color:var(--line-state);
  background:var(--fill);
}
.sp-toggle{
  display:flex;align-items:center;gap:10px;flex:1;min-width:0;
  background:transparent;border:0;color:var(--text);cursor:pointer;font:inherit;text-align:left;padding:0;
}
.sp-caret{color:var(--muted);flex:0 0 auto;width:12px;font-size:11px;line-height:1}
.sp-tag{
  font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);
  border:1px solid rgba(255,255,255,.16);border-radius:var(--radius-chip);padding:3px 8px;white-space:nowrap;flex:0 0 auto;
}
.sp-label{font-weight:var(--fw-control);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:0 1 auto}
.sp-meta{color:var(--muted);font-size:var(--fs-meta);white-space:nowrap;flex:0 0 auto}
/* Yes/No decision route badge (metadata, never in step text) */
/* 11px -> 12px: "Yes"/"No" are lowercase-bearing, so 11 is under the floor.
   700 -> 600: Figtree is self-hosted at 300/400/500/600 ONLY, so 700 was never loaded and the
   browser was synthesising a faux bold. */
.route-badge{display:inline-flex;align-items:center;font-size:12px;font-weight:600;line-height:1;letter-spacing:.02em;padding:3px 8px;border-radius:var(--radius-chip);border:1px solid transparent;white-space:nowrap;flex:0 0 auto;vertical-align:middle}
.next-cell .route-badge{margin-right:6px}
.step-route{margin-right:7px}.step-route .route-badge{vertical-align:middle}
.route-badge.rb-yes{background:rgba(70,185,138,.16);color:var(--color-success);border-color:rgba(70,185,138,.42)}
/* g447 — #CC6B6B measured 4.09:1 against its own tinted ground, under the 4.5 floor for 12px
   text, while its rb-yes twin passed at 4.85. The pair was not even consistent with itself.
   #D67F7F is 5.01 — the smallest shift along the same hue that clears the floor and puts the two
   badges on comparable footing. Found by a contrast sweep during g445, logged there, fixed here. */
.route-badge.rb-no{background:rgba(204,107,107,.16);color:#D67F7F;border-color:rgba(204,107,107,.42)}
:root[data-mode="light"] .route-badge.rb-yes{color:#1f7a55;background:rgba(70,185,138,.15);border-color:rgba(70,185,138,.5)}
:root[data-mode="light"] .route-badge.rb-no{color:#b3403f;background:rgba(204,107,107,.15);border-color:rgba(204,107,107,.5)}
.sp-actions{display:flex;gap:8px;flex:0 0 auto}

.subpath-row>td{padding:0!important;background:transparent;border-bottom:1px solid var(--line)}
.subpath-panel{
  margin:0;padding:10px 0 2px 24px;
  border-left:0;
  background:transparent;
  position:relative;z-index:1;
}
.subpath-panel-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.subpath-panel-head .sp-rename{flex:0 0 auto}
.subpath-add{margin:14px 0 12px}
.subpath-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:14px 0 12px}
.subpath-foot .btn{flex:0 0 auto;width:auto}
/* Rename (head) and Done (foot) sit in a slot centred over the Following-Step column on desktop. */
.sp-col-slot{display:flex;justify-content:center;align-items:center;flex:0 0 auto}
@media(min-width:721px){.sp-col-slot{width:116px;flex:0 0 116px}}
.subpath-panel .nested-table{background:transparent;border:0;border-radius:0;overflow:visible}
/* Expansion grows downward out of the subpath bar */
@keyframes subpathExpand{
  0%{opacity:0;transform:translateY(-8px);clip-path:inset(0 0 100% 0)}
  100%{opacity:1;transform:translateY(0);clip-path:inset(0 0 0 0)}
}
.subpath-panel.sp-animate{
  animation:subpathExpand .30s cubic-bezier(.22,.61,.36,1);
  transform-origin:top center;
}
.subpath-bar .sp-caret{display:inline-block;transition:transform .2s ease}

/* --- Menu (sandwich) button: identical in every state + form factor --- */
.toc-toggle,.mobile-toc-toggle,.chat-toggle{
  width:34px!important;height:34px!important;min-width:34px!important;
  border:1px solid rgba(255,255,255,.14)!important;
  background:transparent!important;
  border-radius:8px!important;
  color:var(--text)!important;
}
.toc-toggle:hover,.mobile-toc-toggle:hover,.chat-toggle:hover{
  border-color:rgba(255,255,255,.30)!important;
  background:rgba(255,255,255,.05)!important;
}
.toc-toggle:focus,.mobile-toc-toggle:focus,.chat-toggle:focus,
.toc-toggle:focus-visible,.mobile-toc-toggle:focus-visible,.chat-toggle:focus-visible{
  outline:none!important;
  border-color:rgba(255,255,255,.45)!important;
}
/* collapsed must look exactly like open — no brighter/larger border */
.app-shell.toc-collapsed .toc-toggle{
  width:34px!important;height:34px!important;min-width:34px!important;
  border:1px solid rgba(255,255,255,.14)!important;
  background:transparent!important;
}
@media(max-width:720px){
  .toc-toggle,.mobile-toc-toggle{width:40px!important;height:40px!important;min-width:40px!important}
}

/* --- Editable per-step notes --- */
.note-box{
  display:flex;align-items:flex-start;justify-content:space-between;gap:10px;
}
.note-box .note-text{min-width:0;overflow-wrap:anywhere;white-space:pre-line}
.note-actions{display:flex;gap:6px;flex:0 0 auto}
.note-add{margin-top:8px}

/* --- Legend / Structure items: never collapse the label in edit mode --- */
/* Responsive columns so panels stay roomy instead of squeezing to ~200px. */
.grid-3{grid-template-columns:repeat(auto-fit,minmax(258px,1fr))!important}
/* Flex layout: when content + actions don't fit, actions wrap to the next line
   instead of crushing the label cell to zero width (which caused overlap). */
.item{
  display:flex!important;
  flex-wrap:wrap;
  align-items:center;
  justify-content:space-between;
  gap:10px 14px;
}
.item>*:first-child{flex:1 1 200px;min-width:0}
.item .actions{flex:0 1 auto;min-width:0;justify-content:flex-end}
.item .item-meta,
.item .item-title{min-width:0;overflow-wrap:anywhere}
.icon-label-row{min-width:0}
.icon-label-row>span:last-child{min-width:0;overflow-wrap:anywhere}
/* Chips/badges keep their shape; their own text never wraps past the bubble. */
.legend-panel .role-chip,
.legend-panel .step-type{max-width:100%}

/* ============================================================
   V48 PRINT / PDF — clean A4 that mirrors the on-screen workflow
   ============================================================ */
/* Top/bottom margin is deliberately 8mm. Chrome only paints its built-in print header/footer
   (the serif date / document-title / URL / page-number) when the page's top/bottom margin is large
   enough to fit them (~10mm+). Keeping top/bottom at 8mm sits below that threshold, so Chrome omits
   them and the export is clean by default — no need for the user to untick "Headers and footers" in
   the print dialog. DO NOT raise the top/bottom margin to ~10mm or more or the serif header/footer
   reappears. Side margins are unaffected by the header/footer and stay generous (14mm). */
@page{size:A4;margin:8mm 14mm}
@media print{
  /* AXD-I light document palette (explicit neutrals — print has no data-mode, so
     tokens can't be used here; these mirror the light-theme --color-* values). */
  html,body{background:#fff!important;color:#141414!important}
  body{font-size:9pt!important;line-height:1.4!important;-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
  /* hide all chrome + editing affordances */
  .sidebar,.toolbar,.toc-backdrop,.head-actions,.actions,.filter-bar,.role-panel,
  #notice,.subpath-add,.note-add,.note-actions,.sp-actions,.subpath-bar .sp-actions{display:none!important}
  .app-shell,.app-shell.toc-collapsed{display:block!important}
  .main,.app-shell.toc-collapsed .main{max-width:none!important;padding:0!important;overflow:visible!important}
  .card,.page-card{border:0!important;box-shadow:none!important;background:#fff!important;color:#141414!important;padding:0!important;margin:0!important}
  /* workflow header as the document title */
  .workflow-header{border-bottom:1.5pt solid #141414!important;margin:0 0 12pt!important;padding:0 0 8pt!important}
  .workflow-header h2{color:#141414!important;font-size:17pt!important;font-weight:600!important;line-height:1.2!important}
  .kicker{color:#5F5F5F!important;font-size:8.5pt!important;letter-spacing:.02em!important}
  /* tables */
  .table-wrap,.nested-table{border:0.75pt solid #D4D4D4!important;border-radius:0!important;overflow:visible!important}
  table{min-width:0!important;width:100%!important;table-layout:auto!important;border-collapse:collapse!important;font-size:8.4pt!important}
  thead{display:table-header-group!important}
  th{background:#F0F0F0!important;color:#141414!important;font-size:7.6pt!important;letter-spacing:.04em!important;border-bottom:0.75pt solid #D4D4D4!important}
  td,th{border:0.5pt solid #E2E2E2!important;padding:4pt 6pt!important;color:#141414!important;vertical-align:top!important}
  tr,.step-row,.subpath-row{break-inside:avoid!important;page-break-inside:avoid!important}
  /* keep the coloured badges, but force readable text on white */
  .role-chip{border:0.5pt solid rgba(0,0,0,.18)!important;box-shadow:none!important}
  .step-type{color:#141414!important;border-width:0.75pt!important}
  /* The icon chip carries a fixed 10px radius — fine on the 32px UI chip (rounded square), but in
     print the chip shrinks to ~20px where 10px == half the box == a full circle. Use a smaller radius
     so it stays the square-ish shape it has in the builder UI. */
  .svg-icon{color:#5F5F5F!important;background:#ECECEC!important;border:0.5pt solid #D4D4D4!important;border-radius:5px!important}
  .svg-icon svg{stroke-width:2!important}
  .step-link{background:none!important;border:0!important;color:#141414!important;padding:0!important;text-decoration:underline!important}
  .next-end{color:#5F5F5F!important}
  /* subpaths: visible, indented, light */
  .subpath-bar{background:#FAFAFA!important;border:0.5pt solid #D4D4D4!important;border-left:2pt solid #9A9A9A!important}
  .sp-toggle{color:#141414!important}
  .sp-caret{display:none!important}
  .sp-tag{color:#5F5F5F!important;border:0.5pt solid #D4D4D4!important}
  .subpath-row>td{border:0!important}
  .subpath-panel{background:#FAFAFA!important;border-left:2pt solid #9A9A9A!important;padding:8pt 10pt!important}
  .note-box{background:#FAFAFA!important;border:0.5pt solid #D4D4D4!important;color:#333333!important}
  .note-box .note-text strong{color:#141414!important}
}

/* --- Data dropdown menu (tucks away export + destructive ops) --- */
.toolbar{overflow:visible!important}
.menu{position:relative;display:inline-block}
.menu>summary{list-style:none;cursor:pointer;user-select:none}
.menu>summary::-webkit-details-marker{display:none}
.menu>summary::marker{content:""}
.menu[open]>summary{border-color:rgba(255,255,255,.42)!important;background:rgba(255,255,255,.05)!important}
.menu-pop{
  position:absolute;right:0;top:calc(100% + 6px);z-index:130;min-width:264px;
  width:min(360px,calc(100vw - 24px));max-width:min(360px,calc(100vw - 24px));
  max-height:calc(100vh - 96px);max-height:calc(100dvh - 96px);overflow-y:auto;overscroll-behavior:contain;
  background:var(--color-overlay);border:1px solid var(--color-linestrong);border-radius:12px;padding:7px;
  display:flex;flex-direction:column;gap:2px;box-shadow:0 14px 34px rgba(0,0,0,.55);
}
.menu-group{border-radius:8px;margin-top:6px}
.menu-pop>.menu-group:first-of-type{margin-top:0}
/* Items nest under their group header (headers sit flush-left, items indented). */
.menu-group .menu-item{padding-left:22px!important}
.menu-group>summary{list-style:none;cursor:pointer;user-select:none}
.menu-group>summary::-webkit-details-marker{display:none}
.menu-group>summary::marker{content:""}
.menu-head{font-size:12px;line-height:15px;font-weight:var(--fw-control);letter-spacing:.11em;text-transform:uppercase;color:var(--muted2);padding:8px 10px 4px}
.menu-group>.menu-head{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  font-size:12px;line-height:15px;font-weight:var(--fw-control);letter-spacing:.11em;text-transform:uppercase;color:var(--muted2);
  padding:9px 10px;border:1px solid transparent;border-radius:8px;
}
.menu-group>.menu-head:hover{background:var(--hover);border-color:var(--line)}
.menu-group[open]>.menu-head{color:var(--ink);background:rgba(255,255,255,.04);border-color:var(--line)}
.menu-group>.menu-head .caret{font-size:16px;line-height:1;transform-origin:center}
.menu-group[open]>.menu-head .caret{transform:rotate(90deg)}
.menu-item{
  justify-content:flex-start!important;text-align:left!important;width:100%;
  height:auto!important;min-height:38px!important;padding:8px 10px!important;line-height:20px;white-space:nowrap;
  border:1px solid transparent!important;background:transparent!important;
}
.menu-item:hover{background:var(--hover)!important;border-color:var(--line)!important}
.menu-item.danger{color:#F1C4C0!important}
.menu-item.danger:hover{background:rgba(180,35,24,.16)!important;border-color:rgba(180,35,24,.5)!important}
.menu-sep{height:1px;background:var(--line);margin:7px 4px;flex-shrink:0}
.menu-pop>*{flex-shrink:0}
/* The "View" menu group (Workflow View toggle + Undo/Redo) only exists to compact the
   phone toolbar — hidden everywhere except the ≤720px phone layout (shown there + the
   in-bar copies hidden; see the @media(max-width:720px) block). */
.menu-view{display:none}
@media(max-width:720px){ .menu-pop{right:0;left:auto;min-width:240px} }


/* ============================================================
   V49 — VALIDATION VIEW + UNIFIED UI POLISH
   ============================================================ */

/* ---- Consolidated type scale (fewer, consistent sizes) ---- */
:root{
  --fs-label-sm:11px;   /* uppercase micro labels: th, tags, badges */
  --fs-meta:12px;         /* secondary text: meta, kicker, notes */
  --fs-body:var(--text-body-axd);         /* body / table / controls */
  --fs-body-lg:16px;
  --fs-title:24px;        /* page heading */
}
.toolbar-title{font-size:16px!important;line-height:24px!important;font-weight:500!important}
.workflow-header h2,.legend-top h2{font-size:16px!important;line-height:30px!important}
.role-panel h3,.legend-panel h3{font-size:var(--text-section)!important;line-height:24px!important}
.manager-note,.item-meta,.filter-bar,.v-loc{font-size:var(--text-body-axd)!important}
table{font-size:16px!important}
th{font-size:12px!important;letter-spacing:.07em!important}
.role-chip,.step-type,.sp-meta{font-size:14px!important}
.sp-tag,.v-badge{font-size:11px!important}
.btn,label.btn{font-size:14px!important}
.btn.small{font-size:var(--text-button)!important}
td{line-height:1.5!important}

/* ---- Smooth, unified sidebar collapse ---- */
.app-shell{transition:grid-template-columns .28s cubic-bezier(.4,0,.2,1)}
@media(min-width:1101px){
  .sidebar{transition:width .28s cubic-bezier(.4,0,.2,1),padding .28s ease!important}
  /* margin-left must transition too — opening the chat shifts .main via margin-left, and
     without it here (this rule overrides the base .main transition) the chat-open snaps
     instead of sliding like the Contents toggle (which only changes max-width). */
  .main{transition:max-width .28s cubic-bezier(.4,0,.2,1),margin-left .28s cubic-bezier(.4,0,.2,1)}
}
.toc-toggle,.mobile-toc-toggle{transition:background .15s ease,border-color .15s ease,transform .05s ease}

/* ---- Sidebar = the app frame: sits on canvas with a hairline edge (rulebook §6 chrome) ---- */
.sidebar{background:var(--bg)!important;border-right:1px solid var(--line)!important}
/* Phone/tablet: the TOC becomes a floating sheet menu -> lift it to the overlay tier
   (--color-overlay: #212121 dark / #FFF light) + a linestrong hairline, over the scrim. */
@media(max-width:1100px){
  /* The drawer sits one tone under the overlay tier: the overlay read noticeably lighter than
     the true-black page behind it. --color-surface2 is the agreed value: one rung off the canvas,
     still separated by the hairline, without the modal-style lift. Modals, popovers and menus keep
     --color-overlay. (Hexes intentionally not quoted here — the v1.7 ladder moved them once
     already; the token names are the contract.) */
  .app-shell .sidebar{background:var(--color-surface2)!important;border-right:1px solid var(--color-linestrong)!important}
}
.sidebar-body{padding-bottom:30px!important}
.nav-actions{margin:6px 0 14px!important;gap:7px!important}
.nav-button{border-radius:9px!important;height:38px!important;min-height:38px!important;font-size:14px!important}
/* The margin lives on .search-wrap, not .search. The wrap is the icon's positioning
   context, so a margin on the FIELD made the wrap 54px tall around a 36px field and
   `top:50%` centred the magnifier on the wrap — 5px below the field's centre
   (measured 15.5px of air above it, 5.5px below). */
.search{height:46px!important;min-height:46px!important;flex:0 0 auto!important;margin:0!important}
.search-wrap{margin:4px 0 14px!important;flex:0 0 auto!important}
/* On phones the search must be a comfortable tap target — taller than the nav buttons.
   Placed after the global override so it wins the equal-specificity !important tie. */
@media(max-width:720px){
  .search{height:50px!important;min-height:50px!important}
  .nav-button{height:44px!important;min-height:44px!important}
  /* Contents drawer fills the screen like the Axi panel; overrides the scattered 330/360 widths. */
  .app-shell:not(.toc-collapsed) .sidebar{width:100vw!important;max-width:100vw!important}
}
.nav-badge{display:inline-flex;align-items:center;justify-content:center;min-width:17px;height:17px;padding:0 5px;border-radius:var(--radius-chip);background:var(--error);color:#fff;font-size:11px;font-weight:600;margin-left:5px;vertical-align:middle}

/* ---- TOC: each section is separated by an eyebrow title + a top hairline + spacing
       (NOT a box) — identical in both themes. See the twin rule below (~L2038). ---- */
.section-block{
  border:0!important;
  border-top:1px solid var(--color-linestrong)!important;
  background:transparent!important;
  border-radius:0!important;
  /* g437 — 16 -> 20. Owner asked for a little more room between sections, and now that the
     selected row paints a FULL-height box the group boundary has to stay louder than the gaps
     inside the group. 20 is the next rung on §5's ramp, and it keeps invariant 5 comfortably:
     20 plus the hairline between groups, against 2 between the rows within one. */
  padding:20px 4px 4px!important;
  margin:20px 0 0!important;
}
.section-title{
  height:auto!important;min-height:32px!important;
  font-size:14px!important;letter-spacing:.02em!important;
  color:var(--muted)!important;font-weight:500!important;
  padding:7px 8px 7px!important;white-space:normal!important;align-items:flex-start!important;
}
.section-title span{white-space:normal!important;line-height:1.35!important}
.workflow-link{
  /* border-radius dropped: a later top-level `.workflow-link{…!important}` restates it */
  min-height:36px!important;height:auto!important;
  padding:7px 10px!important;gap:10px!important;
  font-size:14px!important;line-height:1.35!important;color:var(--text-soft)!important;
}
/* Hover was rgba(255,255,255,.05) — a value tuned when the rail still sat on surface2, where it
   composited to ~#242424 and read fine. g327 dropped the rail to the true-black canvas and the
   same fill now lands at ~#0d0d0d: a 5% veil over #000 is very nearly nothing, which is why the
   selected row stayed obvious while hover went quiet. Tone is the fix, not opacity — hover takes
   the `surface` rung so the rail reads canvas < hover < selection, each a real step on the
   ladder (#000 → #121212 → #171717). Owner call 2026-08-14. */
.workflow-link:hover{background:var(--color-surface)!important;color:var(--text)!important}
.workflow-link.active{background:var(--accent-muted)!important;box-shadow:none!important;border:1px solid rgba(255,255,255,.12)!important;color:var(--text)!important}
.workflow-number{color:var(--muted2)!important;font-weight:500!important;min-width:34px!important}
.workflow-link.active .workflow-number{color:var(--text)!important}

/* ---- Softer toolbar + cards (less sharp, more unified) ---- */
.toolbar{border-bottom:1px solid rgba(255,255,255,.05)!important}
.card,.page-card{border-color:rgba(255,255,255,.06)!important}
.role-panel,.legend-panel{border-color:rgba(255,255,255,.06)!important}
.table-wrap{border-color:rgba(255,255,255,.07)!important}

/* ---- Validation view ---- */
.v-summary{display:flex;gap:8px;flex-wrap:wrap}
.v-pill{font-size:12px;padding:5px 12px;border-radius:var(--radius-chip);border:1px solid var(--line);white-space:nowrap}
.v-pill.error{color:#F1C4C0;border-color:rgba(180,35,24,.5);background:rgba(180,35,24,.13)}
.v-pill.warn{color:#E8D3A8;border-color:rgba(197,179,88,.42);background:rgba(150,120,40,.13)}
.v-pill.ok{color:var(--muted)}
.validate-list{display:flex;flex-direction:column;gap:7px;margin-top:18px}
.validate-row{
  display:flex;align-items:flex-start;gap:12px;width:100%;text-align:left;
  background:var(--paper2);border-radius:11px;
  padding:11px 14px;color:var(--text-soft);font-size:14px;
  transition:border-color .14s ease,background .14s ease;
}
.validate-row:hover{border-color:rgba(255,255,255,.32);background:rgba(255,255,255,.04)}
.validate-row.error{border-left:3px solid var(--error)}
.validate-row.warn{border-left:3px solid rgba(197,179,88,.6)}
/* the click-to-jump area sits inside the row; "Fix with Axi" is a sibling so it isn't a nested button */
.validate-jump{flex:1 1 auto;display:flex;align-items:flex-start;gap:12px;min-width:0;background:none;border:0;margin:0;padding:0;color:inherit;font:inherit;text-align:left;cursor:pointer;transition:transform .05s ease}
.validate-jump:active{transform:translateY(1px)}
.validate-jump[disabled]{cursor:default;opacity:.85}
.validate-fix{flex:0 0 auto;align-self:center;white-space:nowrap}
.v-badge{flex:0 0 auto;letter-spacing:.12em;text-transform:uppercase;padding:3px 9px;border-radius:var(--radius-chip);font-weight:500}
.v-badge.error{background:rgba(180,35,24,.18);color:#F1C4C0}
.v-badge.warn{background:rgba(150,120,40,.20);color:#E8D3A8}
.v-msg{flex:1 1 auto;min-width:0;overflow-wrap:anywhere;word-break:break-word;line-height:1.4}
.v-loc{flex:0 0 auto;color:var(--muted);white-space:normal;overflow-wrap:anywhere;line-height:1.4}
.v-clean{margin-top:18px;padding:26px;text-align:center;color:#CDE6D8;background:rgba(47,74,67,.18);border:1px solid rgba(123,170,150,.32);border-radius:12px;font-size:16px}

@media(max-width:720px){
  .validate-row{flex-wrap:wrap;gap:8px}
  .validate-jump{width:100%;flex-wrap:wrap;gap:8px}
  .validate-fix{width:100%;justify-content:center}
  .v-loc{width:100%}
  /* long workflow titles truncate with an ellipsis; full title shows on hover + in the header */
  .workflow-link{align-items:center!important}
  .workflow-label{white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important}
}

/* SOP Analysis report (Beta 2.6 Task C) */
.analysis-report{max-width:1180px;margin:0 auto;overflow:hidden}
.analysis-report *{box-sizing:border-box}
.analysis-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:18px}
.analysis-head h2{color:var(--text);margin:0 0 4px;font-size:var(--fs-title);font-weight:var(--fw-title);line-height:30px}
.analysis-head .btn{flex:0 0 auto}
.analysis-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex:0 0 auto;flex-wrap:wrap}
.analysis-actions .btn{flex:0 0 auto}
.analysis-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 16px}
.analysis-metrics>div{min-width:0;padding:12px 14px;border-radius:var(--radius-md);background:var(--paper2)}
.analysis-metrics span{display:block;font-size:12px;line-height:1.4;color:var(--muted);margin-bottom:4px}
.analysis-metrics b{display:block;font-size:16px;line-height:1.15;color:var(--text-strong,#fff);font-weight:var(--fw-title)}
.analysis-note,.analysis-empty{border:1px solid rgba(197,179,88,.38);background:rgba(150,120,40,.12);border-radius:var(--radius-md);padding:12px 14px;color:var(--text-soft);font-size:14px;line-height:1.55;margin:0 0 16px}
.analysis-empty{margin-top:8px;text-align:left}
.analysis-empty h3{margin:0 0 6px;color:var(--text);font-size:16px;line-height:24px}
.analysis-empty p{margin:0;color:var(--muted)}
.analysis-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:14px}
.analysis-panel,.analysis-insight{min-width:0;border-radius:var(--radius-md);background:var(--paper2);padding:14px}
.analysis-role-panel,.analysis-insight{grid-column:1/-1}
.analysis-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}
.analysis-panel-head h3{margin:0;color:var(--text);font-size:var(--text-section);font-weight:var(--weight-section);line-height:24px}
.analysis-panel-head p{margin:2px 0 0;color:var(--muted);font-size:12px;line-height:1.45}
.analysis-donut-grid{display:grid;grid-template-columns:minmax(170px,230px) minmax(0,1fr);gap:18px;align-items:center}
.analysis-donut{display:block;width:100%;max-width:230px;height:auto;color:var(--text);overflow:visible}
.analysis-donut-bg{stroke:var(--line)}
.analysis-donut-seg{filter:drop-shadow(0 0 0 transparent)}
.analysis-donut-num{fill:var(--text-strong,#fff);font-size:28px;font-weight:700}
.analysis-donut-label{fill:var(--muted);font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}
.analysis-ranked-list,.analysis-bars{display:flex;flex-direction:column;gap:8px;min-width:0}
.analysis-rank-row{display:grid;grid-template-columns:28px 12px minmax(0,1fr) 48px 86px;gap:8px;align-items:center;min-width:0;padding:8px 0;border-bottom:1px solid var(--line)}
.analysis-rank-row:last-child{border-bottom:0}
.analysis-rank-num{color:var(--muted2);font-size:12px;font-weight:700;text-align:right}
.analysis-role-dot{width:10px;height:10px;border-radius:var(--radius-full);box-shadow:0 0 0 2px rgba(255,255,255,.06)}
.analysis-rank-role{min-width:0;display:flex;flex-direction:column;gap:1px}
.analysis-rank-role b{color:var(--text);font-size:14px;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.analysis-rank-role span{color:var(--muted);font-size:12px;line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.analysis-rank-val{font-weight:700;color:var(--text);text-align:right}
.analysis-rank-sub{font-size:12px;color:var(--muted);text-align:right;white-space:nowrap}
.analysis-bar-row{display:grid;grid-template-columns:26px minmax(130px,240px) minmax(120px,1fr) minmax(0,auto);gap:12px;align-items:center;min-width:0;padding:9px 0;border-bottom:1px solid var(--line)}
.analysis-bar-row:last-child{border-bottom:0}
.analysis-bar-rank{font-size:12px;font-weight:700;color:var(--muted2);text-align:right}
.analysis-bar-role{display:flex;align-items:center;gap:8px;min-width:0;color:var(--muted);font-size:12px}
.analysis-bar-role .role-chip{flex:0 0 auto;max-width:100%}
.analysis-bar-role>span:last-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Pill-shaped usage bar (was an SVG rect with preserveAspectRatio:none, which stretched
   the corner radius into an uneven, sharp shape). Rounded track + rounded fill with a
   min-width so a tiny value reads as a small rounded nub, not a sharp sliver. */
.analysis-mini-bar{position:relative;width:100%;height:11px;min-width:0;border-radius:var(--radius-chip);background:var(--fill);overflow:hidden}
.analysis-mini-fill{height:100%;min-width:11px;max-width:100%;border-radius:var(--radius-chip)}
.analysis-bar-val{min-width:0;text-align:right}
.analysis-bar-val b{display:block;color:var(--text);font-size:14px;line-height:1.2;white-space:nowrap}
.analysis-bar-val span{display:block;color:var(--muted);font-size:12px;line-height:1.35;white-space:nowrap}
.analysis-narrative{margin:0 0 12px;color:var(--text-soft);font-size:14px;line-height:1.6}
.analysis-flag{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-top:8px;padding:10px 12px;border-left:3px solid var(--color-line);border-radius:var(--radius-sm);background:rgba(255,255,255,.025)}
.analysis-flag.high{border-left-color:var(--error)}
.analysis-flag.med{border-left-color:rgba(197,179,88,.75)}
.analysis-flag.low{border-left-color:rgba(131,156,188,.75)}
.analysis-flag b{display:block;color:var(--text);font-size:14px;line-height:1.3;margin:2px 0}
.analysis-flag p{margin:0;color:var(--muted);font-size:14px;line-height:1.45}
.analysis-flag-sev{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--radius-chip);border:1px solid var(--line);color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}
.analysis-flag .btn{flex:0 0 auto}
.analysis-insight h4{margin:14px 0 7px;color:var(--text);font-size:14px;line-height:20px}
.analysis-recs{margin:0;padding-left:22px;color:var(--text-soft);font-size:14px;line-height:1.55}
.analysis-recs li{margin:5px 0}

@media(max-width:1100px){ .analysis-layout{grid-template-columns:1fr} }
@media(max-width:900px){
  .analysis-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}
  .analysis-layout{grid-template-columns:1fr}
  .analysis-role-panel,.analysis-insight{grid-column:auto}
  .analysis-donut-grid{grid-template-columns:1fr}
  .analysis-donut{justify-self:center;max-width:210px}
}
@media(max-width:640px){
  .analysis-head{flex-direction:column;align-items:stretch}
  .analysis-head .btn{width:100%}
  .analysis-actions{width:100%;justify-content:stretch}
  .analysis-metrics{grid-template-columns:1fr 1fr;gap:8px}
  .analysis-metrics>div{padding:10px}
  .analysis-metrics b{font-size:16px}
  .analysis-panel,.analysis-insight{padding:12px}
  .analysis-rank-row{grid-template-columns:24px 10px minmax(0,1fr) 42px;gap:7px}
  .analysis-rank-sub{grid-column:3/-1;text-align:left}
  .analysis-bar-row{grid-template-columns:24px minmax(0,1fr) minmax(98px,auto);gap:8px}
  .analysis-mini-bar{grid-column:2/-1}
  .analysis-bar-val{text-align:right}
  .analysis-flag{flex-direction:column}
  .analysis-flag .btn{width:100%}
}
@media(max-width:380px){
  .analysis-metrics{grid-template-columns:1fr}
  .analysis-bar-row{grid-template-columns:22px minmax(0,1fr)}
  .analysis-bar-val{grid-column:2/-1;text-align:left}
  .analysis-mini-bar{grid-column:2/-1}
}


/* ============================================================
   V49b — de-box / unify corners + move animation + mobile menu
   ============================================================ */

/* Workflow page: drop the redundant outer box so the role panel and
   the table are the only containers (no box-in-a-box behind them). */
.wf-view{background:transparent!important;border:0!important;box-shadow:none!important;padding:0!important;margin-bottom:0!important}

/* TOC section separation = eyebrow title + top hairline + spacing, no box (consistent in
   both themes; the light-only tint that used to sit here was dropped for parity). */
.section-block{
  border:0!important;
  border-top:1px solid var(--color-linestrong)!important;
  background:transparent!important;
  border-radius:0!important;
  /* g437 — 16 -> 20. Owner asked for a little more room between sections, and now that the
     selected row paints a FULL-height box the group boundary has to stay louder than the gaps
     inside the group. 20 is the next rung on §5's ramp, and it keeps invariant 5 comfortably:
     20 plus the hairline between groups, against 2 between the rows within one. */
  padding:20px 4px 4px!important;
  margin:20px 0 0!important;
}
.workflow-link.active{border:0!important;background:var(--accent)!important}

/* Consistent corner radius across the main containers */
.role-panel,.legend-panel,.table-wrap,.nested-table,.page-card,.card,.modal,.notice,
.subpath-bar,.subpath-panel,.item,.validate-row,.menu-pop,.v-clean,.search{
  border-radius:13px!important;
}
.note-box{border-radius:var(--radius-control)!important}

/* Move (arrow-reorder) animation: the moved row settles with a soft highlight
   after sliding (the slide itself is done in JS via a FLIP transform). */
@keyframes movedSettle{0%{background:rgba(255,255,255,.10)}100%{background:transparent}}
.moved-settle{animation:movedSettle .9s ease-out}
.moved-settle>td{animation:movedSettle .9s ease-out}

/* Mobile: keep Settings anchored to its button, matching the desktop right-aligned dropdown,
   while capping height below the measured toolbar. */
@media(max-width:720px){
  .menu{position:relative!important}
  .menu-pop{
    position:absolute!important;top:calc(100% + 6px)!important;left:auto!important;right:0!important;
    min-width:240px!important;width:min(360px,calc(100vw - 24px))!important;max-width:min(360px,calc(100vw - 24px))!important;max-height:calc(100vh - var(--bar-h,60px) - 16px)!important;max-height:calc(100dvh - var(--bar-h,60px) - 16px)!important;overflow:auto!important;overscroll-behavior:contain!important;
  }
}

/* Desktop TOC: drop the dark "housed panel" background and hard border so the
   menu button and the rounded section cards float on the page background — no
   sharp dark slab at the bottom of the list, and the collapsed rail matches the
   toolbar buttons. Mobile keeps the dark drawer (rule above, outside this query). */
@media(min-width:1101px){
  .sidebar{
    background:transparent!important;
    /* 1px separator that matches the chat panel's border-right and animates with the
       sidebar width (so it slides with the sandwich toggle instead of snapping). */
    border-right:1px solid var(--line)!important;
  }
  /* nothing open (collapsed rail + chat closed) → drop the separator so the rail just floats */
  .app-shell.toc-collapsed:not(.chat-open) .sidebar{border-right-color:transparent!important}
}

/* Subpath visibility controls above the steps table */
/* 24 above, 12 below. It measured 0 above and 12 below — glued to the action bar and floating
   free of the table. Backwards: this selector FILTERS the table, so it belongs to the table.
   A 2:1 ratio groups it downward and gives the action bar the clear separation it needs. */
.subpath-controls{display:flex;align-items:center;gap:8px;margin:24px 0 12px}
.subpath-controls .spc-label{font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-right:2px}

/* Subpath tone alternates by nesting depth: odd levels get a soft light tint
   (bar + its expanded panel share the tone so they read as one unit), even
   levels fall back to the dark page background. Indentation + this make depth clear. */
.subpath-bar{background:transparent}
.subpath-bar.sp-tint{background:rgba(255,255,255,.05)}
.subpath-bar.open{background:rgba(255,255,255,.028)}
.subpath-bar.sp-tint.open{background:rgba(255,255,255,.07)}
/* Tone lives on the full-width row band (not a floating block) so it fills
   edge-to-edge with no dark corners. Odd depth = light, even depth = dark;
   deeper levels are inset by indentation, giving the light/dark inversion. */
.subpath-panel{background:transparent!important;border-left:0!important}
/* Each level's content is indented (panel padding-left, accumulates via nesting),
   but the band background is painted full-width by a pseudo-element that extends
   back to the left edge by the accumulated indent (24px × ancestor depth). So the
   band reaches edge-to-edge at every depth — indented content, no inset box/corners.
   Even depth = explicit dark (#121212), odd = light; transparent would reveal the
   light parent and break the gray↔black inversion. */
.subpath-row>td{background:transparent!important;position:relative}
.subpath-row>td::before{content:"";position:absolute;top:0;bottom:0;right:0;left:calc(-24px * (var(--d,1) - 1));background:#121212;z-index:0}
.subpath-row.sp-tint>td::before{background:rgba(255,255,255,.04)}
/* Nesting rail: a soft, rounded vertical guide running the full height of each
   subpath block (one per depth, sitting in that level's indent gutter). It bounds
   the block top-to-bottom — so a nested subpath stays distinct from the main path
   even when its band inverts to the same color. Works on gray and black bands. */
/* The nesting rail: 2.5px at .22 -> 2px at linestrong (.14).
   The rail EARNS its place. "Containers carry no border ... cards, panels, wells" would strike
   it, but this is not a container edge — it carries which steps belong to this subpath, which
   is exactly the reasoning that sanctions row dividers: "they carry the structure of the data,
   not decoration." What was not in the language was its WEIGHT — .22 is heavier than
   linestrong (.14), the strongest line the system defines, and 2.5px is a fractional width
   that lands on a half pixel and renders soft on any non-retina screen. 2px keeps it readable
   as structure over a 180px drop while sitting on a tone the system actually names. */
/* UI audit 2026-08-14: 2px was too faint to READ. Gemini, auditing the render, reported the
   nested rail as MISSING — it was there, just invisible, which is the same as missing to a
   user. g319 dropped it from .22 to linestrong (.14) for tone compliance and I flagged the
   legibility risk at the time; an independent eye confirmed it.
   Fixed by WIDTH, not tone: 2 -> 3px, still var(--color-linestrong). The rulebook governs line
   COLOUR (line/linestrong) and says nothing about the width of a structural rail, so widening
   restores presence without spending a tone the system has not sanctioned. */
.subpath-panel::before{content:"";position:absolute;left:8px;top:7px;height:var(--rail-h,calc(100% - 14px));width:3px;border-radius:2px;background:var(--color-linestrong);z-index:2}
/* The nested table itself carries a dark fill on the main path (clipped by its
   rounded wrapper). In a subpath we want the band tone to show through instead,
   so the header row doesn't sit on a sharp black box. */
.subpath-row .nested-table,.subpath-row .nested-table>table,.subpath-row thead th,.subpath-row .step-row>td{background:transparent!important}

/* Legend: give Roles, Icons and Step Types items a uniform box height (matched to
   the taller icon box) so the three columns line up row-for-row. Content still
   centers within each box, so internal spacing varies by element but boxes don't. */
.legend-panel .item{min-height:88px}
/* Step-type pills are outlined (faint fill), so the meta at the pill's box edge
   looks like it juts left of the visible text. Indent it to sit under the text,
   matching the pill's left padding — so it reads aligned like the other sections. */
.legend-panel .item[data-drag-type="legend-type"] .item-meta{padding-left:11px}

/* Step move buttons (edit mode): robust chevron icons for up/down + out/in
   (move between nesting levels) — the on-screen alternative to drag, esp. on mobile. */
.btn-ico{display:inline-flex;align-items:center;justify-content:center;padding-left:8px;padding-right:8px}
.btn-ico .mv-ico{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;display:block}
.btn-ico[disabled]{opacity:.32;cursor:default}
.actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px}

/* Workflow View (diagram) module host — keep overflow visible so the module's
   sticky header and title dropdown aren't clipped. */
.diagram-view .main,.diagram-view #mainView,.diagram-view #wfvPanel{overflow:visible}
#wfvPanel{min-height:240px}
/* ── Check page: "What this check looks for" brief ───────────────────── */
.v-rules{margin:16px 0 4px;border-radius:var(--radius-lg);background:var(--tertiary);overflow:hidden}
.v-rules>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;padding:13px 16px;font-weight:600;color:var(--text-soft)}
.v-rules>summary::-webkit-details-marker{display:none}
.v-rules>summary::before{content:"›";font-size:16px;line-height:1;opacity:.6;transition:transform .15s ease}
.v-rules[open]>summary::before{transform:rotate(90deg)}
.v-rules-hint{margin-left:auto;font-weight:400;font-size:12px;color:var(--muted)}
.v-rules-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;padding:2px 16px 8px}
.v-rules-col h4{margin:8px 0;font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px;color:var(--text-soft)}
.v-rules-col ul{margin:0;padding-left:16px;list-style:disc}
.v-rules-col li{margin:6px 0;font-size:14px;line-height:1.5;color:var(--text-soft);opacity:.82}
.v-rules-col li b{font-weight:600;opacity:1;color:var(--text-strong)}
.v-rules-note{margin:0;padding:10px 16px 14px;font-size:12px;line-height:1.55;color:var(--muted);border-top:1px solid var(--line)}
@media(max-width:680px){.v-rules-grid{grid-template-columns:1fr;gap:6px}}

/* ── Workflow diagram → portrait PDF export ──────────────────────────── */
/* Light, print-friendly theme for the diagram. Applied (via the
   .printing-diagram class set by printDiagram()) so it takes effect during
   Export PDF; the stage is re-fitted to page width in JS. Reuses the existing
   @page (A4 portrait, 14mm) so the table PDF is unaffected. */
body.printing-diagram .wfv{
  --wfv-ink:#141414; --wfv-label:#5F5F5F;
  --wfv-canvas:#ffffff; --wfv-card-bg:#ffffff;
  --wfv-card-border:#D4D4D4; --wfv-spine:#5F5F5F; --wfv-branch:#8A8A8A;
}
body.printing-diagram .wfv-viewport,
body.printing-diagram .wfv-stage{background:#fff!important}
body.printing-diagram .wfv-card{box-shadow:none!important}
/* in the PDF export, the outline carries the ROLE colour, kept close to the (dark) role colour so
   it reads on white instead of washing out (the screen brightens it toward light for the dark canvas) */
body.printing-diagram .wfv-card{border-color:color-mix(in srgb, var(--wfv-r,#D4D4D4) 82%, #9A9A9A)!important;border-width:2.5px!important}
body.printing-diagram .wfv-rail{display:none!important}
body.printing-diagram #wfv-ah-spine path{fill:#5F5F5F!important}
body.printing-diagram #wfv-ah-branch path{fill:#8A8A8A!important}
@media print{
  body.printing-diagram .sidebar,
  body.printing-diagram .toolbar,
  body.printing-diagram .toc-backdrop,
  body.printing-diagram #notice,
  body.printing-diagram .wfv-filter,
  body.printing-diagram .wfv-zoom{display:none!important}   /* hide the zoom controls in the PDF */
  body.printing-diagram,
  body.printing-diagram .app-shell,
  body.printing-diagram .card,
  body.printing-diagram .page-card{background:#fff!important}
  /* Centre the flowchart on the page: printDiagram() sizes the viewport to the scaled diagram, so
     margin-auto centres it regardless of page width (no left-bias from a fixed target width). */
  body.printing-diagram .wfv-viewport{overflow:visible!important;margin-left:auto!important;margin-right:auto!important}
  body.printing-diagram .wfv-header{justify-content:center!important;text-align:center!important}
  body.printing-diagram .wfv-card,
  body.printing-diagram .wfv-stage,
  body.printing-diagram .wfv-wires{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
}

/* diagram PDF — complete light-theme coverage for non-var dark surfaces */
body.printing-diagram .wfv-card.wfv-terminal{background:#fff!important;border-color:color-mix(in srgb, var(--wfv-r,#D4D4D4) 82%, #9A9A9A)!important}
/* decision: drop the rectangular border — the hexagon SVG carries the outline (avoids a double box) */
body.printing-diagram .wfv-card.wfv-decision{border-color:transparent!important;background:transparent!important}
body.printing-diagram .wfv-card.wfv-decision .wfv-hexbg path{fill:#fff!important;stroke:color-mix(in srgb, var(--wfv-r,#D4D4D4) 82%, #9A9A9A)!important;stroke-width:2.5px!important}
body.printing-diagram .wfv-titlebtn,
body.printing-diagram .wfv-titletext,
body.printing-diagram .wfv-card .wfv-body{color:#141414!important}
body.printing-diagram .wfv-titlebtn{background:transparent!important}
body.printing-diagram .wfv-wlabel{color:#141414!important;background:#fff!important}

/* Whole-SOP diagram PDF — each workflow's flowchart on its own page. #printAll holds
   cloned, page-scaled .wfv-stage snapshots; .printing-diagrams supplies the diagram's
   light print theme directly on the clones (no .wfv wrapper, so set the vars on the stage). */
@media print{
  body.printing-diagrams .pdg-title{font-size:13pt;font-weight:600;color:#141414;margin:0 0 8pt;border-bottom:1pt solid #D4D4D4;padding-bottom:5pt}
  body.printing-diagrams .pdg-holder{position:relative;margin:0 auto;overflow:hidden}   /* centre each flowchart on its page */
  body.printing-diagrams .pdg-empty{color:#5F5F5F;font-size:10pt;padding:18pt 0}
  body.printing-diagrams .pdg-holder .wfv-stage{
    --wfv-ink:#141414;--wfv-label:#5F5F5F;--wfv-canvas:#fff;--wfv-card-bg:#fff;
    --wfv-card-border:#D4D4D4;--wfv-spine:#5F5F5F;--wfv-branch:#8A8A8A;
    background:#fff!important;-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important;
  }
  body.printing-diagrams .wfv-card{box-shadow:none!important;border-color:color-mix(in srgb, var(--wfv-r,#D4D4D4) 82%, #9A9A9A)!important;border-width:2.5px!important;-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
  body.printing-diagrams .wfv-card.wfv-terminal{background:#fff!important;border-color:color-mix(in srgb, var(--wfv-r,#D4D4D4) 82%, #9A9A9A)!important}
  /* decision: drop the rectangular border — the hexagon SVG carries the outline (avoids a double box) */
  body.printing-diagrams .wfv-card.wfv-decision{border-color:transparent!important;background:transparent!important}
  body.printing-diagrams .wfv-card.wfv-decision .wfv-hexbg path{fill:#fff!important;stroke:color-mix(in srgb, var(--wfv-r,#D4D4D4) 82%, #9A9A9A)!important;stroke-width:2.5px!important}
  body.printing-diagrams .wfv-card .wfv-head,
  body.printing-diagrams .wfv-card .wfv-body{color:#141414!important}
  /* Step notes carry a dark fill (#15181F) for the on-screen dark diagram; the print theme never
     overrode it, so notes printed as solid black boxes. Force the light-mode note styling. */
  body.printing-diagrams .wfv-card .wfv-note{background:#f3f5f9!important;color:#3a4150!important;border-color:rgba(20,24,30,.24)!important}
  body.printing-diagrams .wfv-card .wfv-note .wfv-note-t{color:#22262e!important}
  body.printing-diagrams .wfv-wires,
  body.printing-diagrams .wfv-wlabel{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
  body.printing-diagrams #wfv-ah-spine path{fill:#5F5F5F!important}
  body.printing-diagrams #wfv-ah-branch path{fill:#8A8A8A!important}
}
/* step-type chips: light tints with dark, legible text */
body.printing-diagram .wfv-step.start{background:#e8f3ee!important;color:#1d5a43!important;border-color:#9ccdb8!important}
body.printing-diagram .wfv-step.process{background:#e8eef9!important;color:#1f3a66!important;border-color:#9fb4d8!important}
body.printing-diagram .wfv-step.decision{background:#f6efd9!important;color:#6b551c!important;border-color:#cdb558!important}
body.printing-diagram .wfv-step.end{background:#f9e8e8!important;color:#6b2c2c!important;border-color:#d8a09a!important}
body.printing-diagram .wfv-step.document{background:#efeaf6!important;color:#3f2c66!important;border-color:#b9a6d6!important}
/* icon chips + in-card notes */
body.printing-diagram .wfv-icon{background:#f2f3f5!important;border-color:#c7ccd4!important}
body.printing-diagram .wfv-card .wfv-note{background:#FAFAFA!important;color:#5F5F5F!important;border-color:#D4D4D4!important}
body.printing-diagram .wfv-card .wfv-note .wfv-note-t{color:#141414!important}
body.printing-diagram .wfv-header{background:#fff!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;border-radius:0!important}

/* ── Whole-SOP PDF (Data → Whole SOP (PDF)) ──────────────────────────── */
/* #printAll holds every workflow's table; built on demand, hidden on screen,
   shown only in print where each workflow starts on a fresh page. Inherits
   the per-workflow @media print table styling above. */
#printAll{display:none}
@media print{
  body.printing-all .app-shell,
  body.printing-all .app-shell.toc-collapsed{display:none!important}
  body.printing-all #printAll{display:block!important}
  body.printing-all .print-wf{break-before:page;page-break-before:always}
  body.printing-all .print-wf:first-child{break-before:auto;page-break-before:avoid}
  /* each captured workflow card flows naturally; tidy spacing */
  body.printing-all .print-wf>.card{padding:0!important;margin:0!important}
  body.printing-all .filter-bar,
  body.printing-all .head-actions{display:none!important}
  /* Rebalance the main workflow table: give Description the room (it was ~21% and over-wrapped,
     ballooning row height and page count). printAllSOP prepends a full-width colspan title row to
     <thead>, which defeats table-layout:fixed — so use auto layout with width hints on the data
     cells (the "1% + nowrap to shrink, big % to grab the slack" trick). The `>` chain keeps this
     off the nested subpath tables (.nested-table), which aren't direct children of .table-wrap. */
  body.printing-all .table-wrap>table{table-layout:auto!important;width:100%!important}
  body.printing-all .table-wrap>table>tbody>tr>td:nth-child(1){width:3%}                       /* step #   */
  body.printing-all .table-wrap>table>tbody>tr>td:nth-child(2),
  body.printing-all .table-wrap>table>tbody>tr>td:nth-child(3){width:1%;white-space:nowrap}    /* role/type pills */
  body.printing-all .table-wrap>table>tbody>tr>td:nth-child(4){width:56%}                       /* description — grab slack */
  body.printing-all .table-wrap>table>tbody>tr>td:nth-child(5){width:1%;white-space:nowrap}     /* icons */
  body.printing-all .table-wrap>table>tbody>tr>td:nth-child(6){width:13%}                        /* following step */
  body.printing-all .table-wrap>table td{overflow-wrap:break-word}
}


/* ============================================================
   Beta 1.0 PDF EXPORT POLISH (headless Chrome print-to-pdf)
   Overrides the live-app print output: light subpaths everywhere,
   no interactive controls, readable pills, saner pagination.
   ============================================================ */
@media print{
  /* hide remaining interactive controls / inputs (incl. per-step comment buttons + the
     edit-pen tool cluster, which are UI chrome that was leaking into the PDF) */
  .subpath-controls,.spc-label,.search,.toc-toggle,.diagram-toggle,#diagramToggle,
  .step-tools,.cmt-btn,.step-pen-wrap{display:none!important}
  /* The inline subpath summary pill (collapsed-state toggle) is redundant in print — every subpath
     is expanded into its own labelled row below — and it clipped at the Description column edge. */
  .subpath-bar{display:none!important}
  /* Square the table/panel containers in print. On screen they round their corners AND clip the
     square cell grid with overflow:hidden; print forces overflow:visible (so a subpath can flow
     across a page break), which un-clips the cells and leaves their sharp corners poking past the
     rounded border ("rounded + sharp" mismatch). Squaring removes the rounding so corners stay clean
     — and crisp rectangular tables read better in a printed document anyway. */
  .table-wrap,.nested-table,.subpath-panel,.branch-panel{border-radius:0!important}
  /* SUBPATHS: force ALL bands light (kills the dark even-depth slabs) + drop the nesting rail */
  .subpath-row>td::before,
  .subpath-row.sp-tint>td::before{background:#FAFAFA!important}
  .subpath-panel::before{display:none!important}
  .subpath-row .nested-table,
  .subpath-row .nested-table>table,
  .subpath-row .nested-table thead th,
  .subpath-row .nested-table td,
  .subpath-row .step-row>td{background:transparent!important;color:#141414!important;border-color:#E2E2E2!important}
  .subpath-bar,.subpath-bar.sp-tint,.subpath-bar.open,.subpath-bar.sp-tint.open{background:#F0F0F0!important}
  /* inline subpath summary pill in decision rows -> a readable label */
  .sp-toggle,.subpath-bar .sp-toggle{background:#F0F0F0!important;color:#141414!important;border:0.5pt solid #D4D4D4!important;text-decoration:none!important}
  .sp-toggle *,.sp-tag,.sp-count,.sp-label{color:#141414!important;opacity:1!important}
  /* references, not web links */
  .step-link{text-decoration:none!important;color:#141414!important}
  /* pagination: allow tall subpath/note blocks to flow (was forcing big blank pages) */
  .subpath-row,.subpath-panel,.note-box{break-inside:auto!important;page-break-inside:auto!important}
  .step-row{break-inside:avoid!important;page-break-inside:avoid!important}
  /* compact notes */
  .note-box{padding:4pt 7pt!important;margin:3pt 0!important;border-radius:2pt!important}
  .note-box+.note-box{margin-top:2pt!important}
}


/* ---- Beta 1.0 PDF export: cover, legend, running titles ---- */
@media print{
  .print-cover{break-after:page;page-break-after:always}
  .print-cover .pc-logo{display:block;max-width:165pt;max-height:64pt;object-fit:contain;margin:0 0 28pt}
  .print-cover .pc-kicker{font-size:10pt;letter-spacing:.18em;text-transform:uppercase;color:#9A9A9A;margin-top:54pt}
  .print-cover .pc-title{font-size:30pt;font-weight:700;color:#141414;margin:8pt 0 2pt;line-height:1.1}
  .print-cover .pc-sub{font-size:11pt;color:#5F5F5F;margin-bottom:38pt}
  .print-cover .pc-meta{display:grid;grid-template-columns:1fr 1fr;gap:0 28pt;max-width:380pt;border-top:1pt solid #D4D4D4;padding-top:6pt}
  .print-cover .pc-meta>div{display:flex;justify-content:space-between;border-bottom:0.5pt solid #e6e8ec;padding:6pt 0}
  .print-cover .pc-meta span{color:#9A9A9A;font-size:9.5pt}
  .print-cover .pc-meta b{color:#141414;font-size:10.5pt}
  .print-legend{break-after:page;page-break-after:always}
  .print-legend h2{font-size:18pt;color:#141414;margin:0 0 4pt;border-bottom:1.5pt solid #222;padding-bottom:6pt}
  .print-legend h3{font-size:9.5pt;letter-spacing:.08em;text-transform:uppercase;color:#5F5F5F;margin:16pt 0 7pt}
  .pl-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6pt 18pt}
  .pl-item{display:flex;align-items:center;gap:7pt;font-size:9pt;color:#141414}
  .pl-chip{display:inline-block;min-width:30pt;text-align:center;color:#fff;border-radius:9pt;padding:2pt 6pt;font-size:8pt;font-weight:600}
  .pl-type{border:0.75pt solid #D4D4D4;border-radius:9pt;padding:2pt 8pt;font-size:8.5pt}
  /* Legend icons print as the real SVG (matching the workflow tables), not an emoji glyph. */
  .pl-ico{display:inline-flex;align-items:center;justify-content:center;flex:none;color:#5F5F5F}
  .pl-ico .svg-icon{width:15pt!important;height:15pt!important;min-width:15pt!important;margin:0!important}
  .pl-ico .svg-icon svg{width:10pt!important;height:10pt!important}
  .pl-name{color:#5F5F5F}
  /* repeating running title (sits in thead -> repeats on every continuation page) */
  .print-run-title th{background:#141414!important;color:#fff!important;font-size:9.5pt!important;font-weight:600!important;text-align:left!important;letter-spacing:.01em!important;padding:5pt 8pt!important;border:0!important}
}

/* ---- Beta 2.6 analysis report PDF ---- */
@media print{
  body.printing-analysis #printAll,
  body.printing-analysis #printAll *{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
  body.printing-analysis .print-analysis-page{break-before:page;page-break-before:always;break-inside:auto;page-break-inside:auto}
  body.printing-analysis .analysis-report{width:100%!important;max-width:none!important;margin:0!important;padding:0!important;border:0!important;background:#fff!important;box-shadow:none!important;color:#141414!important;overflow:visible!important;box-sizing:border-box!important}
  body.printing-analysis .analysis-head{display:block!important;margin:0 0 12pt!important;padding-bottom:8pt!important;border-bottom:1pt solid #D4D4D4!important}
  body.printing-analysis .analysis-head h2{color:#141414!important;font-size:21pt!important;line-height:1.15!important;margin:2pt 0 3pt!important}
  body.printing-analysis .analysis-head .kicker{color:#5F5F5F!important;font-size:8.5pt!important;letter-spacing:.1em!important}
  body.printing-analysis .analysis-head .manager-note{color:#5F5F5F!important;font-size:9.5pt!important;line-height:1.4!important;margin:0!important}
  body.printing-analysis .analysis-actions,
  body.printing-analysis .analysis-flag .btn{display:none!important}
  body.printing-analysis .analysis-metrics{display:grid!important;grid-template-columns:repeat(4,minmax(0,1fr))!important;gap:6pt!important;margin:0 0 9pt!important}
  body.printing-analysis .analysis-metrics>div,
  body.printing-analysis .analysis-panel,
  body.printing-analysis .analysis-insight{background:#fff!important;border:0.75pt solid #D4D4D4!important;border-radius:3pt!important;color:#141414!important;box-shadow:none!important}
  body.printing-analysis .analysis-metrics>div{min-width:0!important;padding:6pt 7pt!important;break-inside:avoid;page-break-inside:avoid}
  body.printing-analysis .analysis-metrics span,
  body.printing-analysis .analysis-panel-head p,
  body.printing-analysis .analysis-rank-role span,
  body.printing-analysis .analysis-rank-sub,
  body.printing-analysis .analysis-bar-role,
  body.printing-analysis .analysis-bar-val span,
  body.printing-analysis .analysis-flag p{color:#5F5F5F!important}
  body.printing-analysis .analysis-metrics b{color:#141414!important;font-size:15pt!important;line-height:1.1!important}
  body.printing-analysis .analysis-note,
  body.printing-analysis .analysis-empty{background:#fff8df!important;border:0.75pt solid #d8c77a!important;color:#4f461a!important;border-radius:3pt!important;padding:7pt!important;margin:0 0 9pt!important;break-inside:avoid;page-break-inside:avoid}
  body.printing-analysis .analysis-layout{display:block!important}
  body.printing-analysis .analysis-panel,
  body.printing-analysis .analysis-insight{padding:8pt!important;margin:0 0 9pt!important;break-inside:auto;page-break-inside:auto}
  body.printing-analysis .analysis-panel-head{display:block!important;margin:0 0 7pt!important}
  body.printing-analysis .analysis-panel-head h3{font-size:13pt!important;line-height:1.25!important;margin:0!important;color:#141414!important}
  body.printing-analysis .analysis-panel-head p{font-size:8.5pt!important;line-height:1.35!important;margin:2pt 0 0!important}
  body.printing-analysis .analysis-insight h4{font-size:11pt!important;line-height:1.25!important;margin:9pt 0 5pt!important;color:#141414!important}
  body.printing-analysis .analysis-narrative{font-size:9.5pt!important;line-height:1.45!important;margin:0 0 8pt!important;color:#5F5F5F!important}
  body.printing-analysis .analysis-donut-grid{display:grid!important;grid-template-columns:128pt minmax(0,1fr)!important;gap:10pt!important;align-items:center!important}
  body.printing-analysis .analysis-donut{display:block!important;width:128pt!important;height:128pt!important;max-width:128pt!important;overflow:visible!important}
  body.printing-analysis .analysis-donut-bg{stroke:#E2E2E2!important}
  body.printing-analysis .analysis-donut-seg{filter:none!important}
  body.printing-analysis .analysis-donut-num{fill:#141414!important}
  body.printing-analysis .analysis-donut-label{fill:#5F5F5F!important}
  body.printing-analysis .analysis-ranked-list,
  body.printing-analysis .analysis-bars{gap:4pt!important}
  body.printing-analysis .analysis-rank-row{grid-template-columns:18pt 8pt minmax(0,1fr) 34pt 58pt!important;gap:5pt!important;padding:4pt 0!important;border-color:#E2E2E2!important;break-inside:avoid;page-break-inside:avoid}
  body.printing-analysis .analysis-rank-num,
  body.printing-analysis .analysis-bar-rank{font-size:8.5pt!important;color:#5F5F5F!important}
  body.printing-analysis .analysis-role-dot{width:7pt!important;height:7pt!important;box-shadow:none!important}
  body.printing-analysis .analysis-rank-role b,
  body.printing-analysis .analysis-bar-val b,
  body.printing-analysis .analysis-flag b{color:#141414!important}
  body.printing-analysis .analysis-rank-role b{font-size:9pt!important}
  body.printing-analysis .analysis-rank-role span,
  body.printing-analysis .analysis-rank-sub{font-size:8pt!important}
  body.printing-analysis .analysis-rank-val{font-size:9pt!important;color:#141414!important}
  body.printing-analysis .analysis-bar-row{grid-template-columns:18pt minmax(82pt,1fr) minmax(105pt,1.6fr) minmax(78pt,.85fr)!important;gap:6pt!important;padding:4.5pt 0!important;border-color:#E2E2E2!important;break-inside:avoid;page-break-inside:avoid}
  body.printing-analysis .analysis-mini-bar{display:block!important;width:100%!important;height:9pt!important;min-width:0!important;overflow:visible!important}
  body.printing-analysis .analysis-mini-bg{fill:#e7ebf0!important}
  body.printing-analysis .analysis-bar-role{gap:5pt!important;font-size:8.5pt!important}
  body.printing-analysis .analysis-bar-role .role-chip{font-size:7.5pt!important;line-height:1.2!important;padding:1.5pt 5pt!important;max-width:58pt!important}
  body.printing-analysis .analysis-bar-val b{font-size:8.8pt!important;line-height:1.2!important;white-space:normal!important}
  body.printing-analysis .analysis-bar-val span{font-size:7.8pt!important;line-height:1.25!important;white-space:normal!important}
  body.printing-analysis .analysis-flag{display:block!important;background:#fff!important;border:1pt solid #E2E2E2!important;border-left-width:3pt!important;border-radius:3pt!important;margin-top:6pt!important;padding:7pt!important;break-inside:avoid;page-break-inside:avoid}
  body.printing-analysis .analysis-flag.high{border-left-color:var(--color-danger)!important}
  body.printing-analysis .analysis-flag.med{border-left-color:#a68100!important}
  body.printing-analysis .analysis-flag.low{border-left-color:#4f6fa8!important}
  body.printing-analysis .analysis-flag-sev{height:auto!important;border-color:#D4D4D4!important;color:#5F5F5F!important;background:#FAFAFA!important;font-size:7.5pt!important;padding:1pt 5pt!important}
  body.printing-analysis .analysis-flag p{font-size:8.8pt!important;line-height:1.35!important}
  body.printing-analysis .analysis-recs{color:#5F5F5F!important;font-size:9pt!important;line-height:1.45!important;margin:0!important;padding-left:16pt!important}
  body.printing-analysis .analysis-recs li{margin:3pt 0!important;break-inside:avoid;page-break-inside:avoid}
}

.brand-logo-wide[src=""], .brand-logo-icon[src=""] { display: none; }

/* ===== Sidebar footer + sandwich toggle ===== */
/* Sidebar fills the viewport so the builder-identity footer pins to the bottom (all sizes) */
@media screen{.sidebar{display:flex!important;flex-direction:column!important;min-height:100vh!important}}
.sidebar-footer{margin-top:auto;flex:0 0 auto;padding:12px 2px 4px;color:var(--muted);font-size:11px;line-height:14px;font-weight:var(--fw-body);letter-spacing:.14em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-top:1px solid rgba(255,255,255,.06)}
.app-shell.toc-collapsed .sidebar-footer{display:none}
@media (max-width:1100px){.app-shell.toc-collapsed .sidebar-footer{display:block}}

/* Desktop only (min-width:1101px): sandwich-toggle sizing, brand anchor, and the widened
   collapsed rail. Scoped so the mobile overlay layout (max-width:1100px) is untouched. */
@media (min-width:1101px){
  /* Match toolbar button height (44px), square, identical open/collapsed so it never shifts */
  .toc-toggle,
  .app-shell.toc-collapsed .toc-toggle{
    width:44px!important;height:44px!important;min-width:44px!important;
    border-radius:8px!important;font-size:16px!important;line-height:1!important;
    margin:0!important;flex:0 0 auto!important;
  }
  /* Anchor the toggle at the same offset regardless of collapse state */
  .brand,.app-shell.toc-collapsed .brand{
    align-items:center!important;justify-content:flex-start!important;
    height:auto!important;min-height:0!important;
    padding:10px 0 8px!important;margin:0 0 6px!important;gap:10px!important;
  }
  /* Even out the page frame: left + top breathing room closer to the (liked) ~40px right gutter */
  /* small right padding so the body's scrollbar sits near the edge (not floating inset) and the list gets more room */
  .sidebar,.app-shell.toc-collapsed .sidebar{padding-left:32px!important;padding-right:14px!important;padding-top:14px!important}
  .app-shell .sidebar > .brand{padding-right:10px}
  /* Top breathing room goes on the INNER content (#mainView), not on .main — .main is the
     scroll container, and padding-top on it makes the sticky thead stick BELOW that padding
     (a 14px band where scrolling rows showed through, above the header). With it on #mainView
     the scroll container's top is flush under the toolbar, so the thead pins right against it. */
  .main{padding-top:0!important}
  .app-shell .main>#mainView{padding-top:14px}
  /* Smooth collapse (Beta 2.7) — the Axi-chat mechanism. grid-template-columns cannot be animated in
     this engine (transitioning it FROZE the track at the open width, so main never reclaimed the space:
     the "resizes big then small" jump). So the grid track is held FIXED at the open width and we ease
     the same normal, interpolatable properties the chat panel eases: the sidebar's WIDTH (the rail) and
     main's MARGIN-LEFT + WIDTH (main slides left over the vacated rail and grows to fill it). The rail's
     right edge and main's left edge share one easing curve, so they move in lockstep — no gap, no jump.
     main overflows its grid cell leftward by design; app-shell's overflow-x:clip stops any scrollbar. */
  .app-shell,.app-shell.toc-collapsed{grid-template-columns:var(--toc-open) minmax(0,1fr)!important;transition:none!important}
  .sidebar{width:var(--toc-open);max-width:var(--toc-open);transition:width .26s cubic-bezier(.4,0,.2,1),max-width .26s cubic-bezier(.4,0,.2,1),padding .26s cubic-bezier(.4,0,.2,1)!important}
  /* The collapsed rail is 0, not 88px. The 88px stub dated from when the rail still held the
     menu button; #195 moved Contents/Axi up into the app bar, leaving an empty 88px column that
     showed up as an asymmetric page: 88 + main's 24px padding = 112px of gutter on the left
     against 24px on the right. Tablet looked right precisely because the rail goes off-canvas
     there. One token so the rail width stays tunable in one place — the slide still animates,
     it just animates to zero. `border-right-width` is zeroed explicitly: a 0-width sidebar with
     a live 1px border still paints a 1px line and re-introduces the offset. */
  .app-shell{--toc-rail:0px}
  .app-shell.toc-collapsed .sidebar{width:var(--toc-rail)!important;max-width:var(--toc-rail)!important;
    padding-left:0!important;padding-right:0!important;border-right-width:0!important;
    /* .sidebar is a GRID ITEM, so its default min-width:auto resolves to min-content and clamps
       the width straight back up — which is why neither this 0 nor the previous 88px ever
       actually took, and the rail measured its full 240px while "collapsed". overflow:hidden
       keeps the clipped contents from re-inflating that min-content floor. */
    min-width:0!important;overflow:hidden!important}
  .app-shell.toc-collapsed .chat-panel{left:var(--toc-rail)}
  .app-shell.toc-collapsed .brand{overflow:visible!important}
  /* the resize handle rides the rail's right edge — hide it while collapsed (a rail can't be resized) */
  .app-shell.toc-collapsed .toc-resizer{display:none}
  /* main slides + grows via margin-left/width (both interpolate). 4 states: {toc collapsed?}×{chat open?}.
     A negative margin pulls main left onto the freed rail; the chat variants add the chat panel's width. */
  .main{margin-left:0;width:calc(100vw - var(--toc-open));max-width:calc(100vw - var(--toc-open));transition:margin-left .26s cubic-bezier(.4,0,.2,1),width .26s cubic-bezier(.4,0,.2,1),max-width .26s cubic-bezier(.4,0,.2,1)}
  .app-shell.toc-collapsed .main{margin-left:calc(var(--toc-rail) - var(--toc-open))!important;width:calc(100vw - var(--toc-rail))!important;max-width:calc(100vw - var(--toc-rail))!important}
  .app-shell.chat-open .main{margin-left:var(--chat-open,380px)!important;width:calc(100vw - var(--toc-open) - var(--chat-open,380px))!important;max-width:calc(100vw - var(--toc-open) - var(--chat-open,380px))!important}
  .app-shell.chat-open.toc-collapsed .main{margin-left:calc(var(--toc-rail) - var(--toc-open) + var(--chat-open,380px))!important;width:calc(100vw - var(--toc-rail) - var(--chat-open,380px))!important;max-width:calc(100vw - var(--toc-rail) - var(--chat-open,380px))!important}
}

/* SOP content-version badge next to the title; hide empty brand subtitle */
.content-ver{flex:0 0 auto;font-size:12px;font-weight:var(--fw-body);color:var(--muted);border:1px solid var(--line2);border-radius:var(--radius-chip);padding:2px 8px;letter-spacing:.02em;line-height:16px}
/* Persistent data-handling posture chip in the toolbar (Beta 2.3 #4). Green = Local/no-AI
   (nothing leaves the device); amber = a live BYO-key provider (requests go straight to it). */
.ai-posture{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;font:inherit;font-size:12px;font-weight:var(--fw-control,600);line-height:16px;padding:3px 10px;border-radius:var(--radius-chip);border:1px solid var(--line2);background:transparent;color:var(--text-soft);cursor:pointer;white-space:nowrap;max-width:40vw;overflow:hidden;text-overflow:ellipsis}
.ai-posture .ai-posture-dot{flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:currentColor}
.ai-posture .ai-posture-txt{overflow:hidden;text-overflow:ellipsis}
.ai-posture:hover{border-color:rgba(255,255,255,.32);background:rgba(255,255,255,.05)}
.ai-posture.is-ready{color:var(--ok,var(--color-success));border-color:rgba(70,185,138,.45);background:rgba(70,185,138,.10)}
.ai-posture.is-warn{color:#E8D3A8;border-color:rgba(197,179,88,.45);background:rgba(150,120,40,.12)}
.ai-posture.is-off{color:var(--muted2);border-color:var(--line2);background:transparent}
.brand small:empty{display:none}

/* Edit-mode per-step action buttons: Edit + Delete on top, move/nest arrows in a row below */
.wf-view .actions{display:flex;flex-direction:column;gap:6px;flex-wrap:nowrap;align-items:stretch}
.wf-view .act-row{display:flex;gap:6px}
/* Item C1 — inline step editor. The step row MORPHS into this editor card in place
   (no duplicate read row); it slides+fades in via stepEditorIn. */
.step-editor-row>td{padding:0!important;border:none!important}
.step-editor{margin:0 0 12px;padding:0;border:1px solid var(--line-state);border-radius:var(--radius-md);background:var(--paper2);overflow:hidden;box-shadow:0 12px 32px rgba(0,0,0,.34);animation:stepEditorIn .24s cubic-bezier(.2,.7,.3,1)}
@keyframes stepEditorIn{from{opacity:0;transform:translateY(-8px) scale(.992)}to{opacity:1;transform:none}}
.se-head{display:flex;flex-wrap:wrap;align-items:center;gap:9px;padding:10px 14px;background:color-mix(in srgb,var(--accent-strong) 16%,var(--paper2));border-bottom:1px solid var(--line)}
.se-head-step{font-weight:700;font-size:14px;letter-spacing:.02em;color:var(--text)}
.se-head-chip{flex:0 0 auto;margin:0}
.se-done{flex:0 0 auto;width:auto;white-space:nowrap}
.se-body{padding:14px 14px 4px}
.se-reorder{display:flex;align-items:center;gap:8px;padding:10px 14px;border-top:1px solid var(--line)}
.se-reorder .se-move{flex:0 0 auto;width:auto;padding:0 10px;min-height:34px}
.se-foot{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:11px 14px;border-top:1px solid var(--line);background:color-mix(in srgb,var(--paper) 55%,transparent)}
.se-foot .btn{flex:0 0 auto;width:auto}
@media(prefers-reduced-motion:reduce){.step-editor{animation:none}}
/* On phones: bigger reorder tap targets, keep the header compact so Done isn't clipped/oversized. */
@media(max-width:720px){
  .se-reorder{flex-wrap:wrap}
  .se-reorder .se-move{min-height:44px;min-width:54px;padding:0 12px}
  .se-reorder .se-move svg{width:20px;height:20px}
  .se-head-chip{display:none}
  .se-done{flex:0 0 auto;width:auto!important;min-height:36px;padding:6px 14px;font-size:14px;align-self:center}
}
.se-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media(max-width:560px){.se-grid{grid-template-columns:1fr}}
.se-f{display:flex;flex-direction:column;gap:4px;margin-bottom:10px;min-width:0}
.se-f>span{font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted)}
.se-f select,.se-f textarea{width:100%;padding:8px 10px;border-radius:var(--radius-sm);border:1px solid var(--line);background:var(--paper);color:var(--text);font:inherit;font-size:14px}
.se-f textarea{resize:vertical;line-height:1.45}
.se-wide{grid-column:1/-1}
.se-polish-row{display:flex;align-items:center;gap:8px;margin-top:6px}
.se-polish-status{font-size:12px;color:var(--muted)}
.se-polish-card{margin-top:8px}
.se-notes{display:flex;flex-direction:column;gap:6px;margin-bottom:6px}
.se-note{display:flex;gap:6px;align-items:flex-start}
.se-note textarea{flex:1 1 auto;min-width:0;width:auto}
.se-note-del{flex:0 0 auto;width:34px!important;padding:0!important}
/* rule 6 — a decision can't carry notes: read-only list + relocate/delete actions in the editor */
.se-dn-list{display:flex;flex-direction:column;gap:6px;margin:6px 0}
.se-dn-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px}
.se-icons{display:flex;flex-wrap:wrap;gap:4px}
/* Minimal icon chips: faded by default; a box appears on hover; selected = full saturation + box. */
.se-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;padding:4px;width:auto;border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;cursor:pointer;opacity:.45;transition:opacity .12s,border-color .12s,background .12s}
.se-icon:hover{opacity:1;border-color:var(--line);background:var(--paper)}
.se-icon.on{opacity:1;border-color:var(--line-state);background:color-mix(in srgb, var(--text) 12%, transparent)}
.se-icon .svg-icon{margin:0}
.se-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:6px}
.se-actions .btn{flex:0 0 auto;width:auto}
.se-spacer{flex:1 1 30px}
.se-spacer{flex:1 1 auto}
.polish-note-line{font-size:12px;color:var(--text-soft);line-height:1.5}

/* Item C2 — inline editors for the Legend (roles/step-types/icons) and Structure (sections/workflows).
   Reuse the .se-* field primitives from the step editor; these add the container + a few extras. */
.legend-editor,.struct-editor{margin:2px 0 6px;padding:14px;border:1px solid var(--line-state);border-radius:var(--radius-md);background:var(--paper2)}
.legend-editor{min-width:0;max-width:100%;display:flex;flex-direction:column;gap:2px;overflow-wrap:anywhere}
.struct-editor{margin:2px 0 10px}
.legend-editor input[type=text],.legend-editor textarea,.struct-editor input[type=text],.struct-editor textarea{width:100%;min-width:0;max-width:100%;padding:8px 10px;border-radius:var(--radius-sm);border:1px solid var(--line);background:var(--paper);color:var(--text);font:inherit;font-size:14px}
.legend-editor textarea{resize:vertical;line-height:1.45;min-height:82px}
.legend-editor .se-grid,.legend-editor .se-f,.legend-editor .se-actions{min-width:0;max-width:100%}
.struct-editor textarea{resize:vertical;line-height:1.45}
.le-color-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.le-color-row input[type=color]{flex:0 0 auto;width:42px!important;height:38px!important;padding:0!important;border:0!important;border-radius:var(--radius-control)!important;background:transparent!important;overflow:hidden;cursor:pointer;box-shadow:none!important}
.le-color-row input[type=color]::-webkit-color-swatch-wrapper{padding:0}
.le-color-row input[type=color]::-webkit-color-swatch{border:1px solid var(--line2);border-radius:var(--radius-control)}
.le-color-row input[type=color]::-moz-color-swatch{border:1px solid var(--line2);border-radius:var(--radius-control)}
.le-color-row input[type=color]:hover::-webkit-color-swatch{border-color:var(--accent)}
.legend-color-state{font-size:12px;color:var(--muted);line-height:1.4}
.legend-icon-preview{display:flex;flex-direction:column;align-items:flex-start;gap:6px;margin:0 0 10px;min-width:0}
.legend-icon-preview>span,.legend-icon-library-title{font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted)}
.legend-icon-library-title{margin:4px 0 8px;line-height:1.4}
.legend-icon-suggest{min-width:0;max-width:100%;margin-top:12px;padding:10px;border-radius:var(--radius-sm);background:var(--paper)}
.legend-icon-suggest .se-f{margin:0}
.legend-icon-suggest-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px;margin-top:8px}
.le-note{font-size:12px;color:var(--text-soft);line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:var(--radius-sm);background:var(--paper)}
.item.is-editing{outline:1px solid var(--line-state);outline-offset:1px;border-radius:var(--radius-sm)}
.se-moves{gap:6px}
.se-move-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--muted);margin-right:2px;align-self:center}
.se-move-to{display:flex;flex:1 1 220px;align-items:center;gap:6px;min-width:0}.se-move-to>span{flex:0 0 auto;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--muted)}.se-move-to select{min-width:0;width:100%;padding:7px 34px 7px 10px;font-size:12px}
.se-moves .btn-ico{flex:0 0 auto;width:auto;padding:0 7px}
.step-row.is-editing>td{background:var(--paper2)}
.card.is-editing{outline:1px solid var(--line-state)}
.icon-lib-hint{font-size:11px;color:var(--muted);margin:2px 0 8px;line-height:1.3}
.icon-save-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 6px}
.icon-save-note{font-size:11px}
.legend-icon-editor .icon-lib-btn.saved-icon{position:relative}
.legend-icon-editor .saved-icon-remove{position:absolute;top:3px;right:3px;width:18px;height:18px;padding:0;border-radius:50%;border:1px solid var(--line2);background:var(--paper2);color:var(--muted);font-size:14px;line-height:1;cursor:pointer;display:none;align-items:center;justify-content:center}
.legend-icon-editor .icon-lib-btn.saved-icon:hover .saved-icon-remove,.legend-icon-editor .saved-icon-remove:focus-visible{display:flex}
.legend-icon-editor .saved-icon-remove:hover{color:#e0796f;border-color:#e0796f}
.legend-icon-editor .icon-lib-grid{width:100%;min-width:0;max-width:100%;grid-template-columns:repeat(auto-fill,minmax(min(88px,100%),1fr));grid-auto-rows:80px;gap:10px;max-height:min(46vh,360px);overflow-x:hidden;padding:4px}
.legend-icon-editor .icon-lib-btn{min-width:0;max-width:100%;overflow:hidden}
.legend-icon-editor .icon-lib-name{max-width:100%;overflow-wrap:anywhere}
@media(max-width:420px){
  .legend-editor{padding:12px}
  .legend-editor .se-actions{align-items:stretch}
  .legend-editor .se-actions .btn:not(.btn-ico){white-space:normal}
  .legend-icon-editor .icon-lib-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .legend-icon-suggest-actions .btn{flex:1 1 110px}
}
.tut-tour-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 14px;padding:12px;border-radius:var(--radius-md);background:var(--paper2)}
.tut-tour-row .muted{font-size:12px}

/* Item F — guided tour: spotlight overlay (a transparent hole + huge box-shadow dims the rest). */
/* THE GUIDED TOUR JOINS THE DESIGN LANGUAGE (g440)
   =======================================================================================
   Owner: "the guided tour colours need to be updated — the entire guided tour needs updating."
   This block predates the reskin and never came across. What was wrong, measured:
     · the spotlight scrim was `rgba(8,10,14,.66)` — blue channel highest, the same pre-reskin
       navy tint the role filter carried. It now takes `--color-scrim`, the token every other
       overlay dims with, so the tour dims the app the way the app dims itself.
     · the spotlight ring was `2px solid var(--accent-strong)`. Two faults in one declaration:
       §5 reserves 2px for the focus ring alone, and `--accent-strong` resolves to ink, which is
       the g438 problem — a line at maximum tone reads as heavy whatever its width. 1px on the
       state tier.
     · the step counter was `font-weight:700` with free-sized tracking. The eyebrow rung has
       tokens for both; 700 is off the weight scale entirely.
     · the card's radius was 14 and the "try this" well 9 — neither on the radius scale, which is
       a ROLE scale (control 10 / card 16 / modal 20). A card gets the card radius.
     · the body copy sat on `--text-soft`, which resolves to #F2F2F2 — full ink. The card had no
       tonal hierarchy at all: counter, title and body were the same brightness.
   The scrim and the ring both take brand-aware tokens, so the tour re-tints with Appearance
   like the rest of the app now does. */
.coach-overlay{position:fixed;inset:0;z-index:300;pointer-events:auto}
/* A TARGETLESS TOUR STEP IS A MODAL, SO IT GETS THE MODAL BACKDROP (g454). The welcome and
   sign-off steps point at nothing, and they were the only overlay in the app dimming without
   the 8px blur `.modal-backdrop` carries — same .6 black, but the structure behind stayed
   sharp, so the diagram kept competing with the card and the whole screen read muddy rather
   than "out of play".
   ON A PSEUDO-ELEMENT, NOT THE OVERLAY: `.coach-card` is a CHILD of `.coach-overlay`, and
   `backdrop-filter` on an ancestor makes it a containing block for `position:fixed`
   descendants. The card's top/left are computed against the viewport in JS, so filtering the
   parent would silently re-base every coordinate. The pseudo takes the filter and the card's
   containing block is left alone. The card still paints above it — the card carries z-index
   301, the pseudo carries none.
   TARGETED STEPS DELIBERATELY DO NOT BLUR: their scrim is the 9999px spread on `.coach-hole`,
   and a filter there would blur the very element the step is pointing at. The spotlight ring
   is what separates card from ground on those steps. */
.coach-overlay.no-target::before{content:"";position:absolute;inset:0;background:var(--color-scrim);
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
.coach-hole{position:absolute;border-radius:var(--radius-control);box-shadow:0 0 0 9999px var(--color-scrim);outline:1px solid var(--color-primary);outline-offset:0;transition:top .18s ease,left .18s ease,width .18s ease,height .18s ease;pointer-events:none}
/* Modal tier, so the modal's radius and the shared float shadow (g454). The hand-written
   `rgba(0,0,0,.6)` shadow was 43% heavier than `--shadow-float`'s .42 — it pooled darkness
   around the card on top of a scrim that was already dimming, which is the "darkening" the
   owner objected to. Radius 16 -> 20 for the same reason `.modal` uses 20: the role scale is
   control 10 / card 16 / modal 20, and this floats over a scrim like a modal, not in flow
   like a card.
   FILL LEFT AT --color-overlay (#212121) ON PURPOSE: `.setpanel,.modal` re-point that token to
   --color-surface (#121212), so settings overlays sit one rung DARKER than this card. Matching
   them exactly would mean darkening the tour, the opposite of what was asked, so the chrome is
   aligned and the ground is not. */
.coach-card{position:fixed;z-index:301;background:var(--color-overlay);border:1px solid var(--color-linestrong);border-radius:var(--radius-modal);box-shadow:var(--shadow-float);padding:20px 20px 16px}
.coach-step-count{font-size:var(--text-eyebrow,11px);font-weight:var(--weight-eyebrow);letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--color-faint);margin-bottom:8px}
.coach-title{margin:0 0 8px;font-size:16px;line-height:1.25;color:var(--color-ink)}
.coach-body{margin:0 0 12px;font-size:14px;line-height:1.55;color:var(--color-muted)}
.coach-body b{color:var(--text)}
.coach-actions{display:flex;align-items:center;gap:8px}
.coach-spacer{flex:1 1 auto}
.coach-card::after{content:"";display:none;position:absolute;width:14px;height:14px;background:var(--color-overlay);border:1px solid var(--color-linestrong);transform:rotate(45deg)}
.coach-arrow-up::after,.coach-arrow-down::after,.coach-arrow-left::after{display:block}
.coach-arrow-up::after{top:-8px;left:24px;border-right:none;border-bottom:none}
.coach-arrow-down::after{bottom:-8px;left:24px;border-left:none;border-top:none}
.coach-arrow-left::after{left:-8px;top:24px;border-right:none;border-top:none}
/* g440 — the progress bar takes the brand (it is the tour's own state), and its radius goes to
   the full round a 4px bar actually wants; 3px on a 4px bar is neither square nor round. */
.coach-progress{height:4px;border-radius:var(--radius-full,999px);background:var(--color-line);overflow:hidden;margin:0 0 12px}
.coach-progress>span{display:block;height:100%;background:var(--color-primary);border-radius:var(--radius-full,999px);transition:width .25s ease}
.coach-body i{color:var(--color-faint);font-style:italic}
/* g440 — the "try this" well is a well: `inset-well` is 16, and it sits on surface2 with the
   line tier around it. It was 9/11 padding on a 9px radius, all three off their scales. */
.coach-try{display:flex;gap:8px;align-items:flex-start;margin:0 0 12px;padding:12px;border-radius:var(--radius-control);background:var(--color-surface2);border:1px solid var(--color-line);font-size:12px;line-height:1.5;color:var(--color-muted)}
.coach-try b{color:var(--text)}
.coach-try-ico{flex:none;line-height:1.4}
/* g452 — the tour's buttons come off the COMPACT rung. They were `.btn small` (28px, r8),
   which §5 reserves for card-header actions only: "Control height 36px with pointer input …
   a compact 28px ghost exists for card-header actions only." Skip / Back / Next and the
   in-card action ARE the card's actions, so they take the standard control height and radius
   like every other button in the app. Markup change; this rule just loses its off-ramp margin. */
.coach-action{width:100%;justify-content:center;margin:0 0 12px}
.wf-view .act-row>.btn{flex:1 1 0;min-width:0}
.wf-view .act-main>.btn{padding:0 5px;white-space:nowrap}
.wf-view .act-moves>.btn{padding:0}

/* Icon picker in the edit-step modal */
.icon-pick-grid{display:flex;flex-wrap:wrap;gap:8px;margin-top:2px}
/* Item M — icon library picker */
.icon-edit-preview{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:64px;height:64px;border:1px solid var(--line);border-radius:var(--radius-md);background:var(--paper2)}
.icon-edit-preview .svg-icon{width:44px;height:44px;margin:0}
.icon-edit-preview .svg-icon svg{width:26px;height:26px}
.icon-lib-search{width:100%;margin:0 0 10px}
.icon-lib-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(92px,1fr));gap:8px;max-height:min(40vh,320px);overflow:auto;padding:2px}
.icon-lib-btn{display:flex;flex-direction:column;align-items:center;gap:5px;padding:9px 6px;border:1px solid var(--line);border-radius:var(--radius-md);background:var(--paper2);color:var(--text-soft);font:inherit;cursor:pointer;transition:border-color .12s,background .12s}
.icon-lib-btn:hover{border-color:var(--line-state);background:rgba(255,255,255,.05)}
.icon-lib-btn.sel{border-color:var(--line-state);background:color-mix(in srgb, var(--text) 12%, transparent)}
.icon-lib-btn .svg-icon{margin:0}
.legend-icon-editor .icon-lib-btn{height:100%;flex-direction:column;justify-content:center;align-items:center;gap:6px;padding:7px 4px;background:transparent;border:1px solid transparent;border-radius:10px}
.legend-icon-editor .icon-lib-btn:hover{background:color-mix(in srgb,var(--accent-strong) 10%,transparent)}
.legend-icon-editor .icon-lib-btn.sel{background:var(--sel-fill);border-color:var(--line-state)}
.legend-icon-editor .icon-lib-btn .icon-lib-name{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:11px;line-height:1.2;text-align:center;color:var(--text-soft);max-width:100%;word-break:break-word}
.legend-icon-editor .icon-lib-btn .svg-icon{flex:0 0 auto;margin:0}
.icon-lib-name{font-size:11px;line-height:1.2;text-align:center;color:var(--muted)}
.icon-pick{display:inline-flex;align-items:center;gap:8px;padding:5px 13px 5px 6px;border:1px solid var(--line2);border-radius:var(--radius-chip);background:rgba(255,255,255,.03);color:var(--text-soft);cursor:pointer;font-size:14px;line-height:1}
.icon-pick:hover{border-color:var(--text-soft);background:var(--hover)}
.icon-pick.is-on{border-color:var(--color-ink);background:var(--color-surface2);color:var(--text)}
.icon-pick .svg-icon{width:28px;height:28px;min-width:28px;margin:0;flex:0 0 auto;font-size:16px}
.icon-pick .svg-icon svg{width:16px;height:16px}
.icon-pick-label{white-space:nowrap}

/* Rename-before-export modal */
.rename-modal{max-width:520px}
.rename-hint{color:var(--muted);font-size:14px;line-height:1.45;margin:0 0 12px}
.theme-default-note{font-size:14px;line-height:1.45;color:var(--text-soft);margin:-2px 0 16px;padding:9px 11px;border-radius:var(--radius-sm);background:var(--paper2)}
.theme-default-note.is-default{color:var(--text-soft)}
.linklike{background:none;border:0;padding:0;margin:0;color:var(--accent-strong);font:inherit;cursor:pointer;text-decoration:underline}
.linklike:hover{opacity:.82}
.rename-hint b{color:var(--text-soft);font-weight:var(--fw-control)}
.rename-modal input[name="fname"]{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}

/* Toolbar: wrap the action buttons to a second row before the title gets cramped,
   and truncate the title so the SOP title/version never clips into the buttons. */
.toolbar{flex-wrap:wrap}
/* desktop: keep the app-bar on ONE row — the title truncates so the actions never wrap.
   Below 1101 the bar may wrap and the actions drop to a second row, right-aligned. */
@media(min-width:1101px){.toolbar{flex-wrap:nowrap}.toolbar-actions{flex-wrap:nowrap;flex:0 0 auto}}
.toolbar-title{flex:1 1 auto;min-width:0}
.toolbar-sop-title{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* The sidebar/toolbar base background is routed through the token so the active
   mode (dark default / light) re-skins it. (The old off-brand dark theme presets —
   graphite/navy/forest/plum — were removed in the AXD-I re-skin: monochrome is the
   only theme, and applyTheme never sets data-theme.) */
:root[data-mode] .sidebar{background:var(--bg)!important;border-right-color:var(--line)!important}
:root[data-mode] .toolbar{background:var(--bg)!important;backdrop-filter:none!important}

/* Appearance / theme picker modal */
.theme-grid{display:flex;flex-wrap:wrap;gap:10px;margin-top:2px}
.theme-swatch{display:flex;flex-direction:column;align-items:center;gap:6px;padding:8px;border:1px solid var(--line2);border-radius:var(--radius-md);background:rgba(255,255,255,.03);color:var(--text-soft);cursor:pointer;width:84px}
.theme-swatch:hover{border-color:var(--text-soft)}
.theme-swatch.sel{border-color:var(--color-ink);background:var(--color-surface2);color:var(--text)}
.theme-swatch .ts-chip{width:100%;height:34px;border-radius:6px;border:1px solid rgba(255,255,255,.14)}
.theme-swatch .ts-name{font-size:12px}
.theme-mode-row{display:flex;border-radius:var(--radius-md);overflow:hidden;margin-top:2px}
.theme-mode-btn{flex:1 1 0;min-width:0;appearance:none;border:0;background:transparent;color:var(--text-soft);padding:9px 14px;font-size:var(--fs-body);font-weight:var(--fw-control);cursor:pointer;font-family:inherit;text-align:center;white-space:nowrap}
.theme-mode-btn+.theme-mode-btn{}
.theme-mode-btn:hover{background:var(--paper3);color:var(--text)}
.theme-mode-btn.sel{background:color-mix(in srgb,var(--text) 11%,transparent);color:var(--text);font-weight:var(--fw-control)}
/* Appearance: BRAND-layer colour picker + logo drop (AXD-I re-skin) */
.brand-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:2px}
.brand-none{appearance:none;display:inline-flex;align-items:center;height:36px;padding:0 12px;border:1px solid var(--color-linestrong);border-radius:var(--radius-control);background:transparent;color:var(--color-ink);font-size:var(--fs-meta);font-weight:var(--fw-control);cursor:pointer;font-family:inherit;white-space:nowrap}
.brand-none:hover{background:var(--color-hover)}
.brand-none.sel{border-color:var(--color-ink);box-shadow:inset 0 0 0 1px var(--color-ink)}
.brand-swatch{appearance:none;width:30px;height:30px;flex:0 0 auto;padding:0;border:1px solid var(--color-linestrong);border-radius:var(--radius-chip);background:var(--sw);cursor:pointer;transition:box-shadow .14s ease,border-color .14s ease}
/* selectable control: hover brightens the edge, it never moves (the .sel ring is the selection marker) */
.brand-swatch:hover{border-color:var(--color-ink)}
.brand-swatch.sel{box-shadow:0 0 0 2px var(--color-canvas),0 0 0 4px var(--sw)}
.brand-sep{width:1px;height:26px;background:var(--color-line);margin:0 2px}
/* Single brand-colour control — a swatch + hex that opens the standard colour popover. */
.brand-pick{position:relative;display:inline-flex;align-items:center;gap:10px;height:36px;padding:0 12px 0 8px;flex:0 0 auto;border:1px solid var(--color-linestrong);border-radius:var(--radius-control);background:transparent;cursor:pointer;transition:border-color .14s ease}
.brand-pick:hover{border-color:var(--color-ink)}
.brand-pick-sw{width:22px;height:22px;flex:0 0 auto;border-radius:var(--radius-chip);border:1px solid var(--color-linestrong);background:conic-gradient(from 180deg,#e06666,#f6d365,#68d391,#4fd1c5,#63b3ed,#b794f4,#e06666)}
.brand-pick.has .brand-pick-sw{border-color:var(--color-line)}
.brand-pick-label{font-size:var(--fs-meta);font-weight:var(--fw-control);color:var(--color-ink);white-space:nowrap;font-variant-numeric:tabular-nums;letter-spacing:.01em}
.brand-pick input[type=color]{position:absolute;inset:0;width:100%;height:100%;border:0;padding:0;margin:0;background:transparent;opacity:.001;cursor:pointer}
.brand-hint{display:block;margin-top:10px;font-size:var(--fs-label-sm);line-height:1.5}
.logo-dual{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:2px}
@media(max-width:560px){.logo-dual{grid-template-columns:1fr}}
.logo-dual .logo-drop{margin-top:0}
/* Light-mode logo thumb sits on a light chip so a dark/black light-variant logo is visible while previewing in dark mode. */
.logo-drop-thumb-light{background:#F0F0F0}
.logo-drop{display:flex;align-items:center;gap:12px;margin-top:2px;padding:12px;border:1px dashed var(--color-linestrong);border-radius:var(--radius-card);cursor:pointer;transition:border-color .14s ease,background .14s ease}
.logo-drop:hover{border-color:var(--color-ink);background:var(--color-hover)}
.logo-drop-thumb{width:40px;height:40px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-chip);background:var(--color-control);overflow:hidden}
.logo-drop-thumb img{max-width:100%;max-height:100%;object-fit:contain}
.logo-mono{font-size:16px;font-weight:var(--fw-control);color:var(--color-muted)}
.logo-drop-text{display:flex;flex-direction:column;gap:2px}
.logo-drop-text b{font-size:var(--fs-body);font-weight:var(--fw-control);color:var(--color-ink)}
.logo-drop-text small{font-size:var(--fs-label-sm);color:var(--color-muted)}
.accent-row{display:flex;align-items:center;gap:10px}
/* Unified button-shaped colour swatch across ALL colour pickers (appearance, legend roles/icons/
   step-types, modals): a clean rounded box (matches the Apply / Dark-Light buttons) with a subtle
   outline hover. Strip the native inner color-swatch border/padding so the colour fills the whole
   box (no box-in-box). The appearance picker keeps the larger mode-button size. */
input[type=color]{width:60px;height:38px;flex:0 0 auto;padding:0!important;border:1px solid var(--line2)!important;border-radius:var(--radius-md)!important;background:transparent!important;overflow:hidden;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease;vertical-align:middle}
input[type=color]:hover{border-color:var(--accent)!important;box-shadow:0 0 0 3px var(--accent-muted)}
input[type=color]::-webkit-color-swatch-wrapper{padding:0}
input[type=color]::-webkit-color-swatch{border:none;border-radius:calc(var(--radius-md) - 1px)}
input[type=color]::-moz-color-swatch{border:none;border-radius:calc(var(--radius-md) - 1px)}
.accent-row input[type=color]{width:75px;height:41px}
/* In-theme colour popover (replaces the sharp native picker) */
#colorPopRoot:empty{display:none}
.color-pop{position:fixed;z-index:320;width:244px;background:var(--color-overlay);border:1px solid var(--color-linestrong);border-radius:var(--radius-md);box-shadow:0 16px 38px rgba(0,0,0,.4);padding:11px}
.color-pop-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:7px}
.color-pop-swatch{width:100%;aspect-ratio:1;height:auto;border-radius:var(--radius-chip);border:1px solid var(--line2);cursor:pointer;padding:0;transition:border-color .12s ease,box-shadow .12s ease}
/* selectable control: hover brightens the edge, it never resizes (.sel ring is the marker) */
.color-pop-swatch:hover{border-color:var(--color-ink)}
.color-pop-swatch.sel{box-shadow:0 0 0 2px var(--color-overlay),0 0 0 4px var(--accent)}
.color-pop-add{display:flex;align-items:center;justify-content:center;background:transparent!important;border-style:dashed!important;color:var(--text-soft);font-size:16px;line-height:1;font-weight:400}
.color-pop-add:hover{border-color:var(--accent)!important;color:var(--text);background:var(--color-overlay-hover)!important}
.color-pop-custom{position:relative}
.color-pop-del{position:absolute;top:-6px;right:-6px;width:16px;height:16px;border-radius:50%;border:1px solid var(--line2);background:var(--color-overlay);color:var(--text-soft);font-size:12px;line-height:1;display:none;align-items:center;justify-content:center;padding:0;cursor:pointer;z-index:2}
.color-pop-custom:hover .color-pop-del,.color-pop-del:focus{display:flex}
.color-pop-del:hover{color:#fff;background:var(--color-danger);border-color:var(--color-danger)}
/* hidden wheel input — feeds the picked colour into the popover without committing the live target */
#cpWheel{position:absolute!important;right:10px;bottom:10px;width:1px!important;height:1px!important;min-width:0!important;opacity:0;pointer-events:none;border:0!important;padding:0!important;margin:0!important}
.color-pop-row{display:flex;align-items:center;gap:7px;margin-top:10px}
.color-pop-prev{width:30px;height:30px;flex:0 0 auto;border-radius:var(--radius-chip);border:1px solid var(--line2)}
.color-pop-hex{flex:1 1 auto;min-width:62px!important;width:auto!important;height:30px!important;padding:0 8px!important;font-size:14px;text-transform:lowercase}
.color-pop-row .btn.small{flex:0 0 auto;width:auto!important;padding:0 10px!important;white-space:nowrap}

/* ===== AI "Polish" feature (Beta 1.3) ===== */
.polish-bar{display:flex;align-items:center;gap:10px;margin-top:8px;flex-wrap:wrap}
.polish-btn{white-space:nowrap}
.polish-status{font-size:12px;line-height:1.4;min-width:0;flex:1 1 140px}
.polish-status .muted{color:var(--text-soft)}
.polish-ok{color:#7fd1a8}
.polish-err{color:#e88b8b;word-break:break-word}
.polish-card{margin-top:10px;border-radius:var(--radius-md);padding:12px;background:rgba(255,255,255,.04)}
.polish-card.hidden{display:none}
.polish-card-head{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px}
.polish-tag{font-weight:600;font-size:14px}
.polish-prov{font-size:11px;color:var(--text-soft);white-space:nowrap}
.polish-suggest{font-size:14px;line-height:1.55;padding:9px 11px;border-radius:var(--radius-sm);background:rgba(127,209,168,.10);border:1px solid rgba(127,209,168,.34)}
.polish-diff-wrap{margin-top:8px}
.polish-diff-wrap>summary{cursor:pointer;font-size:12px;color:var(--text-soft);list-style:revert}
.polish-diff{margin-top:6px;font-size:14px;line-height:1.7;padding:8px 10px;border-radius:var(--radius-sm);background:rgba(0,0,0,.22);word-break:break-word}
.polish-diff ins{background:rgba(127,209,168,.26);text-decoration:none;border-radius:3px;padding:0 2px}
.polish-diff del{background:rgba(204,107,107,.24);border-radius:3px;padding:0 2px}
.polish-notes{margin-top:10px}
.polish-notes-h{font-size:12px;color:var(--text-soft);margin-bottom:4px}
.polish-note{display:flex;gap:8px;align-items:flex-start;font-size:14px;line-height:1.45;padding:3px 0}
.polish-note input{margin-top:3px;flex:0 0 auto}
.polish-actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
.ai-key-inputs{display:flex;gap:8px}
.ai-key-inputs input{flex:1 1 auto;min-width:0}
.ai-key-hint{font-weight:400;font-size:11px;color:var(--text-soft)}
/* g418 — the row needs to end somewhere. Owner: Test Connection is touching the encrypted-sync
   card below it. Measured: `.ai-test-row` carries `margin-bottom:0`, and the sync block is not
   its sibling — it follows the whole form grid — so nothing between them was spending any space
   at all and the button sat flush against the next card's edge.
   24 is gap-group's narrow rung: the test control and the sync settings are separate groups, and
   §5 invariant 5 wants the space between groups to beat the space inside them, which here is the
   row's own 10px. The gap also goes 10 -> 12, gap-inline, since 10 is on no ramp. */
.ai-test-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:24px}
.ai-test-out{font-size:12px;line-height:1.4;word-break:break-word}
.ai-log-row{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin:16px 0 4px;padding-top:12px;border-top:1px solid var(--line2);font-size:12px}
.ai-log-btns{display:flex;gap:8px;flex-wrap:wrap}
.ai-model-custom{margin-top:8px}
.ai-keys-saved{display:block;margin-top:8px;font-size:11px;color:var(--text-soft)}
.ai-key-detect{display:block;margin-top:6px;font-size:12px;color:var(--muted)}
.ai-key-detect.ok{color:#9FD8B6}
.ai-key-detect.warn{color:#E8C68A}
.ai-or-note{display:block;margin-top:6px;font-size:12px;line-height:1.45;color:var(--muted)}
.ai-or-note.hidden{display:none}
.ai-embed-note{display:block;font-size:12px;line-height:1.45;color:var(--muted)}
.ai-embed-note.ok{color:#9FD8B6}
:root[data-mode="light"] .ai-embed-note.ok{color:#1f7a4d}
/* ---- Live model discovery note + Refresh (Beta 2.4) ---- */
.ai-models-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-top:8px;flex-wrap:wrap}
.ai-models-note{flex:1 1 60%;min-width:0;font-size:12px;line-height:1.45;color:var(--muted)}
.ai-models-note.ok{color:#9FD8B6}
.ai-models-note.warn{color:#E8C68A}
.ai-models-row .btn.small{flex:0 0 auto;white-space:nowrap}
.ai-models-row .btn.hidden{display:none}
:root[data-mode="light"] .ai-models-note.ok{color:#1f7a4d}
.ai-cap-summary{display:flex;flex-direction:column;gap:4px;margin-top:8px;font-size:12px;line-height:1.4;color:var(--muted)}
.ai-cap-summary>b{color:var(--text-soft)}
.ai-cap-summary strong.yes{color:var(--ok,var(--color-success))}
.ai-cap-summary strong.no{color:var(--muted);font-weight:500}
/* ---- Knowledge Base (RAG Library) modal (Beta 2.4) ---- */
.ai-kb-modal{max-width:640px}
.kb-topbar{display:flex;align-items:center;gap:12px;margin:10px 0 12px;flex-wrap:wrap}
.kb-progress{font-size:12px;color:var(--muted)}
.kb-progress.hidden{display:none}
.kb-warn{margin:8px 0;padding:8px 12px;border-radius:8px;font-size:12px;line-height:1.45;border:1px solid rgba(197,179,88,.4);background:rgba(150,120,40,.12);color:#E8D3A8}
.kb-model{margin:10px 0 4px}
.kb-model-lbl{display:block;font-size:12px;margin-bottom:5px}
.kb-model-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.kb-model-row select{max-width:100%}
.kb-model-warn{font-size:12px}
.kb-error{margin-top:10px;padding:8px 12px;border-radius:8px;font-size:12px;border:1px solid rgba(200,90,90,.4);background:rgba(160,60,60,.12);color:#E8A8A8}
.kb-empty,.kb-loading{padding:16px;text-align:center;color:var(--muted);font-size:14px;border-radius:10px;background:var(--color-surface2)}
.kb-list{display:flex;flex-direction:column;gap:8px;margin:4px 0 4px}
.kb-doc{display:flex;align-items:center;gap:10px;justify-content:space-between;padding:9px 12px;border-radius:10px;background:var(--panel,rgba(255,255,255,.02))}
.kb-doc-title{font-weight:600;font-size:14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.kb-doc-meta{font-size:12px;color:var(--muted);margin-top:3px}
.kb-doc-acts{display:flex;gap:6px;flex-shrink:0}
.kb-kind{font-size:11px;padding:1px 7px;border-radius:var(--radius-chip);border:1px solid var(--border);color:var(--text-soft);text-transform:uppercase;letter-spacing:.03em}
.kb-kind-sop{border-color:rgba(80,170,120,.4);color:#9FD8B6}
.kb-status{text-transform:capitalize}
.kb-status.ok{color:#9FD8B6}
.kb-status.err{color:#E8A8A8}
.kb-status.idx{color:#E8C68A}
.kb-add{margin-top:14px;padding-top:14px;border-top:1px solid var(--border)}
.kb-add-h{font-size:14px;margin:0 0 8px}
.kb-files{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.kb-url-row{display:flex;gap:8px;align-items:center}
.kb-url-row input{flex:1;min-width:0}
.kb-add-acts{margin-top:10px}
:root[data-mode="light"] .kb-kind-sop,:root[data-mode="light"] .kb-status.ok{color:#1f7a4d}

/* ---- Axi memory & rulebook (Beta 2.9) ---- */
.axi-memory-modal{width:min(1040px,100%)}
.axi-memory-section{margin-top:14px;padding:16px;border-radius:var(--radius-md);background:var(--panel,rgba(255,255,255,.025));min-width:0}
.axi-memory-section-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}
.axi-memory-section-head h3{margin:0;color:var(--text);font-size:16px;line-height:1.35}
.axi-memory-section-head p{margin:4px 0 0;color:var(--muted);font-size:12px;line-height:1.5}
.axi-memory-permanent{display:inline-flex;margin-left:6px;padding:2px 7px;border-radius:var(--radius-chip);color:var(--muted);font-size:11px;line-height:1.4;letter-spacing:.06em;text-transform:uppercase;vertical-align:2px;border:1px solid var(--color-line)}
.axi-rulebook{border-radius:10px;background:var(--paper2);overflow:hidden}
.axi-rulebook>summary{padding:11px 13px;color:var(--text-soft);font-size:14px;font-weight:650;cursor:pointer;list-style-position:inside}
.axi-rulebook[open]>summary{border-bottom:1px solid var(--line2)}
/* g443 — `scrollbar-width:thin` removed. g436 gave this scroller a `::-webkit-scrollbar` thumb,
   and this declaration was silently cancelling it in Chromium (>=121, the two are mutually
   exclusive). Firefox keeps the thin bar via the query at the end of the modal scrollbar block. */
.axi-rulebook-scroll{max-height:min(48vh,520px);overflow:auto;padding:16px}
.axi-rulebook-doc{font-size:12px;line-height:1.58;color:var(--text-soft);overflow-wrap:anywhere}
.axi-rulebook-doc h1,.axi-rulebook-doc h2,.axi-rulebook-doc h3,.axi-rulebook-doc h4,.axi-rulebook-doc h5,.axi-rulebook-doc h6{margin:18px 0 8px;color:var(--text);line-height:1.35}
.axi-rulebook-doc h1:first-child{margin-top:0}.axi-rulebook-doc h1{font-size:16px}.axi-rulebook-doc h2{font-size:16px}.axi-rulebook-doc h3{font-size:16px}.axi-rulebook-doc h4,.axi-rulebook-doc h5,.axi-rulebook-doc h6{font-size:14px}
.axi-rulebook-doc p{margin:0 0 7px}.axi-rulebook-doc hr{border:0;border-top:1px solid var(--line2);margin:18px 0}
.axi-rulebook-doc code{padding:1px 4px;border-radius:4px;background:rgba(255,255,255,.06);font-size:12px}
.axi-rulebook-doc blockquote{margin:5px 0;padding:6px 10px;border-left:1px solid var(--line-state);background:rgba(255,255,255,.025);color:var(--text-soft)}
.axi-rule-item{display:grid;grid-template-columns:24px minmax(0,1fr);gap:4px;margin:3px 0 3px 12px}
.axi-rule-gap{height:6px}
.axi-rule-example{margin:10px 0;padding:11px;border-radius:8px;background:rgba(255,255,255,.02)}
.axi-rule-example h3{margin-top:0}.axi-rule-example>div{margin-top:7px}
.axi-rule-example pre{display:inline;white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font:inherit;color:var(--text-soft)}
.axi-memory-learned{display:flex;flex-direction:column;gap:12px;min-width:0}
.axi-memory-toggle{display:flex;align-items:flex-start;gap:10px;padding:12px;border:1px solid var(--line2);border-radius:10px;background:var(--paper2);cursor:pointer}
.axi-memory-toggle input{flex:0 0 auto;width:18px;height:18px;margin:2px 0 0;accent-color:var(--accent-strong)}
.axi-memory-toggle span{display:flex;flex-direction:column;gap:3px;min-width:0}.axi-memory-toggle b{color:var(--text);font-size:14px}.axi-memory-toggle small{color:var(--muted);font-size:12px;line-height:1.45}
.axi-memory-docbox{display:flex;flex-direction:column;gap:9px;min-width:0}
.axi-memory-docbox textarea{box-sizing:border-box;width:100%;min-width:0;min-height:260px;max-height:48vh;padding:12px;resize:vertical;border:1px solid var(--line2);border-radius:10px;background:var(--bg);color:var(--text);font:500 13px/1.55 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
.axi-memory-docbox textarea:focus{outline:none;border-color:var(--color-focus);box-shadow:0 0 0 2px rgba(242,242,242,.12)}
.axi-memory-doc-meta{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;color:var(--muted);font-size:12px;line-height:1.4}
.axi-memory-doc-meta b{color:var(--text-soft);font-variant-numeric:tabular-nums;white-space:nowrap}
.axi-memory-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.axi-memory-actions .btn{max-width:100%;white-space:normal}
.axi-memory-empty,.axi-memory-signedout{padding:18px;border-radius:10px;color:var(--muted);font-size:14px;text-align:center;background:var(--color-surface2)}
@media(max-width:560px){
  .axi-memory-modal{padding:14px}.axi-memory-section{padding:12px}
  .axi-rulebook-scroll{max-height:52vh;padding:12px}
  .axi-memory-docbox textarea{min-height:220px;padding:10px;font-size:12px}
  .axi-memory-actions .btn:last-child{flex:1 1 100%}
}

/* Inset custom dropdown chevron — the native <select> arrow hugs the right edge and
   looks inconsistent; replace it with our own chevron sitting 14px in from the edge.
   !important is needed to win over the later "skin" rules that set `background` on selects. */
select{
  -webkit-appearance:none;-moz-appearance:none;appearance:none;
  padding-right:42px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23a0a6b0' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")!important;
  background-repeat:no-repeat!important;
  background-position:right 14px center!important;
}
select:focus{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23c2c8d2' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")!important;
  background-repeat:no-repeat!important;
  background-position:right 14px center!important;
}

/* Custom dropdown — styled replacement for the native <select> popup, consistent with
   the Settings menu. The real <select> is hidden but kept for value/form/change logic. */
.cdd{position:relative;min-width:0}
/* Let form-grid items shrink below their content's min-content so a long, non-wrapping
   dropdown label can't blow the grid wider than the modal on narrow screens. */
.form-grid>div{min-width:0}
.cdd>select{display:none}
.cdd-trigger{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-width:0;overflow:hidden;text-align:left;border:1px solid rgba(255,255,255,.10);border-radius:var(--radius-sm);padding:14px 16px;background:#15161a;color:var(--text-soft);font-size:var(--fs-body);font-weight:var(--fw-body);line-height:1.3;cursor:pointer}
.cdd-trigger:hover{border-color:rgba(255,255,255,.17)}
.cdd.open .cdd-trigger,.cdd-trigger:focus-visible{border-color:rgba(255,255,255,.30);background:#181a20;outline:none}
.cdd-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.cdd-chev{flex:0 0 auto;width:14px;height:14px;fill:none;stroke:var(--color-muted);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;transition:transform .15s}
.cdd.open .cdd-chev{transform:rotate(180deg)}
.cdd-panel{position:fixed;z-index:9999;display:none;overflow-y:auto;background:var(--color-overlay);border:1px solid var(--color-linestrong);border-radius:var(--radius-card);padding:7px;box-shadow:0 14px 34px rgba(0,0,0,.55)}
.cdd.open .cdd-panel{display:block}
.cdd-opt{display:block;width:100%;text-align:left;border:1px solid transparent;background:transparent;color:var(--text-soft);border-radius:8px;padding:9px 11px;font-size:var(--fs-body);line-height:1.35;white-space:normal;cursor:pointer}
/* Search field for large dropdowns (e.g. OpenRouter's 300+ models) — sticks to the top while options scroll. */
.cdd-search{position:sticky;top:-7px;z-index:3;display:block;box-sizing:border-box;width:calc(100% + 14px);margin:-7px -7px 6px;border:0;border-bottom:1px solid var(--line2);background:var(--paper2);color:var(--text);padding:9px 12px;font-size:14px;line-height:1.3;outline:none;border-radius:12px 12px 0 0}
.cdd-search::placeholder{color:var(--muted)}
.cdd-opt[hidden]{display:none}   /* filtered-out options — .cdd-opt sets display:block, which would otherwise override the [hidden] default */
.cdd-opt:hover,.cdd-opt.active{background:var(--hover);border-color:var(--line)}
.cdd-opt.sel{color:var(--text);background:color-mix(in srgb, var(--text) 12%, transparent);border-color:var(--color-linestrong)}

/* Step notes editor (inside the Edit Step modal; the table shows notes read-only) */
.step-notes{margin-top:14px}
.step-notes-head{font-size:12px;color:var(--text-soft);margin-bottom:8px}
.step-notes-list{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}
.step-note-row{display:flex;gap:8px;align-items:flex-start}
.step-note-input{flex:1 1 0;min-width:0;min-height:54px;resize:vertical}
.step-note-row .note-del{flex:0 0 auto;width:auto!important;align-self:flex-start}

/* ===== AI review panel (Beta 1.4 — AI Checking V1) ===== */
.ai-check{margin-top:18px;border-top:2px solid var(--accent-strong,var(--accent))}
.ai-beta-tag{display:inline-block;vertical-align:middle;margin-left:8px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;color:var(--text-soft);background:rgba(255,255,255,.08);border:1px solid var(--line);padding:2px 8px;border-radius:var(--radius-chip)}
.ai-prov-line{display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:14px;color:var(--muted);margin-bottom:12px}
.ai-prov-line b{color:var(--text-soft);font-weight:600}
.ai-model{font-family:var(--font-mono,ui-monospace,monospace);font-size:12px;color:var(--text-soft)}
.ai-prov-warn{color:#E8D3A8}
.ai-prov-note{font-size:12px;color:var(--muted)}
.ai-prov-line.is-local b{color:var(--ok,var(--color-success))}
.ai-prov-line.is-local .ai-prov-note{color:#9FD8B6}
/* Data-handling note — the plain "here's where your content goes" box in the AI settings modal. */
.ai-data-note{font-size:14px;line-height:1.55;color:var(--text-soft);background:rgba(70,185,138,.08);border-radius:var(--radius-md,10px);padding:11px 13px;margin:0 0 12px}
.ai-data-note b{color:var(--text)}
.ai-settings-link{margin-left:auto}
.ai-check-controls{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.ai-scope{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}
.ai-depth{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}
.ai-autorun-btn{border-style:dashed}
.ai-restored{color:var(--muted);font-size:12px;margin-left:2px}
.ai-scope-label{font-size:14px;color:var(--muted);flex:0 0 auto}
.ai-scope-btn{font-size:14px;padding:6px 13px;border-radius:var(--radius-chip);border:1px solid var(--line);background:transparent;color:var(--text-soft);cursor:pointer;white-space:nowrap}
.ai-scope-btn:hover:not([disabled]){border-color:rgba(255,255,255,.32);background:rgba(255,255,255,.04)}
/* Selected toggle = subtle tonal step, NOT a harsh white fill (white is reserved for the one
   primary action per screen). Matches the .theme-mode-btn.sel treatment. */
.ai-scope-btn.sel{background:color-mix(in srgb,var(--text) 12%,transparent);border-color:var(--color-linestrong);color:var(--text);font-weight:var(--fw-control)}
/* g447 — the "Fix with Axi" / "Fix All" rules that lived here are DELETED, not amended. Three
   places in this stylesheet described this button's resting state and all three disagreed: an
   outlined transparent button here, ghost text further down, and a control-filled button at the
   end. The last one wins (it is !important and last) and is the owner-approved g416 decision, so
   the other two never reached the screen — they only made the file lie about what it does.
   This is the cleanup g416 recorded as owed. The surviving rule is at the end of this file. */
.ai-scope-btn[disabled]{opacity:.4;cursor:not-allowed}
/* Legend "+ Add role/icon" header buttons: a one-tone-step fill so they read on the
   near-black legend card (a bare transparent secondary vanishes on true black). */
.legend-add .btn,.legend-add .menu-summary{background:var(--color-control);border-color:var(--color-linestrong)}
.legend-add .btn:hover,.legend-add .menu-summary:hover{background:var(--color-hover);border-color:var(--color-ink)}
/* This is a BADGE (status recipe, chip radius, and the border it keeps is the one the line
   language explicitly allows badges) — but it was missing the half of the badge rung that makes
   11px legible: tracked caps. 0.08em is the badge tracking, deliberately not the 0.12em eyebrow
   rung, because a badge is a compact object rather than a chrome label. Owner call 2026-08-14. */
.ai-weight{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.08em;padding:4px 10px;border-radius:var(--radius-chip);border:1px solid var(--line);white-space:nowrap;color:var(--muted)}
.ai-weight.w-light{color:#9FD8B6;border-color:rgba(80,170,120,.4);background:rgba(60,150,100,.12)}
.ai-weight.w-medium{color:#CFE0A8;border-color:rgba(150,170,80,.4);background:rgba(120,140,50,.12)}
.ai-weight.w-heavy{color:#E8D3A8;border-color:rgba(197,179,88,.45);background:rgba(150,120,40,.14)}
.ai-weight.w-too-heavy{color:#F1C4C0;border-color:rgba(180,35,24,.5);background:rgba(180,35,24,.14)}
.ai-run{flex:0 0 auto}
.ai-warn{font-size:14px;line-height:1.5;color:#F1C4C0;background:rgba(180,35,24,.10);border:1px solid rgba(180,35,24,.4);border-radius:var(--radius-md);padding:10px 12px;margin:0 0 10px}
.ai-privacy{font-size:12px;line-height:1.5;color:var(--muted);margin:0 0 14px}
.ai-result-meta{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:12px;color:var(--muted);margin:4px 0 10px;padding-top:12px;border-top:1px solid var(--line2)}
.ai-clear{margin-left:auto}
.ai-check-loading{display:flex;align-items:center;gap:10px;font-size:14px;color:var(--text-soft);padding:14px 0}
.spin{width:16px;height:16px;border-radius:50%;border:2px solid var(--line2);border-top-color:var(--accent-strong,var(--text-soft));display:inline-block;animation:aiSpin .8s linear infinite;flex:0 0 auto}
@keyframes aiSpin{to{transform:rotate(360deg)}}
.ai-check-error{font-size:14px;line-height:1.5;color:#F1C4C0;background:rgba(180,35,24,.10);border:1px solid rgba(180,35,24,.4);border-radius:var(--radius-md);padding:12px;word-break:break-word}
.ai-check-clean{font-size:14px;color:#9FD8B6;padding:10px 0}
.ai-issue-list{display:flex;flex-direction:column;gap:10px}
.ai-issue{border-left:3px solid var(--color-linestrong);border-radius:var(--radius-md);padding:12px 14px;background:rgba(255,255,255,.03)}
.ai-issue.sev-high{border-left-color:var(--error)}
.ai-issue.sev-medium{border-left-color:rgba(197,179,88,.7)}
.ai-issue.sev-low{border-left-color:rgba(131,156,188,.6)}
.ai-issue-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px}
.ai-sev{font-size:11px;letter-spacing:.05em;text-transform:uppercase;font-weight:700;padding:3px 9px;border-radius:var(--radius-chip)}
.ai-sev.high{background:rgba(180,35,24,.18);color:#F1C4C0}
.ai-sev.medium{background:rgba(150,120,40,.20);color:#E8D3A8}
.ai-sev.low{background:rgba(131,156,188,.18);color:var(--color-info)}
.ai-type{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
.ai-loc{font-size:12px;color:var(--text-soft);margin-left:auto}
.ai-issue-reason{font-size:14px;line-height:1.5;color:var(--text-soft)}
.ai-issue-fix{font-size:14px;line-height:1.5;color:var(--muted);margin-top:6px}
.ai-fix-label{color:var(--text-soft);font-weight:600}
.ai-fix-rep{margin-top:8px;font-size:14px}
.ai-fix-rep summary{cursor:pointer;color:var(--muted);font-size:12px}
.ai-fix-rep-body{margin-top:6px;padding:8px 10px;border-radius:var(--radius-sm);background:rgba(255,255,255,.04);color:var(--text-soft);line-height:1.5}
.ai-fix-notes-h{margin-top:8px;font-size:12px;color:var(--muted)}
.ai-fix-note{white-space:pre-wrap}
.ai-result-meta-btns{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap}
.ai-issue-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px}
/* Keep the destructive per-suggestion Delete away from Jump/Apply/Fix-with-Axi so it isn't mis-hit:
   pushed to the far end of the row. On narrow screens where the row wraps it drops to its own line. */
.ai-issue-actions .ai-dismiss{margin-left:auto}
@media (max-width:560px){.ai-issue-actions .ai-dismiss{margin-left:auto;flex:0 0 auto}}
.ai-applied{font-size:12px;color:#9FD8B6}
.ai-cat{font-size:11px;color:var(--text-soft);background:var(--hover);border:1px solid var(--line);border-radius:var(--radius-chip);padding:2px 9px;letter-spacing:.02em}

/* Final-review readiness report (Beta 1.9) */
.readiness-report{border-radius:var(--radius-md);padding:14px 16px;margin:4px 0 14px;background:rgba(255,255,255,.03)}
.readiness-report.rr-ready{border-color:rgba(123,170,150,.5);background:rgba(47,74,67,.16)}
.readiness-report.rr-review{border-color:rgba(197,179,88,.45);background:rgba(150,120,40,.12)}
.readiness-report.rr-blocked{border-color:rgba(180,35,24,.5);background:rgba(180,35,24,.12)}
.rr-head{display:flex;align-items:flex-start;gap:12px}
.rr-icon{flex:0 0 auto;width:30px;height:30px;border-radius:var(--radius-chip);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px}
.rr-ready .rr-icon{background:rgba(123,170,150,.22);color:#CDE6D8}
.rr-review .rr-icon{background:rgba(197,179,88,.22);color:#E8D3A8}
.rr-blocked .rr-icon{background:rgba(180,35,24,.22);color:#F1C4C0}
.rr-headline{font-size:16px;font-weight:600;color:var(--text-strong,#fff)}
.rr-sub{font-size:14px;line-height:1.5;color:var(--text-soft);margin-top:2px}
.rr-copy{flex:0 0 auto;margin-left:auto}
.rr-sevs,.rr-cats{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:12px}
.rr-sev{font-size:12px;font-weight:600;padding:3px 10px;border-radius:var(--radius-chip);letter-spacing:.02em}
.rr-sev.high{background:rgba(180,35,24,.18);color:#F1C4C0}
.rr-sev.med{background:rgba(150,120,40,.20);color:#E8D3A8}
.rr-sev.low{background:rgba(131,156,188,.18);color:var(--color-info)}
.rr-cats-label{font-size:12px;color:var(--muted)}
.rr-cat{font-size:12px;color:var(--text-soft);background:rgba(255,255,255,.05);border-radius:var(--radius-chip);padding:3px 10px}
.rr-cat b{color:var(--text-strong,#fff)}
@media (max-width:560px){
  .ai-check-controls{align-items:stretch}
  .ai-run{width:100%}
  .ai-loc{margin-left:0;width:100%}
  .rr-copy{margin-left:0;width:100%;margin-top:8px}
  .rr-head{flex-wrap:wrap}
}

/* AI review — workflow picker (Beta 1.4) */
.ai-wf-pick{font-size:14px;padding:6px 10px;border-radius:8px;border:1px solid var(--line);background:var(--paper2,rgba(255,255,255,.05));color:var(--text-soft);max-width:100%;min-width:0;cursor:pointer}
.ai-wf-pick:hover{border-color:rgba(255,255,255,.32)}
@media (max-width:560px){.ai-wf-pick{flex:1 1 100%;width:100%}}

/* ============================================================================
 * AI DRAFTING (Beta 1.5) — draft markers + the skeleton-drafting modal.
 * Draft = an amber marker on AI-generated content until the author marks it final.
 * ========================================================================== */
/* Axi glyph on AI buttons — the exact artwork from the Executive Comms Assistant app
   (public/axi-glyph.png), embedded as a currentColor mask so it stays crisp and themed. */
/* The Axi mark is the AXI EXECUTIVE ASSISTANT glyph (axi-branding/icons/axi-glyph-white-1024.png),
   not the older mark this used to carry. Rebuilt for the mask rather than dropped in raw: cropped to
   its alpha bounding box, then re-padded onto a 96x96 canvas at 84px wide so it occupies exactly the
   footprint the previous mask did (ink 84x70 at L6/R6/T13/B13 against the old 84x69 at L6/R6/T14/B13).
   That matters because the rule masks with `center/contain` — canvas padding, not just the artwork,
   decides the rendered size, so a raw 1024 master with its own margins would have rendered smaller.
   One definition feeds all 26 aiGlyph() call sites. */
.ai-glyph{display:inline-block;width:1.05em;height:1.05em;flex:0 0 auto;margin-right:.4em;vertical-align:-.13em;background-color:currentColor;-webkit-mask:url(../img/axi-glyph.png) center/contain no-repeat;mask:url(../img/axi-glyph.png) center/contain no-repeat}
.draft-pill{display:inline-block;vertical-align:middle;font-size:11px;letter-spacing:.06em;text-transform:uppercase;font-weight:700;color:#E8C68A;background:rgba(192,136,58,.16);border:1px solid rgba(192,136,58,.5);padding:2px 8px;border-radius:var(--radius-chip);white-space:nowrap;line-height:1.4}
.draft-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#C0883A;box-shadow:0 0 0 2px rgba(192,136,58,.22);margin-left:6px;flex:0 0 auto}
.is-draft{border-left:3px solid rgba(192,136,58,.6)!important}
.draft-banner{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin:0 0 14px;padding:10px 14px;border:1px solid rgba(192,136,58,.4);background:rgba(192,136,58,.08);border-radius:var(--radius-md)}
.draft-banner-msg{flex:1 1 240px;min-width:0;font-size:14px;line-height:1.5;color:var(--text-soft)}
.btn.ok{border-color:rgba(70,185,138,.55);color:#A9E2C4}
.btn.ok:hover{background:rgba(70,185,138,.12);border-color:rgba(70,185,138,.8)}
.item-intent{margin-top:6px;font-size:var(--fs-meta);line-height:1.5;color:var(--muted);max-width:70ch}
.item-intent-tag{display:inline-block;font-size:11px;letter-spacing:.05em;text-transform:uppercase;font-weight:700;color:var(--muted2);border:1px solid var(--line);border-radius:4px;padding:1px 5px;margin-right:6px;vertical-align:middle}
.wf-draft-banner{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin:0 0 16px;padding:10px 14px;border-radius:var(--radius-md);background:var(--paper2)}
.wf-draft-banner.is-draft{border-color:rgba(192,136,58,.4);background:rgba(192,136,58,.07)}
.wf-intent{flex:1 1 240px;min-width:0;font-size:14px;line-height:1.5;color:var(--text-soft)}
.wf-intent b{color:var(--text);font-weight:600}

/* The drafting modal */
.ai-draft-modal{width:min(880px,100%)}
.ai-draft-qs{display:flex;flex-direction:column;gap:14px;margin-top:4px}
.ai-draft-q label{display:block;font-size:14px;font-weight:var(--fw-control);color:var(--text-soft);line-height:1.4;margin-bottom:4px}
.ai-draft-hint{font-size:12px;line-height:1.5;color:var(--muted2);margin-bottom:6px}
.ai-draft-q textarea{resize:vertical}
.ai-draft-advisories{margin:0 0 16px;padding:12px 14px;border-left:3px solid rgba(131,156,188,.6);border-radius:var(--radius-md);background:rgba(255,255,255,.03)}
.ai-draft-adv-h{font-size:14px;font-weight:var(--fw-control);color:var(--text-soft);margin-bottom:6px}
.ai-draft-advisories ul{margin:0;padding-left:20px}
.ai-draft-advisories li{font-size:14px;line-height:1.55;color:var(--muted);margin:3px 0}
.ai-draft-roles{margin:0 0 14px;padding:12px 14px;border-left:3px solid rgba(131,156,188,.6);border-radius:var(--radius-md);background:rgba(255,255,255,.03)}
.ai-draft-role-cards{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.ai-draft-role-card{display:inline-flex;align-items:center;gap:8px;padding:5px 8px 5px 6px;border-radius:var(--radius-chip);background:var(--paper2);font-size:14px;color:var(--text-soft)}
.ai-draft-role-hint{font-size:12px;line-height:1.5;color:var(--muted);margin-top:9px}
.ai-draft-tree{display:flex;flex-direction:column;gap:14px;margin:4px 0 12px}
.ai-draft-sec{border-radius:var(--radius-md);background:var(--paper2);padding:12px}
.ai-draft-sec-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.ai-draft-sec-tag{font-size:11px;letter-spacing:.05em;text-transform:uppercase;font-weight:700;color:var(--muted2);border:1px solid var(--line);border-radius:4px;padding:2px 6px;white-space:nowrap}
.ai-draft-title{flex:1 1 200px;min-width:0;font-weight:var(--fw-control)}
.ai-draft-wfs{display:flex;flex-direction:column;gap:10px;margin-top:12px;padding-left:12px;border-left:1px dashed var(--line2)}
.ai-draft-wf{display:flex;flex-direction:column;gap:6px}
.ai-draft-wf-head{display:flex;align-items:center;gap:8px}
.ai-draft-wtitle{flex:1 1 auto;min-width:0}
.ai-draft-wf-head .btn.small.danger{flex:0 0 auto;width:38px;padding:0}
.ai-draft-intent{resize:vertical;font-size:14px}
.ai-draft-wfs > .btn.small{align-self:flex-start}
@media (max-width:560px){
  .ai-draft-sec-head{align-items:stretch}
  .ai-draft-title,.ai-draft-wtitle{flex-basis:100%}
}

/* Workflow-from-intent: step preview + banner actions */
.wf-banner-actions{display:flex;gap:8px;flex-wrap:wrap;margin-left:auto}
.ai-draft-steps{display:flex;flex-direction:column;gap:12px;margin:4px 0 12px}
.ai-draft-step{border-radius:var(--radius-md);background:var(--paper2);padding:10px 12px}
.ai-draft-step-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px}
.ai-draft-step-n{flex:0 0 auto;width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:var(--accent-muted);border:1px solid var(--line2);font-size:12px;font-weight:700;color:var(--text-soft)}
.ai-draft-step-type{flex:0 1 150px;min-width:0}
.ai-draft-step-role{flex:1 1 160px;min-width:0}
.ai-draft-step-moves{display:flex;gap:6px;margin-left:auto;flex:0 0 auto}
.ai-draft-step-moves .btn.small{width:34px;padding:0}
.ai-draft-step-desc{resize:vertical;font-size:14px}
.ai-draft-step-notes{margin-top:8px;padding:8px 10px;border-radius:var(--radius-sm);background:var(--fill);color:var(--muted);font-size:12px;line-height:1.55;white-space:pre-line}
/* Item B — drafted decision sub-paths, previewed read-only under their step */
.ai-draft-branches{margin-top:8px;display:flex;flex-direction:column;gap:8px}
.ai-draft-branch{border-left:1px solid var(--line-state);padding:6px 10px;background:rgba(255,255,255,.025);border-radius:0 var(--radius-sm) var(--radius-sm) 0}
.ai-draft-branch-h{font-size:11px;font-weight:700;color:var(--text-soft);margin-bottom:5px}
.ai-draft-branch-step{display:flex;flex-direction:column;gap:1px;padding:4px 0;font-size:12px;line-height:1.5;border-top:1px solid var(--line)}
.ai-draft-branch-step:first-of-type{border-top:none}
.ai-draft-bs-meta{font-size:11px;font-weight:600;letter-spacing:.03em;text-transform:uppercase;color:var(--muted);opacity:.8}
.ai-draft-bs-desc{color:var(--text-soft)}
@media (max-width:560px){
  .ai-draft-step-type,.ai-draft-step-role{flex-basis:100%}
  .ai-draft-step-moves{margin-left:0}
  .wf-banner-actions{margin-left:0;width:100%}
}
/* Draft step rows: a subtle amber spine on the first cell (stacked-card view too) */
.step-row.is-draft-step td:first-child{box-shadow:inset 3px 0 0 rgba(192,136,58,.65)}
@media (max-width:720px){.step-row.is-draft-step{box-shadow:inset 3px 0 0 rgba(192,136,58,.55)}}

/* ============================================================================
 * AI WORKFLOW ANALYSIS (Beta 1.6) — the workflow-review modal (tabs + findings).
 * Reuses the .ai-issue / .ai-sev / .ai-weight / .ai-prov-line vocabulary from the
 * Check panel; only the modal shell, the tab strip, the lens chip, and the output
 * panes (summary / responsibilities / explain / checklist / suggested order) are new.
 * ========================================================================== */
.ai-analyze-modal{width:min(880px,100%)}
.ai-az-subtitle{font-size:14px;color:var(--text-soft);margin:-4px 0 0;font-weight:var(--fw-control)}
.ai-scope-static{font-size:14px;padding:6px 13px;border-radius:var(--radius-chip);border:1px solid var(--accent-strong,var(--accent));background:var(--accent-strong,var(--accent));color:var(--text);white-space:nowrap}
.ai-lens{font-size:11px;letter-spacing:.04em;text-transform:uppercase;font-weight:700;color:var(--text-soft);background:rgba(131,156,188,.16);border:1px solid rgba(131,156,188,.4);padding:3px 9px;border-radius:var(--radius-chip)}
.ai-az-tabs{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 14px;border-bottom:1px solid var(--line2);padding-bottom:10px}
.ai-az-tab{font-size:14px;padding:6px 12px;border-radius:var(--radius-chip);border:1px solid var(--line);background:transparent;color:var(--text-soft);cursor:pointer;white-space:nowrap}
.ai-az-tab:hover{border-color:rgba(255,255,255,.32);background:rgba(255,255,255,.04)}
.ai-az-tab.sel{background:var(--accent-strong,var(--accent));border-color:var(--accent-strong,var(--accent));color:var(--text)}
.ai-az-count{display:inline-block;min-width:16px;text-align:center;font-size:11px;font-weight:700;padding:0 5px;margin-left:4px;border-radius:var(--radius-chip);background:rgba(0,0,0,.22)}
.ai-az-body{max-height:min(56vh,560px);overflow:auto;padding-right:4px}
.ai-az-prose{font-size:14px;line-height:1.6;color:var(--text-soft);max-width:72ch}
.ai-az-pre{white-space:pre-line}
.ai-az-roles{display:flex;flex-direction:column;gap:10px}
.ai-az-role{border-radius:var(--radius-md);padding:10px 12px;background:rgba(255,255,255,.03)}
.ai-az-role-h{display:flex;align-items:center;gap:8px;margin-bottom:5px;flex-wrap:wrap}
.role-chip-mini{display:inline-flex;align-items:center;justify-content:center;padding:2px 9px;border-radius:var(--radius-chip);border:1px solid var(--line);background:var(--accent-muted);font-size:12px;font-weight:700;color:var(--text-soft);white-space:nowrap}
.ai-az-role-name{font-size:14px;color:var(--muted)}
.ai-az-role-body{font-size:14px;line-height:1.55;color:var(--muted)}
.ai-az-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:12px;padding-top:12px;border-top:1px solid var(--line2)}
.ai-az-hint{font-size:12px;line-height:1.5}
.ai-az-checklist{margin:0;padding-left:0;list-style:none;display:flex;flex-direction:column;gap:8px}
.ai-az-checklist li{position:relative;padding:8px 12px 8px 34px;border-radius:var(--radius-sm);background:rgba(255,255,255,.03);font-size:14px;line-height:1.5;color:var(--text-soft)}
.ai-az-checklist li::before{content:"";position:absolute;left:12px;top:10px;width:14px;height:14px;border:1.5px solid var(--muted2);border-radius:3px}
.ai-az-order-note{font-size:14px;color:var(--muted);line-height:1.55;margin:0 0 10px}
.ai-az-order-list{margin:0;padding-left:22px;display:flex;flex-direction:column;gap:8px}
.ai-az-order-list li{display:flex;align-items:center;gap:10px;font-size:14px;color:var(--text-soft)}
.ai-az-order-lbl{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.ai-az-order-list .btn.small{flex:0 0 auto}
@media (max-width:560px){
  .ai-az-body{max-height:none}
  .ai-az-order-list li{flex-wrap:wrap}
}

/* Handover documents (Beta 1.9): the generated Markdown doc, reusing the chat bubble's
   typography but full-width + scrollable inside the modal. */
.hv-hint{font-size:14px;line-height:1.55;margin:8px 0}
.hv-controls{align-items:flex-start}
.hv-control-stack{display:flex;flex-direction:column;gap:8px;min-width:0;flex:1 1 auto}
.hv-scope-current{font-size:12px;color:var(--muted);min-width:0;max-width:min(100%,360px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hv-modal .hv-doc{max-width:none;align-self:stretch;width:100%;margin-top:6px;max-height:min(52vh,520px);overflow:auto;font-size:14px;line-height:1.6}
.hv-modal .hv-doc strong{color:var(--text-strong,#fff)}
@media (max-width:560px){ .hv-controls>.btn{width:100%}.hv-scope-current{flex:1 1 100%;max-width:100%;white-space:normal}.hv-modal .hv-doc{max-height:none} }

/* ============================================================================
 * AI SOURCE CONTEXT (Beta 1.7) — source-material modal + source citations.
 * Reuses the .ai-weight / .ai-privacy / .form-grid / .modal vocabulary.
 * ========================================================================== */
.ai-source-modal textarea[name="srcText"]{width:100%;font:inherit;line-height:1.5;resize:vertical}
.ai-source-meta{display:flex;align-items:center;gap:12px;margin-top:8px;font-size:12px}
/* Item D — source library: item list, context chip, add/edit form */
.src-context-chip{display:flex;align-items:center;flex-wrap:wrap;gap:7px;margin:0 0 12px;padding:8px 12px;border-radius:var(--radius-sm);background:rgba(255,255,255,.04);border:1px solid var(--line2);font-size:12px;color:var(--text-soft)}
.src-chip-dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:0 0 auto}
.src-chip-dot.on{background:var(--accent-strong)}
.src-list{display:flex;flex-direction:column;gap:8px;margin:0 0 14px}
.src-empty{padding:10px 0}
.src-item{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:var(--radius-md);background:var(--paper2)}
.src-item.off{opacity:.58}
.src-item-tog{flex:0 0 auto;display:inline-flex;cursor:pointer}
.src-item-tog input{width:16px;height:16px;cursor:pointer}
.src-item-body{flex:1 1 auto;min-width:0}
.src-item-label{font-size:14px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.src-item-tag{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent-strong);border:1px solid var(--line2);border-radius:var(--radius-chip);padding:1px 6px;margin-left:4px}
.src-item-meta{font-size:11px;color:var(--muted);margin-top:2px}
.src-item-acts{flex:0 0 auto;display:flex;gap:6px}
.src-add{border-top:1px solid var(--line);padding-top:14px;margin-top:4px}
.src-add-h{font-size:14px;margin:0 0 10px;color:var(--text-soft)}
.src-add-acts{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}
/* the Axi-panel "what's in context" chip */
.axi-src-row{padding:0 14px 8px}
.axi-src-chip{display:inline-flex;align-items:center;gap:7px;padding:5px 10px;border-radius:var(--radius-chip);border:1px solid var(--line2);background:rgba(255,255,255,.03);color:var(--text-soft);font:inherit;font-size:11px;font-weight:600;cursor:pointer}
.axi-src-chip:hover{border-color:var(--line-state);color:var(--text)}
/* Items J+K — chat activity-log line + the activity-log viewer */
.chat-log-line{display:flex;align-items:center;gap:6px;align-self:center;margin:2px 14px;padding:3px 10px;border-radius:var(--radius-chip);background:rgba(255,255,255,.03);color:var(--muted);font-size:11px;font-weight:600;max-width:90%}
.chat-log-line .ai-glyph{width:12px;height:12px;flex:0 0 auto}
.ai-log-list{display:flex;flex-direction:column;gap:6px;max-height:min(56vh,520px);overflow:auto;margin:4px 0}
.ai-log-item{padding:8px 12px;border-radius:var(--radius-md);background:var(--paper2)}
.ai-log-feat{font-size:14px;font-weight:600;color:var(--text)}
.ai-log-scope{font-weight:400;color:var(--muted)}
.ai-log-meta{font-size:11px;color:var(--muted);margin-top:2px}
/* Item I — versions & diff */
.ver-save-row{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 12px}
.ver-save-row input{flex:1 1 220px;min-width:0}
.ver-save-row .btn{flex:0 0 auto}
.ver-list{display:flex;flex-direction:column;gap:6px;max-height:min(34vh,300px);overflow:auto;margin:0 0 12px}
.ver-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--radius-md);background:var(--paper2)}
.ver-item.sel{border-color:var(--line-state);background:color-mix(in srgb, var(--text) 10%, transparent)}
.ver-item-body{flex:1 1 auto;min-width:0}
.ver-item-label{font-size:14px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ver-item-meta{font-size:11px;color:var(--muted);margin-top:2px}
.ver-item-acts{flex:0 0 auto;display:flex;gap:6px}
.ver-diff{border-top:1px solid var(--line);padding-top:12px}
.ver-diff-h{font-size:14px;margin:0 0 10px}
.vd-group{margin:0 0 12px}
.vd-group h4{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin:0 0 6px}
.vd-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
.vd-list li{font-size:12px;line-height:1.45;color:var(--text-soft);display:flex;gap:7px}
.vd-mark{flex:0 0 auto;font-weight:700;width:12px}
.vd-add .vd-mark{color:var(--color-success)}.vd-rem .vd-mark{color:var(--color-danger)}.vd-chg .vd-mark{color:#d9a441}
.ver-diff-legend{display:flex;gap:14px;font-size:11px;margin-top:8px;color:var(--muted)}
.ver-diff-legend .vd-add{color:var(--color-success)}.ver-diff-legend .vd-rem{color:var(--color-danger)}.ver-diff-legend .vd-chg{color:#d9a441}
.ver-diff-acts{margin-top:12px;display:flex;justify-content:flex-end}
/* narrowest phones: let the 3-button row wrap below the label instead of crowding it */
@media (max-width:380px){.ver-item{flex-wrap:wrap}.ver-item-acts{flex-wrap:wrap;justify-content:flex-end;width:100%}}
/* Item G — guided walkthrough */
.tut-steps{display:flex;flex-direction:column;gap:10px;margin:4px 0 8px}
.tut-step{display:flex;gap:12px;padding:12px;border-radius:var(--radius-md);background:var(--paper2)}
/* g427 — this was the 50%-green outline, and it has been dead the whole time: it sets a border
   COLOUR on an element whose border-width is 0, so it computed `0px none` and painted nothing.
   Removed rather than left as misleading intent — the done state is now the hairline + tick set
   in the reskin layer below. */
.tut-step-num{flex:0 0 auto;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:var(--accent-muted);border:1px solid var(--line2);font-size:14px;font-weight:700;color:var(--text-soft)}
.tut-step.done .tut-step-num{background:var(--color-success);color:#0b1f17;border-color:var(--color-success)}
.tut-step-body{flex:1 1 auto;min-width:0}
.tut-step-title{font-size:14px;font-weight:600;color:var(--text)}
.tut-step-text{font-size:12px;color:var(--muted);line-height:1.5;margin:3px 0 8px}
.tut-dismiss{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--text-soft);margin-right:auto;cursor:pointer}
@media(max-width:560px){.src-item{flex-wrap:wrap}.src-item-acts{margin-left:auto}}
.ai-source-styleref{display:flex;align-items:flex-start;gap:8px;font-size:14px;color:var(--text-soft);cursor:pointer;line-height:1.45}
.ai-source-styleref input{margin-top:2px;flex:0 0 auto}
.ai-src-cite{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;align-items:center}
.ai-src-cite-lbl{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
.ai-src-chip{font-size:11px;padding:3px 8px;border-radius:var(--radius-chip);border:1px solid rgba(80,170,120,.4);background:rgba(60,150,100,.12);color:#9FD8B6;cursor:help}
/* Inline [S#] citation chip inside chat prose — smaller, baseline-aligned so it sits in a sentence. */
.ai-src-chip.cite-inline{padding:1px 6px;margin:0 1px;font-size:11px;vertical-align:baseline;white-space:nowrap}
.ai-inferred{font-size:11px;padding:3px 8px;border-radius:var(--radius-chip);border:1px solid rgba(197,179,88,.45);background:rgba(150,120,40,.14);color:#E8D3A8}
.ai-src-badge{font-size:11px;color:var(--muted);display:inline-flex;align-items:center;gap:5px}
@media (max-width:560px){
  .ai-source-meta{flex-wrap:wrap;gap:6px}
}
.ai-extract-modal .ai-ex-body{max-height:60vh;overflow:auto;display:flex;flex-direction:column;gap:18px;margin:4px 0 8px}
.ai-ex-cat h3{font-size:14px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin:0 0 8px;display:flex;align-items:center;gap:8px}
.ai-ex-n{font-size:11px;color:var(--text-soft);background:var(--surface-2,rgba(255,255,255,.06));border:1px solid var(--line);border-radius:var(--radius-chip);padding:1px 8px}
.ai-ex-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
.ai-ex-list li{border-radius:var(--radius-md);padding:10px 12px;background:var(--surface-2,rgba(255,255,255,.02))}
.ai-ex-item{font-size:14px;color:var(--text-soft);line-height:1.45;display:flex;align-items:center;flex-wrap:wrap;gap:8px}
.ai-ex-item .btn.small{margin-left:auto}
@media (max-width:560px){.ai-extract-modal .ai-ex-body{max-height:none}.ai-ex-item .btn.small{margin-left:0}}
.ai-source-using{font-size:14px;line-height:1.5;color:var(--text-soft);border-radius:var(--radius-md);padding:10px 12px;background:var(--surface-2,rgba(255,255,255,.03))}
.ai-draft-step .ai-src-cite{margin-top:8px}

/* ==========================================================================
   Beta 1.9 — SOP Assistant Chat ("Process Pilot")
   Floating launcher (fab) opens a right-side panel: a fixed overlay on desktop
   (no reflow of the page), a full-height drawer + backdrop on tablet/phone.
   Sits above the sidebar (120) but below modals (200) so the insert-as-draft
   modal layers on top. Chat history/keys never touch state.data → never export.
   ========================================================================== */
/* The Axi launcher lives in the toolbar ("Axi Assistant" toggle) at every width;
   the old floating .chat-fab was removed (it overlapped the toolbar button and
   reappeared when resizing down from desktop). */
.chat-backdrop{display:none}
/* the two brand toggles (☰ Menu + AI Assistant) share one row above the logo and keep the
   SAME row orientation in every state — labelled pills when the TOC is open, icon-only squares
   when the rail is collapsed. (Base size/border/colour come from the canonical .toc-toggle rule,
   which now also covers .chat-toggle, so the two are identical.) */
.brand-toggles{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;gap:8px;margin:0 0 12px}
.brand-toggles .toc-toggle,.brand-toggles .chat-toggle{width:auto!important;height:34px!important;min-width:34px!important;padding:0 12px!important;display:inline-flex!important;align-items:center;justify-content:center;gap:8px;cursor:pointer;transition:background .15s ease,border-color .15s ease}
.brand-toggles .tog-ico{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex:0 0 auto;font-size:16px;line-height:1}
.brand-toggles .tog-ico .ai-glyph{width:18px;height:18px}
.brand-toggles .tog-lbl{font-size:12px;font-weight:600;white-space:nowrap;letter-spacing:.01em;color:var(--text)}
/* a toggle reads as active while its panel is open — equal treatment for both */
.app-shell:not(.toc-collapsed) .brand-toggles .toc-toggle,
.app-shell.chat-open .brand-toggles .chat-toggle{background:rgba(255,255,255,.08)!important;border-color:rgba(255,255,255,.30)!important}
/* collapsed rail: icon-only squares, STILL side by side (same orientation as open) */
.app-shell.toc-collapsed .brand-toggles{gap:6px}
.app-shell.toc-collapsed .brand-toggles .tog-lbl{display:none}
.app-shell.toc-collapsed .brand-toggles .toc-toggle,.app-shell.toc-collapsed .brand-toggles .chat-toggle{width:34px!important;padding:0!important}

.chat-panel{position:fixed;top:var(--bar-h);left:var(--toc-open);right:auto;bottom:0;z-index:160;width:var(--chat-open,380px);max-width:100vw;
  display:flex;flex-direction:column;background:var(--bg);color:var(--text);border-right:1px solid var(--line);
  clip-path:inset(0 0 0 0);transition:clip-path .3s cubic-bezier(.4,0,.2,1),left .26s cubic-bezier(.4,0,.2,1)}
/* reveal in/out: .chat-enter clips the panel to zero width at its LEFT (TOC) edge; removing it
   (open) or adding it (close) animates the clip so the panel wipes in/out from the TOC's right
   edge into the gap the centre vacates — it never draws over the Contents. If the transition
   never runs, the panel simply rests fully revealed — no stuck state. */
.chat-panel.chat-enter{clip-path:inset(0 100% 0 0)}
/* While EITHER divider is being dragged, the chat panel must track the live width with no
   transition. Its left edge is pinned to var(--toc-open), so during a TOC drag the .26s `left`
   animation makes it trail the rail's edge — the rail momentarily overlaps the panel's header.
   Suppressing the transition only during the drag keeps the open/close animation intact. */
body.chat-resizing .chat-panel,body.toc-resizing .chat-panel{transition:none}
/* drag-resize handle on the inner (right) edge — mirror of .toc-resizer */
.chat-resizer{position:absolute;top:0;bottom:0;right:-4px;width:9px;z-index:5;cursor:col-resize;background:transparent;touch-action:none}
.chat-resizer::after{content:"";position:absolute;top:0;bottom:0;right:4px;width:2px;background:transparent;transition:background .12s}
.chat-resizer:hover::after,body.chat-resizing .chat-resizer::after{background:var(--color-ink)}

.chat-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:14px 14px 8px}
.chat-head-title{display:flex;align-items:center;gap:10px;min-width:0}
.chat-head-title .ai-glyph{width:22px;height:22px;color:var(--text-strong);flex:0 0 auto}
.chat-head-name{display:flex;flex-direction:column;line-height:1.2;min-width:0}
.chat-head-name b{font-size:16px;color:var(--text-strong)}
.chat-sub{font-size:12px;color:var(--muted)}
.chat-head-actions{display:flex;align-items:center;gap:6px;flex:0 0 auto}
.chat-close{font-size:16px;line-height:1;padding:4px 9px}

.chat-sees-row{display:flex;align-items:center;gap:8px;padding:10px 14px 4px;min-width:0}
.chat-sees{display:flex;align-items:center;gap:4px;min-width:0;padding:6px 9px;border-radius:var(--radius-chip);background:var(--paper2,#1a1a1a);color:var(--text-soft);font-size:12px;line-height:1.2}
.chat-sees-prefix{flex:0 0 auto;color:var(--muted)}
.chat-sees b{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chat-sees-row .ai-weight{flex:0 0 auto}
/* Pinned-workflow affordance: a chip that opens a removable list of what Axi is also reading. */
.chat-pins-toggle{flex:0 0 auto;padding:5px 9px;border:1px solid var(--line);border-radius:var(--radius-chip);background:transparent;color:var(--text-soft);font:inherit;font-size:12px;font-weight:var(--fw-control);cursor:pointer;white-space:nowrap;transition:background .14s,border-color .14s}
.chat-pins-toggle:hover{background:var(--color-hover);border-color:var(--color-linestrong)}
.chat-pins-toggle.open{background:color-mix(in srgb,var(--text) 12%,transparent);border-color:var(--color-linestrong);color:var(--text)}
.chat-pins-list{display:flex;flex-direction:column;gap:5px;margin:0 14px 6px;padding:8px;border-radius:var(--radius-control);background:var(--paper2,#141414)}
.chat-pin-item{display:flex;align-items:center;gap:8px;min-width:0}
.chat-pin-num{flex:0 0 auto;font-family:var(--font-mono,ui-monospace,monospace);font-size:11px;color:var(--text-soft);background:var(--color-control);border-radius:var(--radius-chip);padding:2px 6px}
.chat-pin-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--text-soft)}
.chat-pin-x{flex:0 0 auto;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--line);border-radius:var(--radius-chip);background:transparent;color:var(--muted);font-size:14px;line-height:1;cursor:pointer;padding:0;transition:background .14s,color .14s,border-color .14s}
.chat-pin-x:hover{background:color-mix(in srgb,var(--color-danger) 14%,transparent);border-color:var(--color-danger);color:var(--color-danger)}
.chat-sees-note{padding:2px 14px 8px;font-size:12px;color:var(--muted)}
.chat-nokey{color:var(--error,#CC6B6B)}
.chat-warn{margin:0 14px 8px;padding:8px 10px;border-radius:var(--radius-md);font-size:12px;line-height:1.45;
  background:rgba(204,107,107,.12);border:1px solid rgba(204,107,107,.5);color:#f0c9c9}

.chat-log{flex:1 1 auto;overflow-y:auto;overflow-x:hidden;padding:8px 14px 4px;display:flex;flex-direction:column;gap:12px}
/* Match the gray thumb used by the TOC (sidebar-body) + main panes so all scrollbars read consistently. */
/* g443 — the standard properties moved off this rule and behind the query below. They were set
   alongside the pseudos, and since Chrome 121 that combination makes Chromium ignore every
   `::-webkit-scrollbar` rule on the scroller — so this custom thumb has not been rendering at
   all in current Chrome, and the bar you see here is the platform's. Proven at g436 on `.modal`:
   the track margin only began to paint once these two declarations came off.
   The gap also goes 10 -> 12, which was one of the off-ramp `gap:10px` instances flagged in the
   standing brief. */
.chat-log::-webkit-scrollbar{width:10px}
.chat-log::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16);border-radius:6px;border:3px solid transparent;background-clip:content-box}
.chat-log:hover::-webkit-scrollbar-thumb{background:rgba(255,255,255,.30);background-clip:content-box}
/* Firefox only: it does not implement ::-webkit-scrollbar, so this never matches in Chromium and
   cannot switch the pseudos above back off. */
@supports not selector(::-webkit-scrollbar){
  .chat-log{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.20) transparent}
}
.chat-msg{max-width:92%;padding:9px 12px;border-radius:var(--radius-md);font-size:14px;line-height:1.5;
  word-wrap:break-word;overflow-wrap:anywhere}
.chat-msg.me{align-self:flex-end;background:var(--accent-muted);color:var(--text)}
/* Assistant messages are bubble-LESS — flowing full-width text (like the Axeed chat). Only the
   USER's messages (.me) keep a bubble. Errors re-add a box so they still read as a distinct alert. */
.chat-msg.ai{align-self:stretch;max-width:100%;background:transparent;border-radius:0;padding:2px 0 8px;color:var(--text)}
.chat-msg.ai.err{background:rgba(204,107,107,.1);border:1px solid rgba(204,107,107,.5);border-radius:var(--radius-md);padding:9px 12px;color:#f0c9c9}
.chat-msg.busy{display:flex;align-items:center;gap:8px;color:var(--muted)}
.chat-msg p{margin:0 0 7px}.chat-msg p:last-child{margin-bottom:0}
.chat-msg ul,.chat-msg ol{margin:4px 0 7px;padding-left:20px}
.chat-msg li{margin:2px 0}
.chat-msg code{background:rgba(255,255,255,.08);padding:1px 5px;border-radius:4px;font-size:12px}
.chat-msg strong{color:var(--text-strong)}
.chat-msg-actions{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}

.chat-empty{padding:18px 8px;text-align:center;color:var(--muted)}
.chat-empty-glyph{display:inline-flex;width:34px;height:34px;color:var(--accent-strong);margin-bottom:8px}
.chat-empty-glyph .ai-glyph{width:34px;height:34px}
.chat-empty-h{margin:0 0 6px;font-size:14px;color:var(--text-soft)}
.chat-empty-sub{margin:0;font-size:12px;line-height:1.5}

.chat-starters{display:flex;flex-wrap:wrap;gap:6px;padding:6px 14px 4px}
.chat-starter{padding:6px 10px;border-radius:var(--radius-chip);border:1px solid var(--line);
  background:var(--paper2);color:var(--text-soft);font:inherit;font-size:12px;cursor:pointer;transition:border-color .14s,background .14s,color .14s}
.chat-starter:hover{border-color:var(--line2);background:var(--paper3);color:var(--text)}
.chat-greet-qs{padding:10px 0 0;margin-top:2px}
.chat-greet-q{background:var(--paper2);border-color:var(--line2)}

/* persistent chat actions (structured, review-only) — e.g. "Suggest roles" */
.chat-actions{display:flex;flex-wrap:wrap;gap:6px;padding:4px 14px 6px}
.chat-action{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:var(--radius-chip);border:1px solid var(--line2);background:rgba(255,255,255,.03);color:var(--text-soft);font:inherit;font-size:12px;font-weight:600;cursor:pointer;transition:border-color .14s,background .14s,color .14s}
.chat-action:hover{border-color:var(--line-state);background:rgba(255,255,255,.07);color:var(--text)}
.chat-action .ai-glyph{width:14px;height:14px}
.chat-action.cmd-dim{opacity:.45}
.chat-action.cmd-dim:hover{opacity:.6}
/* Item A — the Axi command center: context-aware AI actions grouped by intent */
.axi-cmds{display:flex;flex-direction:column;gap:9px;padding:8px 14px 10px;border-bottom:1px solid var(--line2)}
.axi-cmd-group{display:flex;flex-direction:column;gap:5px}
.axi-cmd-lbl{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-soft);opacity:.6}
.axi-cmd-row{display:flex;flex-wrap:wrap;gap:6px}
.axi-cmd-more{align-self:flex-start;margin-top:1px;padding:2px 2px;background:none;border:none;color:var(--accent-strong);font:inherit;font-size:11px;font-weight:600;cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.axi-cmd-more:hover{color:var(--text)}
/* context gating — what Axi is allowed to change */
.chat-allow{display:flex;align-items:center;flex-wrap:wrap;gap:6px;padding:2px 14px 0}
.chat-allow-lbl{font-size:12px;color:var(--muted)}
.chat-allow-chip{padding:3px 9px;border-radius:var(--radius-chip);border:1px solid var(--line2);background:transparent;color:var(--text-soft);font:inherit;font-size:11px;font-weight:600;cursor:pointer;transition:border-color .14s,background .14s,color .14s}
.chat-allow-chip.on{border-color:rgba(70,185,138,.5);color:var(--ok,var(--color-success))}
.chat-allow-chip.off{color:var(--muted);opacity:.85}
.chat-allow-chip:hover{background:rgba(255,255,255,.05)}
/* proposed-role cards inside an assistant message */
.chat-roles{display:flex;flex-direction:column;gap:8px;margin-top:10px}
/* Item O — seed-roles review cards + Legend panel action row */
.legend-panel-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px}
.legend-core-tag{display:inline-flex;align-items:center;padding:3px 8px;border-radius:var(--radius-chip);border:1px solid var(--line2);font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--muted)}
.seed-role-cards{display:flex;flex-direction:column;gap:8px;margin:4px 0 12px}
.seed-role-card{border-radius:var(--radius-md);background:var(--paper2);padding:9px 12px}
.seed-role-head{display:flex;align-items:center;gap:8px}
.seed-role-name{font-size:14px;font-weight:600;color:var(--text)}
.seed-role-reason{font-size:12px;color:var(--muted);margin:4px 0 6px;line-height:1.5}
.seed-role-act{display:flex;justify-content:flex-end}
.chat-role-card{border-radius:var(--radius-md);background:rgba(255,255,255,.03);padding:8px 10px}
.chat-role-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.chat-role-code{font-weight:700;font-size:12px;letter-spacing:.03em;color:var(--text);background:var(--accent-muted,#2F3E56);border-radius:6px;padding:2px 7px}
.chat-role-name{font-size:14px;color:var(--text-soft)}
.chat-role-reason{font-size:12px;color:var(--muted);margin-top:4px;line-height:1.45}
.chat-role-act{margin-top:8px}
.chat-role-added{font-size:12px;color:var(--ok,var(--color-success));font-weight:600}
/* Axi icon suggestions (mirrors .chat-role-card; reuses .svg-icon for the glyph) */
.chat-icons{display:flex;flex-direction:column;gap:8px;margin-top:10px}
.chat-icon-card{display:flex;align-items:center;gap:10px;border-radius:var(--radius-md);background:rgba(255,255,255,.03);padding:8px 10px}
.chat-icon-card .chat-icon-glyph{margin:0;flex:none;width:34px;height:34px;min-width:34px}
.chat-icon-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:2px}
.chat-icon-name{font-size:14px;color:var(--text-soft);font-weight:600;overflow-wrap:anywhere}
.chat-icon-reason{font-size:12px;color:var(--muted);line-height:1.45}
.chat-icon-card .btn{flex:none}
.chat-icon-card .chat-role-added{flex:none}
.chat-icons-all{margin-top:8px}

/* Axi agentic-edit proposal card */
.chat-proposal{border-left:3px solid var(--accent)}
.chat-prop-head{display:flex;align-items:center;gap:6px;margin-top:10px;font-size:12px;font-weight:700;color:var(--text-soft)}
.chat-prop-head svg{width:14px;height:14px;flex:0 0 auto}
.chat-prop-list{display:flex;flex-direction:column;gap:4px;margin-top:8px}
.chat-prop-item{display:flex;align-items:flex-start;gap:8px;width:100%;text-align:left;padding:7px 9px;border-radius:var(--radius-md);background:rgba(255,255,255,.03);color:var(--text);font:inherit;cursor:pointer;transition:background .12s,border-color .12s}
.chat-prop-item:hover{background:rgba(255,255,255,.07)}
.chat-prop-check{flex:0 0 auto;width:16px;height:16px;margin-top:1px;border:1.5px solid var(--line2,#555);border-radius:4px;position:relative;transition:background .12s,border-color .12s}
.chat-prop-item.on .chat-prop-check{background:var(--accent);border-color:var(--accent)}
.chat-prop-item.on .chat-prop-check::after{content:"";position:absolute;left:4.5px;top:1px;width:4px;height:8px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}
.chat-prop-lbl{display:block;font-size:12px;color:var(--text);line-height:1.4}
.chat-prop-item:not(.on) .chat-prop-lbl{opacity:.5}
.chat-prop-icos{display:inline-flex;align-items:center;gap:4px;margin-left:4px;vertical-align:middle}
.chat-prop-icos svg{width:16px;height:16px}
.chat-prop-prev{display:block;font-size:12px;color:var(--muted);margin-top:3px;line-height:1.45;white-space:pre-wrap}
.chat-applied{margin-top:10px;font-size:12px;color:var(--ok,var(--color-success));font-weight:600;line-height:1.45}

/* source-material file picker */
.ai-source-files{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:0 0 8px}
.ai-source-filehint{font-size:12px}
.ai-source-filestatus{font-size:12px;color:var(--text-soft)}

.chat-input{padding:8px 14px 14px;display:flex;flex-direction:column;gap:6px;position:relative}
/* Item A — Claude/Codex-style composer: a visible rounded field that spans the panel width
   (inside the normal side gaps), taller than a single line with the text centred, and the + and
   send arrow sitting INSIDE it on the left/right (not on a separate bottom row). It grows as you
   type; the box is intentionally larger than the writable line, so short messages sit in the middle. */
.composer{display:flex;align-items:center;gap:6px;border:1px solid var(--line2);border-radius:18px;background:var(--fill);padding:7px 9px;min-height:68px;transition:border-color .14s,background .14s}
.composer:focus-within{border-color:color-mix(in srgb,var(--line2) 50%,var(--text) 36%);background:var(--hover)}
/* Opt this textarea out of the global form-control styling (border/fill/radius set by the
   generic `select,input,textarea{…}` rule and the `textarea:hover` !important) — the visible
   box is the .composer wrapper, so the textarea itself must be a bare, transparent field. */
.composer textarea{flex:1 1 auto;min-width:0;resize:none;min-height:40px;max-height:200px;padding:8px 6px;border:0!important;border-radius:0!important;background:transparent!important;box-shadow:none!important;color:var(--text);font:inherit;font-size:16px;line-height:1.5;overflow-y:auto}
.composer textarea:hover{border-color:transparent!important}
.composer textarea:focus{outline:none}
.composer-plus,.composer-send{flex:0 0 auto;width:38px;height:38px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--line2);background:transparent;color:var(--text-soft);cursor:pointer;line-height:1;padding:0;transition:background .14s,border-color .14s,color .14s}
.composer-plus svg,.composer-send svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;display:block}
/* Minimal composer: the + is borderless and faded, brightening on hover/focus (Claude/Codex style). */
.composer-plus{font-weight:400;border-color:transparent;background:transparent;color:var(--muted);opacity:.55}
.composer:hover .composer-plus,.composer:focus-within .composer-plus{opacity:1}
.composer-plus:hover,.composer-plus.on{border-color:transparent;background:rgba(255,255,255,.08);color:var(--text);opacity:1}
/* Send arrow: no circle; faded by default, brightens on hover/focus (and when there's text). */
/* No circle by default — just the arrow. A subtle circle appears on hover (like the +),
   and it turns blue when pressed. */
.composer-send{border-color:transparent;background:transparent;color:var(--muted);opacity:.6}
.composer:hover .composer-send,.composer:focus-within .composer-send,.composer.has-text .composer-send{opacity:1;color:var(--text-soft)}
.composer-send:hover{opacity:1;background:rgba(255,255,255,.08);color:var(--text)}
.composer-send:active{background:var(--accent-strong);color:#fff}
.composer-send svg{width:21px;height:21px}
.composer-send.is-stop{background:transparent;border-color:transparent;color:var(--text);opacity:1}
.composer-send.is-stop svg rect{fill:currentColor;stroke:none}
.composer-meta{display:flex;justify-content:flex-end;padding:0 6px}
/* 11px sentence case is the one thing the type scale forbids: "below 12px only tracked uppercase
   survives". The other 11px in this app are eyebrows and badges, which earn the rung by being
   tracked caps. This is a plain-English privacy sentence — shouting it in caps would be worse
   than the violation — so it moves UP to 12px instead, the smallest rung sentence case may use.
   Owner call 2026-08-14. */
.chat-prov{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the "+" tools popover */
.composer-tools-bd{position:fixed;inset:0;z-index:40;background:transparent;border:0;padding:0;margin:0;cursor:default;display:block;width:100%}
/* Tools popover: as tall as it needs to be; only scrolls when the chat window is short. */
.composer-tools{position:absolute;left:14px;right:14px;bottom:100%;margin-bottom:8px;z-index:50;max-height:min(82vh,760px);overflow:auto;background:var(--paper2,#1a1a1a);border-radius:14px;box-shadow:0 14px 34px rgba(0,0,0,.5);padding:10px 4px 6px}
.composer-tools .axi-cmds{padding-top:0}
.ct-title{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);padding:0 14px 6px}
.ct-sep{height:1px;background:var(--line);margin:6px 14px}
.ct-attach-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:2px 14px 8px}
.ct-attach .ai-glyph{width:13px;height:13px}
.ct-link{background:none;border:none;color:var(--accent-strong);font:inherit;font-size:12px;font-weight:600;cursor:pointer;padding:2px}
.ct-link:hover{color:var(--text)}
.composer-tools .chat-allow{padding:6px 14px 4px}
/* streaming reply */
.chat-streaming .chat-stream{white-space:pre-wrap}
.chat-cursor{display:inline-block;width:7px;height:14px;margin-left:2px;background:var(--accent-strong);vertical-align:text-bottom;animation:axiBlink 1s steps(2,start) infinite}
@keyframes axiBlink{0%,50%{opacity:1}50.01%,100%{opacity:0}}

/* tablet / phone: full-height drawer + dimmed backdrop, click-outside to close */
@media (max-width:1100px){
  .chat-backdrop{display:block;position:fixed;inset:0;background:rgba(0,0,0,.58);z-index:150}
  /* Same as the TOC drawer: sit BELOW the toolbar so the chat's own header (Axi · context ·
     close) isn't hidden behind it. Base already sets top:var(--bar-h)+bottom:0; just don't
     override it back to 0 here. */
  .chat-panel,.app-shell.toc-collapsed .chat-panel{
    width:min(420px,100vw);left:0!important;right:auto;top:var(--bar-h,60px);
    clip-path:inset(0 0 0 0);transform:translateX(0);transition:transform .18s ease;
  }
  /* Off-screen start/exit state. Must outrank `.app-shell.toc-collapsed .chat-panel`
     (0,3,0) above — opening chat on mobile collapses the TOC, so the panel lives inside
     .toc-collapsed and that rule would otherwise pin transform back to 0 (panel just pops
     in instead of sliding). The .toc-collapsed…​.chat-enter selector (0,4,0) wins. */
  .chat-panel.chat-enter,.app-shell.toc-collapsed .chat-panel.chat-enter{transform:translateX(-105%)}
  .chat-resizer{display:none}
  /* the full-width fixed app-bar is desktop-only; on tablet/phone the toolbar is in normal flow
     (sticky to the top) so it doesn't permanently occupy a tall slice of the small screen. */
  .toolbar{position:sticky;top:0;padding:8px 16px}
}
@media (max-width:480px){
  .chat-fab{top:8px;right:12px;width:40px;height:40px;padding:0}
  .chat-panel{width:100vw;box-shadow:none}
  .chat-msg{max-width:96%}
}

/* ==========================================================================
   Workflow table — floating column header (desktop ≥1101)
   The column-header row (Step / Role / Step Type / Description / Icons /
   Following Step) pins just under the already-pinned toolbar, so the columns
   stay labelled while the steps scroll beneath it. At ≥1101 the table is
   table-layout:fixed/width:100% and fits, so the wrap no longer needs to be a
   horizontal-scroll container (overflow:auto would otherwise break the sticky).
   The repeated sub-path headers are removed in JS, so only this one header shows.
   ========================================================================== */
@media(min-width:1101px){
  .wf-view .table-wrap{overflow:visible}
  .wf-view .table-wrap>table>thead>tr>th{
    position:sticky;top:0;z-index:6;
    background:var(--paper);
    padding-top:14px;padding-bottom:12px;
    border-bottom:1px solid var(--line);
  }
  /* a little breathing room below the floating header line before the first row */
  .wf-view .table-wrap>table>tbody>tr:first-child>td{padding-top:14px}
  /* the wrap has overflow:visible (so the header can pin), so the square header/table
     backgrounds would paint over the wrap's rounded corners — round the content to match.
     The TABLE element itself must round all four corners: its top corners sit behind the
     sticky thead, and if left square its #121212 fill paints a square nub in the corner
     triangle that the thead's rounding leaves open (defeating the rounded look). */
  .wf-view .table-wrap>table>thead>tr>th:first-child{border-top-left-radius:var(--radius-md)}
  .wf-view .table-wrap>table>thead>tr>th:last-child{border-top-right-radius:var(--radius-md)}
  .wf-view .table-wrap>table{border-radius:var(--radius-md)}
}

/* ==========================================================================
   Independent scroll panes (desktop ≥1101)
   The TOC sidebar becomes a fixed-height pane that scrolls its OWN content, so a
   long table of contents stays reachable within the viewport instead of running
   off-screen (it was height:auto + sticky, so its tail was unreachable). The main
   area scrolls via the page (toolbar + table header pinned), and the chat log
   scrolls itself — so each region scrolls independently and a scrollbar only
   appears when that region actually overflows. The sidebar's border-right is the
   separation line, matching the chat panel's border-left.
   ========================================================================== */
@media(min-width:1101px){
  /* The sidebar is a fixed-height flex column; only the BODY scrolls, so the brand
     (logo + sandwich) and footer keep their natural size (scrolling the whole sidebar
     made flex children shrink and clipped the logo). */
  .app-shell .sidebar{
    position:sticky!important;top:0!important;align-self:start!important;
    height:100%!important;max-height:100%!important;min-height:0!important;
    overflow:hidden!important;display:flex!important;flex-direction:column!important;
  }
  .app-shell .sidebar > .brand,
  .app-shell .sidebar > .sidebar-footer{flex:0 0 auto}
  .app-shell .sidebar > .sidebar-body{
    flex:1 1 auto!important;min-height:0!important;overflow-y:auto!important;overflow-x:hidden!important;
    /* g443 — standard scrollbar properties removed here for the same reason as .chat-log: set
       alongside `::-webkit-scrollbar`, they make Chromium ignore the pseudos entirely (>=121),
       so this rule's custom thumb was never reaching the screen. Firefox is served below. */
    /* Cancel the sidebar's 14px right padding for the body only, then re-add it as the body's own
       inner padding — so the scrollbar sits flush against the border-right, exactly like the
       chat-log's does, while the TOC content keeps its inset. The width is FROZEN to the OPEN
       width (var(--toc-open) minus the 32/14 gutters -> the same value as 100%+14 when open) so
       the content never REFLOWS as the rail narrows — it stays put and is wiped, exactly like the
       Axi chat panel. The rail clips it via overflow:hidden; the clip-path animates the wipe. */
    width:calc(var(--toc-open) - 32px)!important;max-width:none!important;margin-right:-14px!important;padding-right:14px!important;
    clip-path:inset(0 0 0 0);transition:clip-path .3s cubic-bezier(.4,0,.2,1);
  }
  .app-shell .sidebar > .sidebar-body::-webkit-scrollbar{width:10px}
  .app-shell .sidebar > .sidebar-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16);border-radius:6px;border:3px solid transparent;background-clip:content-box}
  .app-shell .sidebar > .sidebar-body:hover::-webkit-scrollbar-thumb{background:rgba(255,255,255,.30);background-clip:content-box}
  /* Firefox only — see the note on .chat-log. */
  @supports not selector(::-webkit-scrollbar){
    .app-shell .sidebar > .sidebar-body{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.20) transparent}
  }
  /* Collapsed: wipe the frozen-width content out toward the left edge (chat-panel clip). */
  .app-shell.toc-collapsed .sidebar > .sidebar-body{overflow:hidden;clip-path:inset(0 100% 0 0)}

  /* MAIN is its own scroll pane: the page itself doesn't scroll, so the main
     scrollbar sits at the main column's right edge (between content and the chat),
     not at the viewport's far right where the chat's own scroll lives. The toolbar
     + floating table header pin within this pane; the chat log scrolls itself. */
  html,body{height:100%;overflow:hidden}
  /* Override the base min-height:100vh — it wins over this height:calc and forces the shell to
     full 100vh while sitting at margin-top:bar-h, so its bottom (and the main scroll pane's)
     fell bar-h px BELOW the viewport: the last step + page bottom were unreachable. */
  .app-shell{height:calc(100vh - var(--bar-h))!important;height:calc(100dvh - var(--bar-h))!important;min-height:calc(100vh - var(--bar-h))!important;min-height:calc(100dvh - var(--bar-h))!important;margin-top:var(--bar-h)!important;overflow:hidden!important}
  .main{height:100%!important;overflow-y:auto!important;overscroll-behavior:contain}
  .main::-webkit-scrollbar{width:10px}
  .main::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16);border-radius:6px;border:3px solid transparent;background-clip:content-box}
  .main:hover::-webkit-scrollbar-thumb{background:rgba(255,255,255,.30);background-clip:content-box}
}
/* print must not inherit the fixed-height / hidden-overflow pane model */
@media print{
  html,body{height:auto!important;overflow:visible!important}
  .app-shell{height:auto!important;overflow:visible!important}
  .main{height:auto!important;overflow:visible!important}
}

/* ==========================================================================
   Workflow table — stack into cards when the COLUMN is too narrow (not just the
   window). Uses a container query on .table-wrap, so the table also stacks when
   the main column is squeezed by an open TOC + chat on desktop — instead of the
   description collapsing to one letter per line and the row becoming huge.
   Mirrors the mobile card layout; !important beats the desktop fixed-layout
   column widths. Screen-only so print (which targets a fixed page width) is
   unaffected.
   ========================================================================== */
@media screen{
  .wf-view .table-wrap{container-type:inline-size}
  @container (max-width:600px){
    .wf-view .table-wrap thead{display:none!important}
    .wf-view .table-wrap table,
    .wf-view .table-wrap tbody,
    .wf-view .table-wrap tr,
    .wf-view .table-wrap td{display:block!important;width:auto!important;min-width:0!important}
    .wf-view .table-wrap .step-row{border-bottom:1px solid var(--line)}
    .wf-view .table-wrap td{border-bottom:0;padding:8px 12px}
  }

  /* Structural checks — same column-narrow problem as the table above. With the
     TOC + Axi chat both open the Check column gets squeezed and the inline
     [badge · message · location] row broke the message one letter per line.
     A container query on the list stacks each row (mirroring the ≤720px mobile
     layout) based on the LIST's own width, not the window's. */
  .validate-list{container-type:inline-size}
  @container (max-width:600px){
    .validate-row{flex-wrap:wrap;gap:8px}
    .validate-jump{width:100%;flex-wrap:wrap;gap:8px}
    .validate-fix{width:100%;justify-content:center}
    .v-loc{width:100%}
  }
}

/* ==========================================================================
   Template Library (Beta 2.6) — the in-builder insert-a-fragment modal.
   Renders inside #modalRoot, reusing .modal / .modal-backdrop. Cards group by
   department; each is insertable (workflow → pick a section; section → append).
   ========================================================================== */
.tmpl-lib-modal{width:min(920px,100%)}
.tmpl-lib-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.tmpl-lib-head h2{margin:0}
.tmpl-tabs{display:flex;gap:8px;margin:14px 0 6px;flex-wrap:wrap}
.tmpl-tab{border:1px solid var(--border);background:transparent;color:var(--text-soft);border-radius:var(--radius-chip);padding:6px 16px;font-size:var(--fs-meta);font-weight:var(--fw-control);cursor:pointer;line-height:18px}
.tmpl-tab:hover{border-color:var(--text-soft)}
.tmpl-tab.active{background:var(--sel-fill);border-color:var(--line-state);color:var(--text)}
.tmpl-lib-body{margin-top:8px}
.tmpl-group{margin-top:18px}
.tmpl-group-head{font-size:var(--fs-label-sm);font-weight:var(--fw-control);text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:0 0 10px;border-bottom:1px solid var(--border);padding-bottom:6px}
.tmpl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}
.tmpl-card{border-radius:var(--radius-md);background:rgba(255,255,255,.02);padding:12px;display:flex;flex-direction:column;gap:8px;min-width:0}
.tmpl-card-top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.tmpl-kind{font-size:var(--fs-label-sm);font-weight:var(--fw-control);border-radius:4px;padding:2px 7px;line-height:16px}
.tmpl-kind-workflow{background:var(--accent-muted);color:var(--text)}
.tmpl-kind-section{background:#2f6b4f33;color:#a9e0c4;border:1px solid #2f6b4f55}
.tmpl-cat{font-size:var(--fs-label-sm);color:var(--muted)}
.tmpl-vis-badge{font-size:var(--fs-label-sm);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:1px 6px;margin-left:auto}
.tmpl-card-name{font-weight:var(--fw-control);color:var(--text);line-height:20px;word-break:break-word}
.tmpl-card-summary{font-size:var(--text-body-axd);color:var(--text-soft);line-height:18px}
.tmpl-card-meta{font-size:var(--fs-label-sm);color:var(--muted)}
.tmpl-card-roles{display:flex;flex-wrap:wrap;gap:4px}
.tmpl-role-pip{font-size:11px;font-weight:600;border-radius:4px;padding:1px 6px;line-height:16px;white-space:nowrap}
.tmpl-card-actions{display:flex;gap:8px;margin-top:auto;padding-top:4px;flex-wrap:wrap}
.tmpl-empty{padding:32px 12px;text-align:center;color:var(--muted)}
.tmpl-note{font-size:var(--fs-meta);border-radius:var(--radius-sm);padding:8px 12px;margin:10px 0}
.tmpl-note-warn{background:#7a2b2b22;border:1px solid #7a2b2b55;color:#f0b9b9}
.tmpl-note-ok{background:#2f6b4f22;border:1px solid #2f6b4f55;color:#a9e0c4}
.tmpl-vis{display:flex;flex-direction:column;gap:6px}
.tmpl-vis-opt{display:flex;align-items:center;gap:8px;font-size:var(--fs-meta);color:var(--text-soft);cursor:pointer}
.tmpl-save-modal{width:min(560px,100%)}
@media (max-width:520px){.tmpl-grid{grid-template-columns:1fr}}

/* "Get started" resume pill (2.7) — sits in the sidebar footer once the welcome checklist is
   dismissed but unfinished; resets the footer's uppercase micro-type back to a readable button. */
/* 8 -> 16 (2026-08-14). The TOC pass set the whole column to 16 — list to divider, divider to
   content, content to the bottom edge — but only measured the footer in its SHORT state, with
   the wordmark alone. With the Get Started CTA present the footer is 83px, not 43, and this
   8px was the one gap left off the scale: the CTA and the wordmark sat glued as a pair while
   everything above and below them breathed at 16. Now the footer reads 16/16/16 top to bottom,
   which is what "even from top to bottom" asked for in the first place. */
.sidebar-footer .tut-resume{display:block;width:100%;margin:0 0 16px;padding:7px 10px;border:1px solid var(--line,rgba(255,255,255,.18));border-radius:var(--radius-control);background:rgba(255,255,255,.06);color:inherit;font:inherit;font-size:12px;font-weight:600;letter-spacing:normal;text-transform:none;cursor:pointer;text-align:center;white-space:normal;line-height:1.3}
.sidebar-footer .tut-resume:hover{background:rgba(255,255,255,.12)}

/* Comments overview page (2.7) — whole-SOP comment dashboard, styled to match the Analysis page. */
.comments-overview .cmt-ov-filters{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0 16px}
.cmt-ov-chip{padding:5px 12px;border:1px solid var(--line);border-radius:var(--radius-chip);background:var(--paper2);color:var(--muted);font-size:12px;font-weight:600;cursor:pointer}
.cmt-ov-chip:hover{color:var(--text)}
.cmt-ov-chip.on{background:var(--accent-soft,color-mix(in srgb, var(--text) 12%, transparent));color:var(--text);border-color:var(--color-linestrong)}
.cmt-ov-list{display:flex;flex-direction:column;gap:16px}
.cmt-ov-group{border-radius:var(--radius-md);background:var(--paper);overflow:hidden}
.cmt-ov-step{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:11px 13px;background:var(--paper2);border-bottom:1px solid var(--line)}
.cmt-ov-step-main{min-width:0;flex:1 1 260px}
.cmt-ov-step-label{font-weight:600;font-size:14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.cmt-ov-openpill{font-size:11px;line-height:16px;font-weight:600;padding:0 7px;border-radius:var(--radius-chip);background:rgba(192,136,58,.16);color:#C0883A;border:1px solid rgba(192,136,58,.42)}
.cmt-ov-step-desc{color:var(--muted);font-size:12px;margin-top:3px;overflow:hidden;text-overflow:ellipsis}
.cmt-ov-step-acts{display:flex;gap:6px;flex-wrap:wrap;flex:0 0 auto}
.cmt-ov-group .cmt-ov-thread{border:0;border-top:1px solid var(--line);border-radius:0;background:transparent}
.cmt-ov-group .cmt-ov-thread:first-of-type{border-top:0}
.cmt-ov-thread.resolved{opacity:.62}
.cmt-ov-thread-acts{display:flex;justify-content:flex-end;gap:6px;padding:12px 13px 12px;margin-top:2px}
.cmt-ov-empty{margin-top:6px}
@media (max-width:560px){.cmt-ov-step{flex-direction:column}.cmt-ov-step-acts{width:100%}}

/* Beta 2.8 Review workspace — first-class governance, cadence, comments, and checks. */
.nav-actions{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
.nav-button{flex:none!important;width:100%;min-width:0;padding-left:7px;padding-right:7px}
.nav-attention-dot{width:8px;height:8px;flex:0 0 auto;margin-left:6px;border-radius:50%;background:var(--error);box-shadow:0 0 0 2px rgba(180,35,24,.18)}
.review-view{width:100%;max-width:1180px;margin:0 auto;display:flex;flex-direction:column;gap:18px;min-width:0}
.review-view>.page-card{margin-bottom:0;min-width:0}
.review-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px;min-width:0}
.review-card-head>div:first-child{min-width:0}
.review-card-head h2{margin:0 0 4px;color:var(--text);font-size:var(--fs-title);font-weight:var(--fw-title);line-height:30px}
.review-status-pills{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;min-width:0}
.review-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:14px}
.review-lock-note,.review-soft-nudge,.review-readonly-note{padding:11px 13px;border-radius:var(--radius-sm);font-size:14px;line-height:1.5;color:var(--text-soft);overflow-wrap:anywhere;background:var(--color-surface2)}
.review-lock-note{background:rgba(70,185,138,.08);border-color:rgba(70,185,138,.28)}
.review-soft-nudge{margin-top:10px;background:rgba(150,120,40,.10);border-color:rgba(197,179,88,.30);color:#E8D3A8}
.review-readonly-note{background:var(--paper2);color:var(--muted)}
.review-cadence-row{display:flex;align-items:center;justify-content:space-between;gap:16px;min-width:0}
.governance-cadence-control{display:flex;align-items:center;gap:10px;min-width:0;color:var(--text-soft);font-size:14px;font-weight:600}
.governance-cadence-control select{width:auto;min-width:150px;max-width:220px;height:38px;padding:6px 40px 6px 10px;font-size:14px}
.review-cadence-meta{min-width:0;color:var(--muted);font-size:14px;line-height:1.45;text-align:right;overflow-wrap:anywhere}
.review-readonly-value{display:inline-flex;align-items:center;min-height:38px;padding:7px 11px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--paper2);color:var(--text-soft);font-size:14px;font-weight:600}
.review-checks-foot{display:flex;align-items:center;justify-content:space-between;gap:14px;color:var(--muted);font-size:14px;line-height:1.45}
.review-checks-foot .btn{flex:0 0 auto}
/* These rows must survive a NARROW CONTAINER, not just a narrow viewport: the content column
   is squeezed by the sidebar rail and the Axi chat panel, so a viewport-only breakpoint leaves a
   dead band (~641-760px) where the row can't fit but hasn't stacked — the primary button then
   escapes its card. Wrapping is container-driven, so it holds at every width and panel state. */
.review-card-head,.review-cadence-row,.review-checks-foot{flex-wrap:wrap}
.review-cadence-row>:not(.btn),.review-checks-foot>:not(.btn){min-width:0}
@media(max-width:640px){
  .review-card-head,.review-cadence-row,.review-checks-foot{flex-direction:column;align-items:stretch}
  .review-status-pills{justify-content:flex-start}
  .review-actions .btn,.review-checks-foot .btn{width:100%;justify-content:center;white-space:normal;text-align:center}
  .governance-cadence-control{width:100%;flex-direction:column;align-items:stretch}
  .governance-cadence-control select{width:100%;max-width:none}
  .review-cadence-meta{text-align:left}
}

/* =====================================================================
   AXI PASS (feature/2.9-ui-axi-pass) — texture borrowed from Axi EA.
   Palette, layout and shapes stay AXD SOP: navy accents, squarish
   tiles, Figtree body at 400. This layer changes only: hairline borders,
   a friendlier radius step, micro-label section headers, a lighter
   display weight on page titles, shadow+frost on FLOATING layers only,
   and a small unified motion vocabulary. Additive — no rules deleted.
   ===================================================================== */
:root{
  /* Neutral hairlines (was blue-grey rgba(151,168,199,…) — off-brand in a monochrome
     system). The builder overrides these inline via applyTheme; contexts that don't run it
     (dashboard/console) now inherit these neutral values instead of the old blue. */
  --line:rgba(255,255,255,.14);
  --line2:rgba(255,255,255,.22);
  /* radius aliases removed — see the note at the top of the file. This block used to win
     the cascade and resolved sm/md/lg to 10/14/18, i.e. 14 and 18 are not on the AXD-I
     scale at all. The "friendlier radius step" this pass wanted is now carried by the
     role tokens (chip 8 / control 10 / card 16 / modal 20). */
  --shadow-float:0 18px 44px rgba(0,0,0,.42);
  --tracking-label:.12em;
  --fw-display:350;
  --ease-soft:cubic-bezier(.2,.85,.22,1);
  --hover-fill:rgba(255,255,255,.045);
  /* stylesheet defaults for the JS-injected theme vars, so unthemed boots
     get the same quiet hover language as themed ones */
  --fill:rgba(255,255,255,.035);
  --hover:rgba(255,255,255,.06);
}
:root[data-mode="light"]{
  --line:rgba(20,24,30,.12);
  --line2:rgba(20,24,30,.20);
  --legend-icon-default:#475569;
  --shadow-float:0 18px 44px rgba(20,24,30,.16);
  --hover-fill:rgba(20,24,30,.045);
}

/* ---- corners: cards/panels 16, modals 20, rows/controls 14 ----
   This is the LAST of three layered radius passes and the one that actually wins;
   the "Softer corners" 14px group (~L899) and the 13px group (~L2046) are both
   superseded by it and are left only as history.

   It used to read var(--radius-md)/var(--radius-lg), but those aliases are themselves
   redefined three times (L74-77 semantic, L765-767, L3740-3742 -> sm:10 md:14 lg:18),
   so cards resolved to 14px and modals to 18px — while every one of the 16 locked
   mockups specifies 16px cards and 20px modals. Pointing the CARD and MODAL selectors
   at the semantic base tokens (--radius-card:16 / --radius-modal:20) closes that gap
   app-wide instead of each Pass B batch scoping around it. Approved 2026-08-05.

   Row-ish and control-ish surfaces deliberately KEEP the 14px alias: .item,
   .validate-row, .search and .subpath-bar are rows and inputs, not cards, and the
   mockups do not give them card radius. */
.role-panel,.legend-panel,.table-wrap,.nested-table,.page-card,.card,.notice,
.subpath-panel,.branch-panel{border-radius:var(--radius-card)!important}
.subpath-bar,.item,.validate-row,.v-clean,.search{border-radius:var(--radius-md)!important}
.modal{border-radius:var(--radius-modal)!important}
.menu-pop{border-radius:var(--radius-card)!important}
.btn,label.btn,select,input[type=text],input[type=file],textarea,input[type=color]{border-radius:var(--radius-sm)!important}
.btn.small{border-radius:8px!important}

/* ---- hairline discipline: the two hardcoded slate strokes ---- */
.toolbar{border-bottom-color:var(--line)}
.section-block{border-top-color:var(--line)}

/* ---- micro-label voice: table headers get tracked-caps; sidebar SECTION titles stay
       a quiet muted label (they hold USER-typed section names -> never uppercase, never
       tracked-caps per rulebook §3). Separation is the top hairline + spacing, not a box. ---- */
.section-title{font-size:11px!important;font-weight:500!important;letter-spacing:.02em!important;color:var(--muted)}
/* .section-title:hover removed — it is a label, not a control (see the cursor:default note) */
th{letter-spacing:.09em!important}

/* ---- display voice: page titles go light (350); body stays 400 ---- */
.workflow-header h2,.legend-top h2,.analysis-head h2,.review-card-head h2,.modal h2{font-weight:var(--fw-display)!important}

/* ---- floating layers: soft shadow for depth; SOLID original colors
       (no translucency, no backdrop blur — owner call, 2026-07-16)
       AMENDED 2026-08-16 (g424/g426): the rule is about SURFACES. Panels, menus, sheets and
       cards are opaque and stay opaque — that part is unchanged and still standing. The MODAL
       SCRIM is not a surface, and it now carries an 8px backdrop blur by owner approval. If you
       are here because you found `backdrop-filter` on `.modal-backdrop` and read it as a
       regression, it is not one — leave it. ---- */
.modal{border-color:var(--line2)!important;box-shadow:var(--shadow-float)!important}
.menu-pop{border-color:var(--line2);box-shadow:var(--shadow-float)}

/* ---- the save toast becomes a floating frosted pill ---- */
#notice.notice{
  position:fixed;left:50%;bottom:calc(26px + env(safe-area-inset-bottom,0px));transform:translateX(-50%);
  z-index:400;margin:0;max-width:min(86vw,520px);
  padding:10px 18px;border-radius:var(--radius-card)!important;
  background:var(--paper3);
  box-shadow:0 10px 24px rgba(0,0,0,.35);
  color:var(--text-soft);font-size:var(--fs-meta);font-weight:var(--fw-control);
  line-height:1.4;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}

/* ---- motion: one ease, one duration; tone on hover, squish on press ----
   Hover never MOVES a control (rulebook §states: hover is a tone step, nothing
   else) — the fill appears or deepens. Displacement belongs to the press. */
.btn,label.btn,.nav-button,.section-title,.workflow-link,.subpath-bar .btn,.toc-toggle,.mobile-toc-toggle,.menu-item{
  transition:transform .18s var(--ease-soft),background-color .18s var(--ease-soft),
             border-color .18s var(--ease-soft),color .18s var(--ease-soft),
             box-shadow .18s var(--ease-soft);
}
.btn:active,label.btn:active,.nav-button:active,.workflow-link:active,
.toc-toggle:active,.mobile-toc-toggle:active{transform:scale(.985)}

/* The primary carries NO edge. The faint white border here dated from the navy-fill era, where
   it kept a saturated fill from bleeding into the page; on the v1.8 primary it is a hairline on
   a control whose whole tier statement is "solid fill, no line" — §6's ladder is ghost=no box,
   secondary=hairline, primary=fill, and a fill that also carries a line reads as two tiers at
   once. This was the faint outline visible around Save in every settings modal. */
.btn.primary,label.btn.primary{border-color:transparent}

/* ---- focus: a soft offset ring instead of border swaps ---- */
:where(button,input,select,textarea,summary):focus-visible{outline:2px solid rgba(242,242,242,.5);outline-offset:2px}
/* Search fields take a HAIRLINE ring instead of the 2px control ring. A text field already
   announces focus twice — a blinking caret and its own brightened border — so the heavy ring
   lands as a third, thick edge around a control that is mostly outline to begin with.
   1px at the linestrong weight still marks focus clearly for keyboard users. */
.search:focus-visible,.icon-lib-search:focus-visible{outline:1px solid var(--color-linestrong);outline-offset:2px}
.search:focus,.icon-lib-search:focus{border-color:var(--color-linestrong)}
:root[data-mode="light"] :where(button,input,select,textarea,summary):focus-visible{outline-color:rgba(20,24,30,.5)}

/* ---- reduced motion wins, always ---- */
@media(prefers-reduced-motion:reduce){
  .btn,label.btn,.nav-button,.section-title,.workflow-link,.subpath-bar .btn,.toc-toggle,.mobile-toc-toggle,.menu-item{transition:none}
  .btn:active,label.btn:active,.nav-button:active,.workflow-link:active,
  .toc-toggle:active,.mobile-toc-toggle:active{transform:none}
}

/* =====================================================================
   AXI PASS 2 — control scale. One height standard for every control:
     --ctl-h    40px  buttons, inputs, selects, search, toolbar
     --ctl-h-sm 32px  compact buttons (row actions, .btn.small)
     --ctl-h-xs 28px  micro icon buttons
   Menu rows and chips are lists/badges, not controls — untouched.
   Touch screens keep a 44px minimum on primary buttons.
   ===================================================================== */
/* The TOUCH rung (§5: 36px pointer, 44px touch). The pointer rung overrides these at >=1025px.
   Was 40/32 — four short of the minimum on every phone, which is where the audit's 226
   under-size targets started. */
:root{--ctl-h:44px;--ctl-h-sm:36px;--ctl-h-xs:28px}
.btn:not(.menu-item),label.btn:not(.menu-item){min-height:var(--ctl-h)!important;padding-top:5px!important;padding-bottom:5px!important}
.btn.small,label.btn.small{height:auto!important;min-height:var(--ctl-h-sm)!important;padding-top:3px!important;padding-bottom:3px!important}
.search{height:var(--ctl-h)}
.nav-button{height:var(--ctl-h)}
.toc-toggle,.mobile-toc-toggle{width:40px!important;min-width:40px!important;height:var(--ctl-h)!important}
.form-grid input[type=text],.form-grid select{min-height:var(--ctl-h)}
@media(max-width:720px){
  .btn:not(.menu-item):not(.small),label.btn:not(.menu-item):not(.small){min-height:44px!important}
}

/* ---- Axi chat composer: same visual height as the workflow search.
       Icon and text sizes unchanged; only the box slims down. ---- */
.composer{min-height:0;padding:5px 8px}
.composer textarea{min-height:24px;padding:5px 6px}
.composer-plus,.composer-send{width:34px;height:34px}

/* ---- Legend containment fix (pre-existing bug, surfaced by long role names):
       the .list grid's auto column sized itself to the longest NOWRAP role chip
       ("PM / CM / PRM / SPM (internal requestor)"), so every card in the column
       rendered wider than its panel and overflowed into the neighbouring column.
       Pin the track to the panel width and let long chips wrap inside the card. */
.legend-panel .list{grid-template-columns:minmax(0,1fr)}
.legend-panel .item{min-width:0;max-width:100%}
.legend-panel .item>*{min-width:0}
.legend-panel .role-chip,.legend-panel .step-type{max-width:100%;white-space:normal;height:auto;text-align:left;line-height:1.35}

/* ---- Light-mode refinement: dark-canvas white-alpha borders/fills that
       wash out or vanish on a light page (found by computed-style sweep
       across Legend / table / Review / Structure / menus). Each gets the
       ink-alpha equivalent so light reads as crisply as dark. ---- */
:root[data-mode="light"] .subpath-bar.sp-tint{background:rgba(20,24,30,.035)}
:root[data-mode="light"] .subpath-bar.sp-tint.open{background:rgba(20,24,30,.05)}
:root[data-mode="light"] .subpath-bar .sp-tag,
:root[data-mode="light"] .sp-tag{border-color:rgba(20,24,30,.22)}
:root[data-mode="light"] .sidebar-footer{border-top-color:rgba(20,24,30,.10)}
:root[data-mode="light"] .sidebar-footer .tut-resume{background:rgba(20,24,30,.05);border-color:rgba(20,24,30,.16)}
:root[data-mode="light"] .sidebar-footer .tut-resume:hover{background:rgba(20,24,30,.09)}
:root[data-mode="light"] .nav-button.active{border-color:rgba(20,24,30,.25)}
:root[data-mode="light"] .ai-beta-tag{background:rgba(20,24,30,.06)}
:root[data-mode="light"] .menu[open]>summary{border-color:rgba(20,24,30,.34)!important;background:rgba(20,24,30,.05)!important}
/* the primary button's faint white edge is a dark-canvas trick; on light use ink */
:root[data-mode="light"] .btn.primary,:root[data-mode="light"] label.btn.primary{border-color:transparent}

/* ---- Mark language: one standard for every tick/cross in the app.
       Glyph pair is ✓ / ✗ (already true at all call sites), weight 700,
       and ONE color rule in both modes:
         green = works / done / passed
         red   = error / failed
         muted = not available (capability dimming — deliberately NOT red)
       All native tickboxes (Check apply-all, context-library toggles,
       comment suggestions, learn toggle, tutorial dismiss) render with
       the same navy accent in both modes. ---- */
/* status marks + shorthands -> AXD-I status tokens (auto-flip with data-mode).
   --ok/--warn/--err/--info were previously UNDEFINED, so `var(--ok,var(--color-success))` fell
   through to bright saturated green everywhere; aliasing them mutes it app-wide. */
:root{
  --mark-ok:var(--color-success);--mark-warn:var(--color-warning);--mark-err:var(--color-danger);--mark-info:var(--color-info);
  --ok:var(--color-success);--warn:var(--color-warning);--err:var(--color-danger);--info:var(--color-info);
}
input[type="checkbox"]{accent-color:var(--accent-strong)}
.ai-cap-summary strong.yes{color:var(--mark-ok);font-weight:700}
.ai-cap-summary strong.no{font-weight:700}
.ai-key-detect.ok,.ai-embed-note.ok,.ai-models-note.ok{color:var(--mark-ok)}
.rr-ready .rr-icon{color:var(--mark-ok)}
.rr-review .rr-icon{color:var(--mark-warn)}
.rr-blocked .rr-icon{color:var(--mark-err)}
/* g427 — both of these belonged to the SOLID done badge: a filled --mark-ok disc, which needs
   white glyph on it, and in light mode that is what the second rule supplied.
   The reskin turned the badge into a 14% tint (--sev-ok-fill, set !important further down), and
   only the BACKGROUND half of that changeover landed — the white-glyph rule stayed, out-ranking
   the reskin's `color:var(--color-success)` on specificity (0,4,1 vs 0,3,0). So light mode drew
   a WHITE tick on a near-white card: measured rgb(255,255,255) on a 14%-alpha green over
   rgb(250,250,250). The owner's "the cards that are complete should have a green tick" was
   already true in dark and invisible in light, for this reason alone.
   The background line goes too — it is dead against the reskin's !important, and leaving it
   reads as an intent that is not in force. */
.tut-step.done .tut-step-num{border-color:var(--mark-ok)}

/* ---- Notification badges: outlined + tinted, not solid alarm-red.
       Same language as the End-step pill, a touch more saturated —
       prominent but in-theme. Comment counters take the navy accent
       tint (they're "there's something here", not "something's wrong"). */
.nav-badge{background:rgba(204,107,107,.18);color:#E0837B;border:1px solid rgba(204,107,107,.55);font-weight:700}
:root[data-mode="light"] .nav-badge{background:rgba(180,35,24,.10);color:#a51f15;border-color:rgba(180,35,24,.45)}
.cmt-n{background:var(--accent-muted);color:var(--text-soft);border:1px solid var(--line2)}
:root[data-mode="light"] .cmt-n{background:var(--accent-muted);color:var(--accent-strong);border-color:var(--line2)}

/* ---- Axi composer: + and send anchor to the BOTTOM of the box while
       multi-line text grows upward (Claude-style), instead of floating
       to the vertical middle. ---- */
.composer{align-items:flex-end}

/* ---- Settings panel: desktop two-pane rail/detail; mobile root-to-detail drill-down. ---- */
/* g374: the page-scroll lock goes with the modality. Locking the document was correct while a
   scrim covered it — scrolling something you cannot see is worse than not scrolling. The canvas
   is live now and must scroll while Settings is open. */
/* .set-scrim retired at g374 — the element is no longer emitted and a scrim is the one thing a
   non-modal panel must not have. Kept as a note rather than a dangling rule so nobody wonders
   which state used to show it. */
/* §2 — this is a FLOATING SURFACE (a sheet over the app), so it takes the `overlay` tone and
   the linestrong hairline. It was on `surface2`, which is a STRUCTURAL tone and specifically
   the SELECTOR's track tone; §2 forbids borrowing a structural layer's tone just because it
   comes next on the ladder. Shape is deliberate and stays: full-height, edge-anchored, radius
   0, no shadow — same call as the console's full-bleed overlay.
   Consequence for everything inside: `hover` (#212121) is now IDENTICAL to this ground, so
   every hover fill in the panel must use `overlay-hover` — which is exactly what the token
   names say ("Hover fill inside an overlay"). */
.setpanel{position:fixed;top:0;right:0;bottom:0;z-index:341;width:720px;max-width:100vw;background:var(--color-overlay);border-left:1px solid var(--color-linestrong);display:flex;flex-direction:column;overflow:hidden;animation:settings-slide-in .26s cubic-bezier(.2,.8,.2,1) both}
.setpanel.settings-static{animation:none}
@keyframes settings-slide-in{from{transform:translateX(28px);opacity:0}to{transform:none;opacity:1}}
.set-top{flex:0 0 auto;display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:18px 20px;border-bottom:1px solid var(--color-line)}
/* Display rung: 28 / weight 300 / -0.01em (§3). It was weight 400 — the Display rung is the
   one place the face goes light, and at 400 it read as an oversized heading rather than a
   page title. Mobile takes 24 per the same table (see the <=900 block). */
/* g371: 28 -> 16, the Section rung, on the owner's pick from a three-way render. The Display rung
   is for a title arriving with no context around it; this one sits above a rail that names every
   destination and a pane that titles itself, so it was the largest type in the drawer while being
   the least informative thing in it. Weight goes 300 -> 500 and the negative tracking is dropped:
   both belong to display type and stop paying for themselves at 16.
   RAISED, since it will look like an oversight later: the docked page's own h2 is ALSO 16px, so
   the drawer title and the page title are now peers rather than a hierarchy. Flagged before the
   choice and chosen anyway. If it starts to read flat, the fix is weight or tone on this rule —
   NOT a new size, because 20px is not on the scale and 13px was retired at v1.6. */
.set-title{font-size:16px;font-weight:500;line-height:20px;color:var(--color-ink)}
/* 36px, not the mockup's 28 — rulebook v1.4 §Controls: "Control height 36px with pointer
   input; 44px on touch". Matches the Axi panel's close, which moved to 36 in g284 for the
   same reason; leaving this at 28 would have left the app's two panel closes mismatched.
   The <=720 rule below already takes both to the 44px touch target. */
.set-x,.backbtn{width:36px;height:36px;min-width:36px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;background:transparent;color:var(--color-muted);border-radius:var(--radius-chip);cursor:pointer}
/* The slack belongs BEFORE the search, not between the search and the X. With
   margin-left:auto on .set-x the free space landed in the middle: the search sat 14px
   from the "Settings" title and 196px from the close button (measured at 1440). Moving
   the auto margin to .set-search pushes the pair to the right, so the field reads as
   part of the header's right-hand controls. .set-x keeps its own auto for the mobile
   row, where .set-search goes full-width on its own line (order:3, flex:1 0 100%). */
.set-x:hover,.backbtn:hover{background:var(--color-overlay-hover);color:var(--color-ink)}
/* 20 — "buttons and nav". These are the panel's close and back controls. */
.set-x svg,.backbtn svg{width:20px;height:20px}
.set-cols{flex:1;min-height:0;display:flex}
.set-rail{width:216px;flex:0 0 auto;border-right:1px solid var(--color-line);overflow-y:auto;padding:12px 10px}
.setpanel .navrow{display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:0;background:transparent;color:var(--color-muted);font-size:14px;font-weight:500;line-height:20px;border-radius:var(--radius-control);cursor:pointer;text-align:left}
/* The rail is a SELECTABLE control, so §states rule 2 applies: "on a selectable control,
   hover brightens the text and nothing else… Selection keeps the fill; hover gives it up."
   This is the opposite of the app bar's ghost ACTIONS (Undo/Settings), which do fill on
   hover — an action has no persistent state to be confused with.
   It was doing both, and backwards: hover was `hover` (L* 12.74) while engaged was `control`
   (L* 10.27), so hovering an UNSELECTED row made it brighter than the selected one. Engaged
   now takes `overlay-hover` (L* 17.06), the top of the ladder and the only tone that reads as
   raised against the new overlay ground. */
.setpanel .navrow:hover{color:var(--color-ink)}
.setpanel .navrow.on{background:var(--color-overlay-hover);color:var(--color-ink)}
/* §9 — icon sizes SNAP to 16 / 20 / 24: "16 inline and rows, 20 buttons and nav, 24 headers".
   The rail is nav, so 20. `opacity:.8` is gone: §9 says colour comes from the parent's text
   token via currentColor, never from dimming the glyph — the row already carries muted at rest
   and ink when engaged, and the opacity was quietly darkening both. */
.nav-ic{width:20px;height:20px;flex:0 0 auto;display:inline-flex;color:inherit}
.nav-ic svg{width:20px;height:20px}
.set-main{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column}
/* 16 — inline inside a field. 15px was free-sized. */
/* --set-body-padx is the horizontal page gutter, published as a variable so the row hover band
   can bleed back out to the pane's edges without hardcoding the padding in two places (it
   changes to 12px at <=900). */
.set-body{--set-body-padx:20px;flex:1;min-height:0;overflow-y:auto;padding:8px var(--set-body-padx) 16px}
.set-root{display:none}
.cat-h{font-size:16px;font-weight:500;line-height:24px;color:var(--color-ink);padding:10px 0 2px}
/* Body (14) for the same reason as .sr-desc — a category summary is a sentence the user reads,
   and §3 names "summaries" as Body. It was 12 before this pass. */
.cat-sum{font-size:14px;line-height:1.45;color:var(--color-muted);padding-bottom:10px}
/* These rows carry NO box at any point — they are the TEXT tier (§6: "Selectable text: tabs,
   sub-navigation, view switches, filters that should not look like buttons"), which is what
   they are: sub-navigation into a settings pane. Two earlier attempts gave them a fill — an
   inset rounded tile, then a full-width band — and both read as a control that ought to be
   pressable in its own right, inside a list whose dividers already do the separating.
   Owner call, 2026-08-13: drop the fill entirely. The row rests one tone DOWN and brightens
   together on hover, so hover lands it on the tones §2 assigns (ink title, muted description)
   and rest is the dimmed state:
       rest    .sr-t muted · .sr-desc / icon / chevron faint
       hover   .sr-t ink   · .sr-desc / icon / chevron muted
   The title stays a step above its description at BOTH ends, so nothing collapses on arrival.
   No underline: the Text tier draws a `linestrong` line on hover, but in a vertical list that
   line lands on the row dividers. Tone alone carries it.
   Focus is unaffected — the global :focus-visible ring still applies, which matters more now
   that there is no fill to see. */
.setpanel .sr{position:relative;width:100%;display:flex;align-items:center;gap:12px;padding:13px 8px;background:transparent;border:0;border-radius:0!important;color:var(--color-ink);cursor:pointer;text-align:left;text-decoration:none}
.setpanel .sr .sr-t,.setpanel .sr .sr-desc,.setpanel .sr .sr-ic,.setpanel .sr .sr-chev{transition:color .15s ease}
.setpanel .sr .sr-t{color:var(--color-muted)}
.setpanel .sr .sr-desc,.setpanel .sr .sr-ic,.setpanel .sr .sr-chev{color:var(--color-faint)}
.setpanel .sr:hover .sr-t{color:var(--color-ink)}
.setpanel .sr:hover .sr-desc,.setpanel .sr:hover .sr-ic,.setpanel .sr:hover .sr-chev{color:var(--color-muted)}
.setpanel .sr~.sr{border-top:1px solid var(--color-line)}
/* g432 — THE CATEGORY OVERLAY BORROWS THE PANEL'S ROW LANGUAGE. Export, Saves & Data and any
   cloud-injected category now open as an overlay instead of a second panel column, and they emit
   the same `.sr` rows they always did — but every rule above is scoped `.setpanel .sr`, so in a
   modal they would have arrived completely unstyled. Same grammar, restated for the one place the
   rows now live outside the panel: rest is muted, hover lifts to ink, and a hairline divides
   siblings. Rows are full-bleed to the modal's padding so the divider spans the list rather than
   floating inside it, which is how the panel draws it too. */
.set-cat-modal .set-cat-list{margin:4px -24px 0}
.set-cat-modal .sr{
  position:relative;width:100%;display:flex;align-items:center;gap:12px;
  min-height:var(--ctl-h,36px);padding:12px 24px;
  background:transparent;border:0;border-radius:0!important;
  color:var(--color-ink);cursor:pointer;text-align:left;text-decoration:none}
.set-cat-modal .sr .sr-t{color:var(--color-muted);transition:color .15s ease}
.set-cat-modal .sr .sr-desc{color:var(--color-faint);transition:color .15s ease}
/* THE LIST ANSWERS WITH TYPE, NOT A TILE (g433)
   Owner: "instead of the hover style, when I hover the text would highlight and the others would
   fade — that's the better approach instead of a box hover for the lists."
   So the row no longer takes a fill. Pointing at the list dims EVERY row's title one rung
   (muted 66.99 -> faint 61.32) and lifts the one under the cursor to ink (95.49): ΔL* 34.17
   between the row you are on and the rest, against ~5.7 of movement for the rows you are not.
   The reading is the same one a fill gives — this row, not those — carried by the §6 text tier
   instead of a painted box, which is what the book asks a list to do.
   It also retires a whole class of problem: a fill has EDGES, so it has to be inset from its
   neighbours (§5 invariant 7, hover surfaces never touch) and it changes the row's painted extent.
   Type has neither, so the rows can sit at their natural rhythm.
   `(hover:hover)` matters more than usual here. On a touchscreen `:hover` sticks after a tap, and
   a sticky rule of this shape would leave the list permanently faded around whichever row was
   last touched — a fill merely looks odd, this would look broken. */
@media (hover:hover){
  .set-cat-modal .set-cat-list:hover .sr .sr-t{color:var(--color-faint)}
  .set-cat-modal .set-cat-list:hover .sr .sr-desc{color:color-mix(in srgb,var(--color-faint) 62%,var(--color-surface))}
  .set-cat-modal .set-cat-list .sr:hover .sr-t{color:var(--color-ink)}
  .set-cat-modal .set-cat-list .sr:hover .sr-desc{color:var(--color-muted)}
  /* The danger rows keep their own tier through all of it — a destructive row must not read as
     "just another faded row" when you are pointing somewhere else in the list. */
  .set-cat-modal .set-cat-list:hover .sr.danger .sr-t{color:color-mix(in srgb,var(--color-danger,#E0837B) 70%,var(--color-surface))}
  .set-cat-modal .set-cat-list .sr.danger:hover .sr-t{color:var(--color-danger,#E0837B)}
}
.set-cat-modal .sr~.sr{border-top:1px solid var(--color-line)}
/* Destructive rows (Reset to Blank Baseline, Erase All Workflows) keep the status tier they carry
   in the panel — the danger reads on the title, not as a fill. */
.set-cat-modal .sr.danger .sr-t{color:var(--color-danger,#E0837B)}
@media (max-width:720px){
  .set-cat-modal .sr{min-height:44px}
}
/* 16 — "inline and rows". 17px was free-sized, which §9 forbids outright: off the scale, and a
   17px glyph on a 24 grid lands the 1.5 stroke on a half-pixel. */
.sr-ic{width:16px;height:16px;flex:0 0 auto;color:var(--color-muted);display:inline-flex}
.sr-ic svg{width:16px;height:16px}
.sr-body{flex:1;min-width:0}
.sr-t{display:block;font-size:14px;font-weight:500;line-height:20px;color:var(--color-ink)}
/* Body (14), NOT Meta (12). g302 shrank this to 12 to create hierarchy; that was a
   misreading. §3: "Control labels are 14px at weight 500 — the same size as body copy,
   separated by WEIGHT and by the container they sit in, NEVER by being smaller", and Body is
   named as the rung for "descriptions, help text, summaries". Meta is for text a user SCANS
   (timestamps, counts), reached for deliberately — a row description is read as a sentence.
   The hierarchy here is .sr-t at 500/ink over .sr-desc at 400/muted: weight and tone, same rung.
   The faint→muted half of g302 was right and stays: §2 gives `muted` to secondary text and
   reserves `faint` for tertiary (.set-ver is the correct faint). */
.sr-desc{display:block;font-size:14px;line-height:1.45;color:var(--color-muted);margin-top:2px}
/* 16 — a row affordance, not a dense chip. 12 is reserved for dense chips and drag handles;
   14 is not on the scale at all. */
.sr-chev{width:16px;height:16px;flex:0 0 auto;color:var(--color-faint);display:inline-flex}
.sr-chev svg{width:16px;height:16px}
.setpanel .sr.danger .sr-t{color:var(--color-danger)}
.set-result-group+.set-result-group{border-top:1px solid var(--color-linestrong);margin-top:10px;padding-top:4px}
.set-foot{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 20px;border-top:1px solid var(--color-line)}
.set-ver{font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-faint)}
@media(min-width:901px){.setpanel .backbtn{display:none}}
@media(max-width:900px){
  .setpanel{width:100vw;border-left:0}
  .set-top{padding:18px 16px}
  .set-cols{display:block}
  .set-rail{display:none}
  .set-main{height:100%}
  /* The phone list is flat and permanent (g357): every destination is on it, so it is never
     swapped out for a category's contents. Only SEARCH replaces it — a result set spans
     categories and has no row of its own to live under. */
  .set-root{display:block}
  .set-detail{display:none}
  /* g400 — drilled in, the detail IS the sheet. The list steps aside rather than scrolling
     underneath, so a phone shows one full screen at a time and the back arrow is the way out.
     g408: keyed on `.has-cat`, not `body.settings-docked`. Docking only happens when a sub-page
     writes to #modalRoot, and a category LIST writes nothing — so when Export and Saves & Data
     became drill-in rows their destination landed on a pane that was still display:none. Caught
     by measuring the drill rather than trusting it: 8 rows present, pane hidden. `.has-cat` is
     the panel's own "a category is open" state and covers both kinds of destination. */
  .setpanel.has-cat .set-root{display:none}
  /* g411 — THE GUTTER COMES BACK FOR LIST DESTINATIONS. g408 zeroed this padding, which is right
     for a docked SUB-PAGE: `#modalRoot` moves in and `.modal` brings its own 20px inset, so a
     gutter here would double it. A category LIST has no modal wrapper, so the same rule left
     Export and Saves & Data rendering flush against the screen edge — rows at x=8 under a header
     at x=80 and a footer at x=20, three different left edges down one screen.
     `:has(#modalRoot)` is the honest test for which case this is: that element is only ever a
     child of `.set-detail` while a sub-page is docked into it. */
  .setpanel.has-cat .set-detail{
    display:block;height:100%;overflow:auto;padding:0 var(--set-body-padx,12px)}
  .setpanel.has-cat .set-detail:has(#modalRoot){padding:0}
  .backbtn{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
    background:transparent;border:0;border-radius:var(--radius-control);
    color:var(--color-muted);cursor:pointer;padding:0;margin-right:4px}
  .backbtn:hover{background:var(--color-hover);color:var(--color-ink)}
  .backbtn svg{width:22px;height:22px;display:block}
  .set-body{--set-body-padx:12px}
  .setpanel .sr{min-height:60px;padding:12px 8px}
  .set-x,.backbtn{width:44px;height:44px;min-width:44px}
}
@media(prefers-reduced-motion:reduce){.setpanel{animation:none}}

/* ---- Light-mode parity for dark's SOFT layering (sub-5% white fills and
       hairlines that silently vanish on a light page): the toolbar's soft
       separator line, the sidebar's soft section boxes, nav-button fills,
       and the Following-Step chips now carry ink-alpha equivalents so the
       light theme keeps the same gentle segmentation as dark. ---- */
:root[data-mode="light"] .toolbar{border-bottom:1px solid rgba(20,24,30,.12)!important}
/* Section separation is the top hairline + spacing in BOTH themes (was a faint light-only
   box tint here -> dropped so light matches dark: eyebrow label + hairline, no box). */
:root[data-mode="light"] .nav-button{background:rgba(20,24,30,.035)}
:root[data-mode="light"] .step-link{background:rgba(20,24,30,.04)}

/* ===== Toolbar view-switcher (Legend/Structure/Review/Comments) — concept A1 "Quiet Baseline"
   Was a segmented control (bordered --color-control track, filled active segment). The product is
   already button-heavy, so the four sibling VIEWS now read as a tab rail rather than a control:
   plain labels on a hairline, the active one inked with a 2px underline seated ON that hairline.

   Deliberately NOT applied to .seg (subpath "Show All / Main Path Only", AI scope/depth, template
   tabs). Those pick an OPTION within one view; these switch the view. The older comment by .seg
   says the two were one component — A1 splits them on purpose, so .seg keeps the segment look. */
.viewseg{display:inline-flex;align-items:center;background:transparent;border:0;
  border-bottom:1px solid var(--color-line);border-radius:0;padding:0;gap:0;flex:none}
/* The mockup grounds the rail on a FULL-WIDTH hairline. In the toolbar that hairline already
   exists — it is the bar's own bottom border — so the rail must not draw a second, 302px-wide
   one 9px above it (measured: rail bottom y=46, bar border y=55; Gemini read the pair as a
   double-rule papercut). The bar's border is the baseline here; the rail keeps its own only in
   the sidebar drawer, where it is a standalone full-width row with nothing to collide with. */
.toolbar-toggles > .viewseg{border-bottom:0}
.viewseg button{height:36px;padding:0 12px;border:0;background:transparent;color:var(--color-muted);
  font-size:14px;font-weight:500;letter-spacing:.02em;border-radius:0;cursor:pointer;white-space:nowrap;
  position:relative;display:inline-flex;align-items:center;gap:6px;transition:color .15s ease}
/* the indicator spans the label content, and sits ON the rail hairline (bottom:-1px covers it) */
/* The indicator DRAWS IN from the left rather than switching on: §6 Buttons · Text tier says the
   line "draws in underneath", which is a wipe, not a fade. scaleX on the 2px rule only — the
   control itself never moves on hover (see the motion block), so this is not the hover-lift
   pattern and must not be swept up with it. */
.viewseg button::after{content:"";position:absolute;left:12px;right:12px;bottom:-1px;height:2px;
  background:transparent;transform:scaleX(0);transform-origin:left center;
  transition:transform .26s cubic-bezier(.4,0,.2,1),background .26s cubic-bezier(.4,0,.2,1)}
.viewseg button:hover::after,.viewseg button.on::after{transform:scaleX(1)}
.viewseg button:hover{color:var(--color-ink)}
.viewseg button.on{color:var(--color-ink)}
.viewseg button.on::after{background:var(--color-ink)}
/* the label's colour rides the same curve as its underline, so the tab reads as ONE motion
   rather than a fast text flick followed by a slower line */
.viewseg button{transition:color .26s cubic-bezier(.4,0,.2,1)}
@media(prefers-reduced-motion:reduce){.viewseg button::after{transition:background .15s ease}
  .viewseg button{transition:none}}
/* The count attaches as TYPE, never as a pill — override the filled .nav-badge (0,1,0) at (0,2,0).
   Mockup caption: "a plain numeral in faint, separated by a 6px gap" — the 6px is the button's own
   flex gap, so the badge carries no margin of its own. */
.viewseg .nav-badge{font-variant-numeric:tabular-nums;display:inline;min-width:0;height:auto;
  padding:0;margin-left:0;background:none;border:0;border-radius:0;color:var(--color-faint);
  font-size:12px;font-weight:500}
/* the drawer copy lives in the sidebar; only one is visible at a time */
.viewseg-drawer{display:none;width:100%;margin:8px 0 10px}
@media (max-width:1100px){
  /* The rail is PERMANENT in the bar now — it must not defer to the drawer below 1100, or
     there is no view switcher at all on tablet and phone. (The drawer copy is hidden further
     down; hiding both left the app with no way to reach Legend/Structure/Review/Comments.) */
  .viewseg-drawer{display:flex}
  /* touch rung; the tabs share the width, so the indicator spans each equal cell */
  .viewseg-drawer button{flex:1 1 0;min-width:0;justify-content:center;min-height:44px;height:44px;padding:0 6px}
  .viewseg-drawer button::after{left:6px;right:6px}
}

/* ===== Phone toolbar compaction (restores the deliberate <=430px behaviour) =====
   Before the Settings panel, Workflow View / Undo / Redo were display:none on phones
   because they were reachable inside the "Settings ▾" menu. That menu no longer exists,
   and the panel deliberately does NOT carry them (locked decision: primary/mode toggles
   stay directly accessible; only genuinely secondary actions fold away). So instead of
   hiding them, Undo/Redo collapse to 44px icon-only buttons — still tappable, still in
   the DOM for screen readers — which buys back the row that the text labels cost. */
@media (max-width:430px){
  .toolbar-actions>#undoBtn,.toolbar-actions>#redoBtn{
    flex:0 0 auto;width:44px;min-width:44px;padding:0;font-size:0;justify-content:center}
  /* g360: the Unicode stand-ins are GONE. This rule predates these buttons having icons — back
     then Undo/Redo were text-only, so ≤430px zeroed the font and injected "↶" / "↷" as a
     substitute glyph. They later gained real inline SVGs (.tog-ico) and nobody removed the
     ::before, so below 430px BOTH drew: a small Figtree arrow beside the real one. That is the
     "two arrows each" the owner reported, and it is pre-existing — enlarging the icon to 22px
     only made it legible enough to notice. The SVG is the icon; the pseudo-element is not. */
  .toolbar-actions>#undoBtn::before,.toolbar-actions>#redoBtn::before{content:none}
  /* the mode toggle keeps its label — it is a primary control */
  .toolbar-actions>#diagramToggle{flex:1 1 100%}
}

/* ===== Legend (screen 3): compact rows with STRAIGHT dividers =====
   The legend previously drew every role/icon as its own bordered, rounded card.
   The approved mockup uses a compact list: no per-row border/fill, a single 1px
   hairline between rows, and NO corner radius so the divider reads as a straight
   line edge-to-edge (a rounded row clips the divider ends into curves).
   Scoped to .legend-panel — .item is shared with the Structure manager. */
.legend-panel .list{gap:0}
.legend-panel .list>.item{
  /* !important is required: two global radius-group rules (the 14px "softer corners"
     pass and the later cards/panels pass) both set .item radius with !important.
     Scoped override rather than editing those groups — Structure's cards must keep
     their radius. */
  border:0!important;border-radius:0!important;background:transparent!important;
  padding:13px 8px;align-items:flex-start}
.legend-panel .list>.item + .item{border-top:1px solid var(--color-line)!important}
.legend-panel .list>.item:hover{background:var(--color-hover)}
/* whole row is the drag affordance (no drag-dot handles, by design) */
.legend-panel .list>.item.drag-item{cursor:grab}
.legend-panel .list>.item.drag-item:active{cursor:grabbing}
/* the second .item-meta line is the description */
.legend-panel .list>.item .item-meta+.item-meta{color:var(--color-faint);margin-top:2px}

/* ===== The `muted` text tier =====
   `class="muted"` appears 54 times in app.js markup and app.css defined `.muted{color:…}`
   NOWHERE — only three narrowly-scoped rules (.polish-status, .review-checks-foot,
   .tut-tour-row). Everything else inherited its parent, so measured across every view,
   7 of 12 `.muted` elements in the table and 8 of 15 in the AI modal rendered at full ink.
   All of them are text the markup asks to de-emphasise.
   Deliberately placed BEFORE the per-screen blocks below: several of them put a specific
   element on --faint (e.g. .brand-hint) and, at equal (0,1,0), must still win on order. */
.muted{color:var(--color-muted)}

/* ===== Shared page head (screens 03 Legend + 04 Structure) =====
   The locked mockups give both screens the same head: a small plain eyebrow
   ("Reference" / "Manage"), then a light 28px display title, then the lead.
   Scoped to .pagehead-view so the other .legend-top consumers (Cross-references,
   AI review) keep the existing 23px h2 until their own screens are built. */
.pagehead-view .legend-top{align-items:flex-start}
.ph-eyebrow{font-size:11px;font-weight:500;letter-spacing:.04em;line-height:16px;color:var(--color-faint)}
/* !important is required: two global groups already pin .legend-top h2 with it —
   the type-scale pass (font-size:16px) and the "display voice" pass (font-weight).
   Scoped override rather than editing either shared group; the other .legend-top
   consumers must keep 23px/350 until their own screens are built. */
.pagehead-view .legend-top h2{font-size:28px!important;font-weight:300!important;line-height:34px!important;margin-top:10px}
@media (max-width:720px){
  .pagehead-view .legend-top h2{font-size:24px!important;line-height:30px!important;margin-top:8px}
}

/* ===== Structure Manager (screen 04): one card per section, hairline rows =====
   Live drew the section header AND every workflow as its own bordered, rounded
   card nested inside another card — three stacked card edges per row. The mockup
   is one card per section: an unboxed header, then rows separated by a single
   hairline (same language as the Legend rows in batch 4).
   Scoped to .structure-panel because .item is shared with the Legend. */
.structure-panel>.list{gap:24px}
/* radius now comes from the global corners pass (--radius-card, 16px) — batch 5's
   scoped override was only needed while that pass still resolved to 14px */
.structure-panel>.list>.card.page-card{padding:8px 8px 12px}
/* section header row — unboxed, the card around it is the box */
.structure-panel>.list>.card.page-card>.item{
  border:0!important;border-radius:0!important;background:transparent!important;padding:12px}
/* the workflow list: inline style="margin-top:10px" needs !important to flatten */
.structure-panel .list .list{gap:0;margin-top:0!important}
.structure-panel .list .list>.item{
  border:0!important;border-radius:0!important;background:transparent!important;
  padding:14px 12px;border-top:1px solid var(--color-line)!important}
.structure-panel .list .list>.item:hover{background:var(--color-hover)!important}
.structure-panel .list .list>.item.drag-item{cursor:grab}
.structure-panel .list .list>.item.drag-item:active{cursor:grabbing}
/* Row actions are ghost; only the "+ Add …" buttons keep a border (btn-secondary).
   The mockup's 28px is already on the app's control ladder as --ctl-h-xs ("micro
   icon buttons"), so use the token — hard-coding 28px would drop these buttons off
   the ladder if the ladder is ever retuned. !important is required because the
   control-height group pins .btn.small height/padding with it. */
.structure-panel .actions .btn.small,
.structure-panel .actions .edit-pen{
  border-color:transparent;background:transparent;color:var(--color-muted)}
.structure-panel .actions .btn.small{
  height:auto!important;min-height:var(--ctl-h-xs)!important;
  padding:0 12px!important}
.structure-panel .actions .edit-pen{
  width:var(--ctl-h-xs);height:var(--ctl-h-xs);min-width:var(--ctl-h-xs);padding:0}
.structure-panel .actions .edit-pen svg{width:14px;height:14px}
.structure-panel .actions .btn.small:hover,
.structure-panel .actions .edit-pen:hover{background:var(--color-hover);color:var(--color-ink)}
.structure-panel .actions .btn.small.success{border-color:var(--color-linestrong);color:var(--color-ink)}
.structure-panel .actions .btn.small.danger{color:var(--color-danger)}

/* Tablet and below (mockup breakpoint 1024): the two-column row can no longer hold
   a long title AND four actions, so the actions drop to their own full-width row,
   left-aligned and indented to sit under the title rather than ragged-right. */
@media (max-width:1024px){
  .structure-panel>.list{gap:16px}
  .structure-panel .item{grid-template-columns:minmax(0,1fr)}
  .structure-panel .item>.actions{width:100%;justify-content:flex-start;padding-left:28px;flex-wrap:wrap}
}
/* Phones (mockup breakpoint 768): restore real touch targets. The 28px desktop size
   is a pointer size — the mockup pins .btn/.btn-sm to min-height:44px here, and the
   pencil to a 44px square. NOTE: the same 44px rule sits in the SHARED mockup CSS for
   every screen, but the app's global control group currently pins .btn.small to 32px
   app-wide; lifting that globally would relayout every view, so it needs its own pass. */
@media (max-width:768px){
  /* The app's phone convention is width:100% per button (one action per row). That is
     right for a modal's 2-3 buttons, but Structure has 4 actions on every section AND
     every workflow — at 33 workflows that is ~130 full-width rows to scroll past. The
     mockup instead makes the CONTAINER full-width and lets the buttons wrap inside it,
     so restore auto width here (container stays 100% from the 1024 block above). */
  .structure-panel .item>.actions .btn.small{width:auto!important;flex:0 0 auto}
  .structure-panel .actions .btn.small{min-height:44px!important;padding:0 14px!important}
  .structure-panel .actions .edit-pen{width:44px;height:44px;min-width:44px}
  .structure-panel .actions .edit-pen svg{width:18px;height:18px}
  /* Drop the 28px indent on phones. The app floors action buttons at min-width:88px,
     so at 320px the indent leaves 180px of usable row — 4px short of the 184px two
     buttons need — and every action wraps onto its own line. Without it they pair up. */
  .structure-panel .item>.actions{gap:8px;padding-left:0}
}

/* ===== Shared segmented control (screens 05 Review + 06 Comments + 09 Templates) =====
   The mockups use ONE segmented component everywhere a small set of mutually
   exclusive options is chosen: a bordered container on --color-control with the
   options inside it, active option raised onto --color-surface2. Batch 2 already
   built exactly this as .viewseg, so .seg matches it rather than inventing a variant.

   NOTE: .seg wraps ONLY the mutually-exclusive buttons. It is deliberately not
   applied to .ai-scope / .ai-depth themselves — those are the whole labelled ROW
   (they also hold "Check:", the workflow picker, the "vs. source" toggle and the
   context badge), and .ai-scope is shared with the workflow-review and handover
   modals. Styling the row as the segment swallowed the context badge into the
   bordered group. Active class differs by origin: .sel on the AI buttons,
   .on on the comments filters. */
.seg,.cmt-ov-filters,.tmpl-tabs{
  display:inline-flex;align-items:center;flex-wrap:wrap;
  /* fit-content, not just inline-flex: .cmt-ov-filters is a block-level flex row in
     the comments card and a more specific existing rule keeps it display:flex, so it
     would stretch the full card width instead of hugging its four options. */
  width:fit-content;max-width:100%;
  background:var(--color-control);
  border-radius:var(--radius-control);padding:3px;gap:2px}
.seg>.ai-scope-btn,.cmt-ov-filters>.cmt-ov-chip,.tmpl-tabs>.tmpl-tab{
  height:auto!important;min-height:var(--ctl-h-xs)!important;padding:0 14px!important;
  border:1px solid transparent!important;background:transparent!important;
  color:var(--color-muted);font-size:12px;font-weight:500;letter-spacing:.01em;
  border-radius:var(--radius-chip)!important;cursor:pointer;white-space:nowrap;
  transition:background .15s ease,color .15s ease,border-color .15s ease}
.seg>.ai-scope-btn:hover,.cmt-ov-filters>.cmt-ov-chip:hover,.tmpl-tabs>.tmpl-tab:hover{color:var(--color-ink)}
.seg>.ai-scope-btn.sel,.cmt-ov-filters>.cmt-ov-chip.on,.tmpl-tabs>.tmpl-tab.active{
  background:var(--color-surface2)!important;color:var(--color-ink);
  border-color:var(--color-linestrong)!important;font-weight:500}
@media (max-width:768px){
  /* touch targets, same contract as the Structure rows in batch 5 */
  .seg>.ai-scope-btn,.cmt-ov-filters>.cmt-ov-chip,.tmpl-tabs>.tmpl-tab{
    min-height:44px!important;width:auto!important;flex:0 0 auto}
}

/* ===== Review (screen 05) ===== */
/* the AI-review card carried a 2px top edge; the mockup card is a flat 1px box */
.ai-check{border-top-width:1px!important}
/* "What this check looks for" — a quiet surface2 well, not a raised overlay chip */
.v-rules{background:var(--color-surface2)!important;border-radius:var(--radius-control)!important}
.v-rules>summary{padding:14px 16px}
/* issue rows: card-surface with a 2px severity edge, control radius, roomier padding.
   g414 — THE SEVERITY EDGE AND THE RADIUS BOTH GO, HERE, at the rule that actually decides them.
   This block is why two later attempts measured no change: it pins background AND border-radius
   with !important, so a `surface2` fill and a `border-radius:0` added further down the sheet were
   both silently outranked. Escalating again would have worked and left three rules arguing; the
   honest fix is the one that owns the value.
     · the radius goes to 0 — the row is now separated by a divider, and a border-bottom on a
       rounded box curls at both ends, which is the curvature the owner saw. A rule is straight.
     · `border-left-width:2px` goes with the stripe removed at g413.
     · the background stays `surface`. It matches the card behind it, which is exactly why the
       rows needed the divider — but changing it here would re-tone every issue row on the screen,
       and the divider already solves what was reported. */
.validate-list{gap:12px}
.validate-row{
  background:var(--color-surface)!important;border-radius:0!important;
  padding:14px 16px}
/* g447 — the ghost-text version of "Fix … with Axi" is deleted; see the note where the first of
   these three contradictory rules used to be. Its hover also set `color:ink`, which the surviving
   rule supplies from its REST state, so nothing is lost by removing it. */

/* ===== Comments / Governance overview (screen 06) ===== */
/* eyebrow: tracked caps. !important because the type-scale pass pins .kicker to 13px. */
.review-card-head .kicker,.comments-overview .kicker{
  font-size:var(--text-eyebrow)!important;font-weight:var(--weight-eyebrow);
  letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--color-faint)}
.review-card-head h2,.comments-overview h2{
  font-size:28px!important;font-weight:400!important;line-height:28px!important;margin-top:8px}
/* the "unavailable" well */
.review-readonly-note{
  background:var(--color-surface2)!important;border-radius:var(--radius-control)!important;padding:16px}
/* status chip sits on --color-control so it reads as a chip, not floating text */
.review-status-pills .v-pill{
  min-height:26px;padding:0 10px;background:var(--color-control);border-color:var(--color-line);
  color:var(--color-faint);border-radius:var(--radius-chip)}

/* ===== Modal shell — the mockups' own comment calls it "shared by screens 7·8·10·15" =====
   The live shell already had the right bones (flex column, sticky full-bleed footer via
   negative margins — the P0 mobile fix). This retunes it to the locked numbers rather
   than restructuring markup, so every modal in the app inherits the change. */
/* THE SCRIM BLURS WHAT IT COVERS (g424). Owner's call, and it reverses a standing rejection, so
   the scope is worth stating: translucency is banned on SURFACES — the panel, the chrome, the
   cards are opaque and stay opaque. This is the scrim, which is not a surface. Its whole job is
   to say "the thing behind this is out of play", and softening the structure behind it says that
   more plainly than dimming alone.
   8px, not more: at .6 black (dark) and .45 (light) roughly half the canvas still reads through,
   so the blur lands as the table losing its rows rather than as frosted glass. The value is
   deliberately below the radius where text turns to smear and the effect starts announcing
   itself — "a little bit", which is what was asked for.
   -webkit- prefix for Safari, which still needs it. */
.modal-backdrop{
  background:var(--color-scrim);align-items:center;
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
  padding:24px 24px calc(24px + env(safe-area-inset-bottom, 0px))}
.modal{
  width:min(760px,100%);padding:24px;
  /* svh, not vh/dvh — mobile browser chrome (standing responsive rule) */
  max-height:calc(100svh - 48px);
  /* the floating-layers pass pins --line2 (.22) with !important; the mockups use the
     softer --linestrong (.14) on the modal edge */
  border-color:var(--color-linestrong)!important}
/* the type-scale pass pins --fs-title and the display-voice pass pins the weight.
   Tracking is the display tier (-0.01em → -0.22px at 22px); the modal inherits the
   body's +0.01em otherwise, which is the wrong direction for a title. */
.modal h2{font-size:28px!important;font-weight:300!important;line-height:28px!important;margin:0 0 8px;
  letter-spacing:var(--tracking-display)}
/* .rename-hint IS the mockups' .modal-desc — it carries the one-line description under the
   title on screens 07/08/09/10/15 (and a dozen other modals). 13.5/1.55, not 13/1.45. */
.modal .rename-hint{font-size:14px;line-height:1.55}
.modal-actions{margin:24px -24px -24px;padding:16px 24px;bottom:-24px;border-top-color:var(--color-line);
  gap:8px}
/* THE FOOTER SPLITS: LEAVING ON THE LEFT, COMMITTING ON THE RIGHT (g425). Owner's call — Clear
   Logo, Delete and Cancel belong at the left edge, away from the button that commits.
   Done as ONE rule rather than by editing thirty-odd footers, and it works because every footer
   in the app already ends with its primary: `Clear Logo | Cancel | Save`, `Reset to Default |
   Cancel | Apply`, `Clear Log | Close | Export`, `Back to AI Settings | Cancel | Re-check`.
   Pushing the primary right with an auto margin therefore strands everything before it on the
   left, which is exactly the split asked for, and no markup moves.
   NOT `justify-content:space-between`, which only reads correctly with exactly two children — on
   the three-button footers it would park Cancel in the middle of the bar.
   This is also the mockups' own pattern, not a new one: `.tut-modal .modal-actions` has split
   dismiss-left / Close-right since the reskin, with the mockup comment to match.
   Footers with no primary (a lone Close, a lone Clear Source Items) are untouched and stay
   right — a single button alone at the left edge reads as a mistake rather than a decision. */
.modal-actions .btn.primary{margin-left:auto}
/* Two primaries in one footer (Context Library's `Close | Extract | Add Source First`): only the
   first opens the gap, or the pair would split it between them and drift apart. */
.modal-actions .btn.primary ~ .btn.primary{margin-left:0}
/* Ghost tier for the footer's leading, low-emphasis action (Reset to Default, and any
   future equivalent on the sibling modals). The mockup's .btn-ghost carries no outline:
   muted text on the bar itself, so it can't be mistaken for the secondary Cancel. */
.modal-actions .btn.ghost{color:var(--color-muted);border-color:transparent}
.modal-actions .btn.ghost:hover{background:var(--color-hover);color:var(--color-ink);border-color:transparent}
/* Field labels are tracked caps.
   Descendant, not child: some fields nest two levels deep (.form-grid > .wide > .wide
   > label in the Ollama block) and a child selector silently skipped them.
   :not([class]) is the discriminator: a field CAPTION is a bare text-only <label>,
   while every interactive label in these modals carries a class and wraps controls
   (.logo-drop, .brand-pick, label.btn, .tmpl-vis-opt). Without it, text-transform
   inherited into the tiles and uppercased their titles AND body copy.
   !important on the COLOUR only: `.modal label{color:var(--text-soft)!important}` (the
   light-mode block, ~L1516) is (0,1,1) but important, so it beat this rule's (0,3,1) and
   every caption in every modal rendered at full ink. Captions are --faint. */
.modal .form-grid label:not([class]),.modal>label:not([class]){
  font-size:var(--text-eyebrow);font-weight:var(--weight-eyebrow);
  letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--color-faint)!important}
/* A NOTE inside a field caption is not part of the eyebrow. The mockups' `.fl-note`
   explicitly resets the caption's tracked caps: 12/400, no tracking, no uppercase, --faint.
   Without it the note inherits 11px at .12em and reads as more eyebrow. */
.modal .form-grid label:not([class]) .muted,
.modal .form-grid label:not([class]) small,
.modal>label:not([class]) .muted{
  font-size:12px;font-weight:var(--weight-body);letter-spacing:normal;text-transform:none;
  color:var(--color-faint)!important}

/* Modal body text fields. The mockups put every one at the 44px control height on
   --control with a .08 hairline; the app's global input rule pins 14/16 padding at
   --fs-body and !importants the border to --line2 (.14), which came out 51.7 tall.
   The fill was already right (--paper3 IS --color-control).
   >=721 ONLY: below that the app deliberately runs taller touch targets. */
@media (min-width:721px){
  .modal input[type=text],.modal input[type=password],.modal input[type=email],
  .modal input[type=url],.modal input[type=number],.modal select{
    height:var(--ctl-h-lg,44px);min-height:44px;padding:0 14px;font-size:14px;
    border-color:var(--color-line)!important}
  /* Body buttons are the 36px control, not the 28px chrome tier. The small tier is right
     in toolbars and rows elsewhere in the app; inside a modal the mockups draw exactly one
     button size. Every value here has to be !important — six layered passes pin
     .btn.small's height, radius and size with !important at (0,1,0). */
  .modal .btn.small,.modal label.btn.small{
    height:36px!important;min-height:36px!important;padding:0 16px!important;
    font-size:var(--text-button)!important;border-radius:var(--radius-control)!important}
}

@media (max-width:640px){
  /* full-screen sheet: the mockup drops the inset, radius and border entirely */
  .modal-backdrop{padding:0;align-items:stretch}
  /* padding-bottom:0 is what actually lands the footer ON the bottom edge (owner call
     2026-08-14). The rule below already zeroed the footer's negative margin and set
     bottom:0, but a sticky element bottoms against the PADDING box — so the modal's own
     24px bottom padding parked the bar 24px up, floating over a strip of modal with the
     screen edge below it. Measured before: modal 0->812 (correctly full-screen) with the
     footer ending at 788. The footer carries its own padding plus the safe-area inset, so
     nothing loses breathing room by taking it off the container. */
  .modal{width:100%;max-width:none;max-height:100svh;border-radius:0!important;border:none;padding-bottom:0}
  .modal-actions{margin-bottom:0;bottom:0;padding-bottom:calc(16px + env(safe-area-inset-bottom, 0px))}
}

/* ===== Appearance (screen 07) ===== */
/* Mode picker becomes the full-width variant of the shared segmented control:
   equal columns, container on --color-control, active option raised. */
.theme-mode-row{
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  background:var(--color-control);
  border-radius:var(--radius-control)!important;padding:3px;gap:2px;overflow:visible}
.theme-mode-btn{
  height:40px;padding:0 14px;border:1px solid transparent!important;background:transparent;
  color:var(--color-muted);font-size:14px;font-weight:500;border-radius:8px;
  transition:background .15s ease,color .15s ease,border-color .15s ease}
.theme-mode-btn+.theme-mode-btn{}
.theme-mode-btn:hover{background:transparent;color:var(--color-ink)}
.theme-mode-btn.sel{
  background:var(--color-surface2);color:var(--color-ink);border-color:var(--color-linestrong)!important}
/* Brand-colour row. The mockup's .brand-btn is a 44px control at the body-adjacent 13.5px,
   one rung up from the 36px/12.5px the app had — this row is the modal's primary choice,
   not a toolbar. Row gap 10 to match. */
.brand-row{gap:10px}
/* margin-bottom:0 — .brand-pick is a <label>, so the generic `label` rule's 0 0 6px was
   padding the flex row out to 50px against the mockup's 44. */
.brand-none,.brand-pick{height:44px;padding:0 16px;font-size:14px;margin-bottom:0}
.brand-pick-label{font-size:14px}
/* Helper copy under the row. --fs-label-sm is 10.5px — below the rulebook's 11px eyebrow
   rung — and the `muted` class it carries has no colour rule anywhere, so it inherited
   full ink. The mockup's .helper is 12.5/--faint. */
.brand-hint{font-size:12px;color:var(--color-faint)}
/* Logo drop targets. The mockup gives them a recessed --surface2 well inside the #1C1C1C
   overlay, card radius, and 16/14 metrics; without the fill they read as empty outlines
   rather than drop targets.
   DASHED, deliberately. A Pass B batch had flipped this to solid on a comment reading "the
   mockup draws them as solid tiles, not dashed drop zones" — that premise was wrong:
   kimi-screen07.html sets `border:1px dashed var(--linestrong)` and this file's own base
   rule (~L2699) was dashed before that override. Restored, owner-confirmed. */
.logo-drop{padding:16px;gap:14px;border-style:dashed;border-color:var(--color-linestrong);
  border-radius:var(--radius-card);background:var(--color-surface2);margin-bottom:0}
.logo-drop:hover{border-color:var(--color-ink)}
/* same 10.5px undersize as .brand-hint above; the mockup's .dz-sub is 12/--faint */
.logo-drop-text small{font-size:12px;color:var(--color-faint)}
@media (max-width:640px){
  .theme-mode-row{grid-auto-flow:row}
}

/* Per-modal widths. The shell is shared but each locked screen sets its own max-width;
   the app previously used one 820px value for all of them. */
.modal.theme-modal{width:min(760px,100%)}    /* screen 07 Appearance      */
/* g429 — WIDER, WHICH IS ALSO HOW IT GETS SHORTER. Measured at 1440x900 the overlay was 820
   wide and 852 TALL: 94.7% of the viewport, still scrolling 66px, with the capability list cut
   mid-row by the footer. It was the tallest thing in the app while leaving 310px of dead ground
   down each side. Widening reflows the prose and the two-column capability list into fewer
   lines, so the height comes down without removing anything — the owner asked for both, and
   they are the same fix. 1040 keeps the modal's 24px gutter clear at 1100 and still collapses
   to the viewport below that. */
.modal.ai-modal{width:min(1040px,100%)}      /* screen 08 AI & API key    */
.modal.tmpl-lib-modal{width:min(1120px,100%)}/* screen 09 Template Library*/
.modal.ver-modal{width:min(720px,100%)}      /* screen 10 Version history */
.modal.tut-modal{width:min(880px,100%)}      /* screen 15 Welcome         */
/* =====================================================================================
   GET STARTED FITS WITHOUT SCROLLING ON A REAL SCREEN (g398)
   =====================================================================================
   Owner: "the Get Started tab having a scroll is kind of unnecessary… it can have a scroll on
   the smaller screen sizes." Measured before this change: the modal was 880 wide at EVERY width
   and its content scrolled internally at all eight sizes tested, 1920 included. A seven-step
   welcome that you have to scroll on a 1920 display is a layout problem, not a content one.

   The lever is the COLUMN, not the copy. Seven steps in one 880px column is a tall thin ribbon;
   the same seven in two columns is four rows, and the modal is nowhere near the width a large
   screen can spare. So from 1280 up it widens to 1160 and the step list goes two-up.

   Honest about the limit: at 1024 and below there is not enough height for seven steps however
   they are arranged (768px of viewport against ~230px of intro, tour row and footer before a
   single step is drawn), so those widths keep their scroll — which is what the owner asked for.
   The two-column grid starts at 1280 rather than 1024 for the same reason: at 1024 it would buy
   height the viewport cannot use while halving the reading measure. */
@media (min-width:1280px){
  .modal.tut-modal{width:min(1160px,100%)}
  .modal.tut-modal .tut-steps{
    display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start}
  /* the intro and the tour row stay full width — they introduce the list, they are not in it */
  .modal.tut-modal .tut-step{align-items:flex-start}
}
/* THREE COLUMNS FROM 1440, because two was not enough. Measured with the shortened copy: content
   1018px against an 852px ceiling at 1440, so two columns cleared 1920 and still missed the size
   most people actually work at. Seven steps over three columns is three rows instead of four,
   which is the ~170px that was needed. The reading measure drops to about 370px, which a
   two-line body carries comfortably — this list is scanned, not read through. */
@media (min-width:1440px){
  .modal.tut-modal .tut-steps{grid-template-columns:repeat(3,1fr);gap:10px}
  /* The last ~25px, taken from the grid's own spacing rather than from the copy. In a 3-up grid
     the tiles already read as separate objects, so 12 -> 10 costs nothing legible and is what
     tips 1440x900 under its ceiling. Below 1280 the list is a single column and keeps the 12. */
  .modal.tut-modal .tut-step{padding:10px}
  .modal.tut-modal .tut-tour-row{padding:10px}
}
/* the Welcome modal is the one display-voice title in the set: lighter and larger.
   2026-08-14: it was 24px, which is not on the scale at all (28/16/14/12/11) — it sat
   between two rungs and so read as neither. It goes UP to 28, which is where every other
   modal heading already sits (see the .modal h2 rule above), so "larger" is now true
   against the scale instead of against a number picked off it. The 300 weight stays: the
   display voice here is carried by weight, which is what made this title distinct. */
.modal.tut-modal h2{font-size:28px!important;font-weight:300!important;line-height:34px!important}

/* SCROLL CUE (owner call 2026-08-14). This modal holds 1661px of content in an 850px window,
   so steps 4-7 are below the fold — and macOS overlay scrollbars paint nothing until you
   already scroll, which is exactly the moment the cue is no longer needed. A first-time
   reader had no way to know the list continued.
   A persistent thumb is the honest cue: unlike a gradient fade it shows EXTENT as well as
   existence, and it does not veil the content it sits over. Recipe copied verbatim from
   .chat-log — that rule carries the note "so all scrollbars read consistently", and this is
   what consistent means. Thumb rides on a transparent track with a 3px inset via
   background-clip, and brightens on hover like the others. */
/* EVERY OVERLAY, AND THE BAR STOPS SHORT OF THE CORNER (g435)
   =======================================================================================
   Owner: "the sides of all these overlays are curved in the corners — we don't want the scroll
   to go all the way to the edge of the corner. It looks a bit weird." Right on both counts, and
   it was two separate faults wearing one symptom.
   FIRST, the recipe below was scoped to `.modal.tut-modal` alone. Every other overlay — AI,
   Templates, Version History — scrolled with the platform's default bar, which is why one dialog
   looked considered and the rest looked unfinished. Generalised to `.modal`, which is what the
   note it was copied from ("so all scrollbars read consistently") always intended.
   SECOND, and this is the corner: the scroller IS the modal, so its track runs the full height of
   an element with a 20px radius. A straight bar in a rounded box has nowhere to go at the ends
   but into the curve, and it reads as the bar escaping the dialog. `scrollbar-track` takes a
   margin, so the track simply starts and stops one radius in — the bar now travels the straight
   part of the edge and never enters the arc. Keyed to the radius token, so if the modal's corner
   changes the inset follows it instead of drifting.
   Firefox gets the thin bar and the colours but not the inset — `scrollbar-width` has no track
   equivalent. Accepted: Firefox's own bar already sits inside the padding box. */
/* ⚠️ THE STANDARD PROPERTIES AND THE WEBKIT PSEUDOS ARE MUTUALLY EXCLUSIVE. Since Chrome 121,
   setting `scrollbar-width` or `scrollbar-color` on a scroller makes Chromium IGNORE every
   `::-webkit-scrollbar` rule for it. The recipe this was copied from sets both, which means its
   custom thumb has quietly not been rendering in current Chrome (measured here on 148) — the
   bars that look "styled" are the platform's own. Writing both here would have shipped a track
   margin that could never paint.
   So each engine gets only the one it honours: the pseudos for Chromium/WebKit, and the standard
   properties for Firefox behind a support query for a selector Firefox does not implement. */
.modal::-webkit-scrollbar{width:10px}
.modal::-webkit-scrollbar-track{background:transparent;margin:var(--radius-modal,20px) 0}
.modal::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16);border-radius:6px;border:3px solid transparent;background-clip:content-box}
.modal:hover::-webkit-scrollbar-thumb{background:rgba(255,255,255,.30);background-clip:content-box}
/* Firefox only — it does not implement ::-webkit-scrollbar, so this query never matches in
   Chromium/WebKit and cannot switch their pseudos off. */
@supports not selector(::-webkit-scrollbar){
  .modal{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.20) transparent}
}
/* Inner scroll regions inside an overlay get the same bar. They sit within the modal's padding
   so they are already clear of its corners — no track margin, or the bar would float away from
   content that starts at the region's top edge. */
.modal .tmpl-lib-body::-webkit-scrollbar,
.modal .set-cat-list::-webkit-scrollbar,
.modal .axi-rulebook-scroll::-webkit-scrollbar{width:10px}
.modal .tmpl-lib-body::-webkit-scrollbar-thumb,
.modal .set-cat-list::-webkit-scrollbar-thumb,
.modal .axi-rulebook-scroll::-webkit-scrollbar-thumb{
  background:rgba(255,255,255,.16);border-radius:6px;border:3px solid transparent;background-clip:content-box}

/* ===== AI & API key (screen 08) =====
   The data-handling well was the last off-palette colour in the modal set: it ran on a
   saturated rgba(70,185,138,…) that is NOT --color-success (#7FA287), which is why #160's
   status repoint missed it — that pass worked through the shared severity components and
   this is a bespoke rule. The mockup's `.privacy` is a NEUTRAL well with success used once,
   as a 2px left edge: the fill carries no colour at all. */
/* g429 — 16px, which is `inset-well`: this is a surface2 well inside a modal, and 18 was on no
   ramp at all (§5's list is 4/8/12/16/20/24/32/40/48/64/80 and says a distance that is not on it
   means the design is wrong, not the list). Also the one place the assembly's own height can be
   given back cheaply, which the overlay needs. */
.ai-data-note{
  background:var(--color-surface2);
  border-left:2px solid var(--sev-ok-line);border-radius:var(--radius-card);
  padding:16px;font-size:14px;line-height:1.6;color:var(--color-muted)}
/* <b> in modal prose is emphasis, not a heading — 500, not the browser's 700.
   Scoped, not `.modal b`: the audit found 13 of these in screen 08, 0 in screens 09/10, and
   screen 15's single one is already 500 via `.rename-hint b`. */
.ai-data-note b{color:var(--color-ink);font-weight:var(--fw-control)}

/* A note appended to a field caption. `.ai-key-hint` has a class, so the shell's
   caption-note rule (which keys off `.muted`/`small`) does not reach it — and inside the
   tracked-caps <label> it was inheriting 11px at .12em uppercase, reading as more eyebrow. */
.ai-key-hint{
  font-size:12px;font-weight:var(--weight-body);letter-spacing:normal;text-transform:none;
  color:var(--color-faint)}

/* Notes under the key field and the model row. All three were 11–11.5px, below the
   rulebook's 11px eyebrow rung once you account for them being sentence case body copy;
   the mockup's `.subnote` is 12.5/--faint and its trailing `.para` is 13/1.6/--muted. */
.ai-key-detect{font-size:12px;color:var(--color-faint);margin-top:12px}  /* g429: 10 was off-ramp */
.ai-keys-saved{font-size:12px;color:var(--color-faint)}
.ai-embed-note{font-size:14px;line-height:1.6;color:var(--color-muted)}
/* hairlines are .08; this one was still on the .14 tier */
.ai-log-row{border-top-color:var(--color-line)}

/* Capability checklist. Was a stacked run of 11.5px lines ending in a text glyph; the locked
   screen is a two-column list at body size with drawn ticks. Markup now emits <ul class="caps">
   with an SVG tick per row (see capabilitySummaryHTML). */
.ai-cap-summary{display:block;margin-top:0;font-size:14px;line-height:1.55;color:var(--color-muted)}
.ai-cap-summary>.caps-title{
  /* g429 — 24 is `gap-group`; 22 was off the ramp. This is the gap ABOVE a heading, which §5
     invariant 6 wants larger than the space below it, so the group rung is the right one. */
  display:block;font-size:14px;font-weight:var(--fw-control);color:var(--color-ink);margin-top:24px}
.ai-cap-summary .caps{
  list-style:none;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:8px 24px;margin:12px 0 0;padding:0}
.ai-cap-summary .caps li{display:flex;align-items:center;gap:8px;font-size:14px;color:var(--color-muted)}
.ai-cap-summary .caps .ck{width:15px;height:15px;flex:0 0 auto}
.ai-cap-summary .caps li.yes .ck{color:var(--color-success)}
.ai-cap-summary .caps li.no{color:var(--color-faint)}
.ai-cap-summary .caps li.no .ck{color:var(--color-faint)}
.ai-cap-summary .caps li em{font-style:normal;color:var(--color-faint)}
@media (max-width:640px){.ai-cap-summary .caps{grid-template-columns:1fr}}

/* The provider/model dropdown was the last blue-grey in the modal set: #15161a closed and
   #181a20 open, on off-tier .10/.17/.30 borders. Repointed onto the neutral control tokens.
   Metrics are modal-scoped (below) because .cdd is a general component. */
.cdd-trigger{background:var(--color-control);border-color:var(--color-line);color:var(--color-ink)}
.cdd-trigger:hover{border-color:var(--color-linestrong)}
.cdd.open .cdd-trigger,.cdd-trigger:focus-visible{background:var(--color-control);border-color:var(--color-ink)}
.cdd-chev{stroke:var(--color-faint)}
@media (min-width:721px){
  /* same 44px control tier the shell gives every other modal field */
  .modal .cdd-trigger{min-height:44px;padding:0 14px;font-size:14px}
  .modal .cdd-chev{width:16px;height:16px}
}

/* ===== Template Library (screen 09) =====
   Every card carried a white primary "Insert", so a full grid showed 12+ white buttons
   competing with the modal's own primary. The locked screen makes them secondary —
   one white primary per view. */
.tmpl-card-actions .btn.primary{
  background:transparent;color:var(--color-ink);border-color:var(--color-linestrong)}
.tmpl-card-actions .btn.primary:hover{background:var(--color-hover);border-color:var(--color-ink)}

/* EVERYTHING BELOW IS SCOPED TO .tmpl-lib-modal, and that is load-bearing twice over.
   1) cloud.css is a SECOND stylesheet that loads after app.css and re-declares .tmpl-group,
      .tmpl-card, .tmpl-card-summary, .tmpl-card-meta and .tmpl-card-actions at (0,1,0).
      A bare class here loses on order no matter how late it sits; (0,2,0) wins.
   2) those same classes are shared with cloud.css's template-CHOOSER gallery
      (.tmpl-modal / .tmpl-gallery / .tmpl-card-title / .tmpl-card-cat). That markup is
      currently dead — no JS emits any of it — but scoping keeps this pass off it either way. */

/* The kind/scope tabs ARE the shared segmented control (added to that rule above), but the
   FULL-WIDTH variant: the locked screen stretches them edge to edge in equal columns, where
   the shared component hugs its content. Same relationship as screen 07's mode picker.
   cloud.css re-declares .tmpl-tabs' fill, radius and padding at (0,1,0) after app.css, so
   the shared rule's chrome has to be restated here at (0,2,0) to land. */
.tmpl-lib-modal .tmpl-tabs{
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  width:100%;max-width:none;margin:20px 0 0;
  background:var(--color-control);
  border-radius:var(--radius-control);padding:3px;gap:2px}
.tmpl-lib-modal .tmpl-tabs>.tmpl-tab{
  text-align:center;justify-content:center;border-radius:var(--radius-chip)!important}

/* Group heading: the tracked-caps eyebrow, not a bold sentence-case label.
   The hairline rule under it was already right; only the type was off (12.5/600/.04em). */
.tmpl-lib-modal .tmpl-group-head{
  font-size:var(--text-eyebrow);font-weight:var(--weight-eyebrow);
  letter-spacing:var(--tracking-eyebrow);color:var(--color-faint);
  border-bottom:1px solid var(--color-line);padding:0 0 12px;margin:0}

/* Three across, not four. The grid lives on .tmpl-group (the heading is a full-width cell),
   and `auto-fill,minmax(min(220px,100%),1fr)` produced four 259px cards in the 1070px body.
   The locked screen is a fixed 3-track grid at gap 16 — which lands each card on the
   mockup's 346px exactly. Breakpoints mirror the mockup's own. */
.tmpl-lib-modal .tmpl-group{grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
@media (max-width:960px){.tmpl-lib-modal .tmpl-group{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.tmpl-lib-modal .tmpl-group{grid-template-columns:minmax(0,1fr)}}

/* Card. The mockup spaces the stack with per-child margins rather than one flex gap —
   12/8/14/10/16 down the card — so the gap goes to 0 and each child carries its own.
   Padding is scoped >=721px: the mockups are a 1440 spec, and at 320 the card is
   full-width where 18px a side is 12px of viewport the phone does not have. */
.tmpl-lib-modal .tmpl-card{border-radius:var(--radius-card);gap:0}
@media (min-width:721px){.tmpl-lib-modal .tmpl-card{padding:18px}}
.tmpl-lib-modal .tmpl-card-top{gap:10px}
.tmpl-lib-modal .tmpl-card-name{line-height:1.55;margin-top:12px}
.tmpl-lib-modal .tmpl-card-summary{
  line-height:1.5;color:var(--color-muted);margin-top:8px;flex:1 1 auto}
.tmpl-lib-modal .tmpl-card-meta{color:var(--color-faint);margin-top:14px}
/* The card's type ladder is the 1440 spec. Held back below 721 for the same reason as the
   padding: on a full-width phone card, 13px summary text pushes more descriptions into the
   3-line clamp than 12.5px does (measured 14 clamped at 320 against 8 before). */
@media (min-width:721px){
  .tmpl-lib-modal .tmpl-card-name{font-size:16px}
  .tmpl-lib-modal .tmpl-card-summary{font-size:var(--text-body-axd)}
  .tmpl-lib-modal .tmpl-card-meta{font-size:12px}
}
.tmpl-lib-modal .tmpl-card-roles{gap:6px;margin-top:10px}
.tmpl-lib-modal .tmpl-card-actions{margin-top:16px;padding-top:0;align-self:flex-start}

/* The kind chip is the house .badge: neutral fill, hairline, chip radius, 11/500 --muted.
   BOTH variants go neutral — the locked screen draws it that way, the word already says
   which kind it is, and the section variant was running on an off-palette #2f6b4f/#a9e0c4
   green (the same class of stray as screen 08's data-handling well). */
.tmpl-lib-modal .tmpl-kind,
.tmpl-lib-modal .tmpl-kind-workflow,
.tmpl-lib-modal .tmpl-kind-section{
  font-size:var(--text-eyebrow);font-weight:var(--fw-control);letter-spacing:.04em;
  line-height:1.5;padding:3px 8px;border-radius:var(--radius-chip);
  background:var(--color-control);color:var(--color-muted);border:1px solid var(--color-line)}
.tmpl-lib-modal .tmpl-cat{font-size:var(--text-eyebrow);color:var(--color-faint)}

/* Role pips take the locked screen's .rolechip recipe: the role's own hue mixed into the
   neutral control chip, ink text, chip radius — instead of a solid saturated fill with
   white text. D1 keeps role colour functional; this keeps it inside the palette.
   --rc is set per pip by templateCardHTML. */
.tmpl-lib-modal .tmpl-role-pip{
  font-size:11px;font-weight:var(--weight-body);line-height:1.4;letter-spacing:normal;
  padding:3px 8px;border-radius:var(--radius-chip);color:var(--color-ink);
  background:color-mix(in srgb,var(--rc,transparent) 42%,var(--color-control));
  border:1px solid color-mix(in srgb,var(--rc,transparent) 55%,var(--color-line))}

/* ===== Version history (screen 10) =====
   The smallest screen in the set — the shell pass had already fixed its title tracking, its
   description, its 44px label field and its footer. What is left is spacing and two sizes.

   The mockup carries the description's own bottom margin at 0 and puts 22px on the save row;
   the app had 14 + 4 = 18. Matched to the mockup's structure rather than fudging one number. */
.ver-modal .rename-hint{margin-bottom:0}
.ver-modal .ver-save-row{margin-top:22px}
/* A primary that sits beside a 44px field matches its height and takes the wider 0/20 padding
   (the mockup sets exactly this inline). The footer's Close stays on the standard 0/16. */
.ver-modal .ver-save-row .btn.primary{padding-left:20px;padding-right:20px}
/* The list — empty message or saved rows — is body copy at 13.5, not 14. */
.ver-modal .ver-list{margin-top:20px}
.ver-modal .ver-list>.muted{font-size:14px;line-height:1.55}
/* The POPULATED list has no mockup — screen 10 only draws the empty state. Rows are left as
   built except for the radius: they sat on `--radius-md`, the legacy alias still resolving to
   14px, which is not on the AXD-I scale (8/10/16/20) at all. A compact 8/12 filled row is the
   control tier, so 10. Scoped: --radius-md is referenced 72 times across app.css + cloud.css,
   and repointing the alias is an app-wide pass of its own, not a screen PR's business. */
.ver-modal .ver-item{border-radius:var(--radius-control)}
/* One white primary per view — the same rule Pass B already applied to the Template Library's
   Insert and the Welcome modal's step CTAs. Every saved version carried a white "Restore", so
   a list of five put five of them against the modal's real primary, "Save Current Version". */
.ver-item-acts .btn.primary{
  background:transparent;color:var(--color-ink);border-color:var(--color-linestrong)}
.ver-item-acts .btn.primary:hover{background:var(--color-hover);border-color:var(--color-ink)}

/* ===== Welcome (screen 15) =====
   Same one-white-primary-per-view rule as the Template Library: the seven per-step
   CTAs were all white, competing with "Get Started — Take the Guided Tour", which the
   locked screen keeps as the single primary. The step CTAs become secondary. */
.tut-step-act .btn.primary{
  background:transparent;color:var(--color-ink);border-color:var(--color-linestrong)}
.tut-step-act .btn.primary:hover{background:var(--color-hover);border-color:var(--color-ink)}

/* Description runs a touch looser here than the shell's 1.55 — it is the longest lede in the
   app and the mockup gives it 1.6 on a 92ch measure. */
.tut-modal .rename-hint{line-height:1.6;margin:10px 0 0;max-width:92ch}

/* The tour row is an OUTLINED call to action, not a filled one: the mockup gives it the ink
   border and leaves the fill transparent, so the white primary inside it is the only weight
   in the row. On --control it read as a second card. */
.tut-tour-row{
  background:transparent;border-color:var(--color-ink);border-radius:var(--radius-card);
  padding:16px;gap:20px;margin:22px 0 0}
.tut-tour-row .muted{font-size:14px}

/* EVERY STEP IS A CARD, AND THE LINE CARRIES THE STATE (g427).
   The previous split gave open steps --surface and lifted only DONE ones to --surface2. That
   reads correctly on a mockup page, and not at all here, because the modal this list sits in is
   ITSELF --surface: measured, an open card was rgb(18,18,18) on a rgb(18,18,18) ground, ΔL* 0.00.
   There was no card to see. Five of the seven were invisible and the list read as a loose text
   grid with two tinted blocks in it — the owner's "it kind of annoys me".
   So the two jobs are separated onto the two channels the book gives them:
     · CARD-NESS is tone. All seven sit on --surface2 (ΔL* 2.28 off the modal), so every one of
       them reads as a card whether or not it is done.
     · DONE-NESS is line. v1.7.1 is explicit that line means state, and this is that case exactly:
       a neutral hairline at --color-line, which composites to ΔL* 9.32 over the new fill — above
       the tone step, so it reads, and well short of shouting.
   NEUTRAL, not green, deliberately. A 50%-green outline was tried and pulled for being the
   loudest thing on the screen; the green stays where it belongs, in the tick. Owner's call was
   "a green tick, and a hairline outline to show they're complete" — this is both, kept quiet.
   The transparent border on the resting card is load-bearing: a 1px edge that appears only on
   done would grow those cards by 2px and step them out of the grid rows. Same trap as adding a
   hover fill — a border is also a spacing change. */
.tut-steps{gap:12px;margin:16px 0 0}
.tut-step{
  background:var(--color-surface2);border-radius:var(--radius-card);padding:18px 20px;gap:16px;
  border:1px solid transparent}
.tut-step.done{border-color:var(--color-line)}

/* Step number: a 26px ring, not a filled 28px disc at 700. */
.tut-step-num{
  width:26px;height:26px;margin-top:2px;border-radius:var(--radius-full);
  background:transparent;border:1px solid var(--color-linestrong);
  font-size:12px;font-weight:var(--fw-control);color:var(--color-muted)}
/* Done marker on the status recipe — 14% fill / 35% border / full-strength text — carrying a
   drawn tick (see renderTutorialModal). It was a solid --color-success disc with dark text. */
.tut-step.done .tut-step-num{
  background:var(--sev-ok-fill)!important;border-color:var(--sev-ok-line)!important;
  color:var(--color-success)}
.tut-step-num .tut-tick{width:14px;height:14px}

.tut-step-title{font-size:16px;font-weight:var(--fw-control)}
.tut-step-text{font-size:14px;line-height:1.55;margin:6px 0 0}
.tut-step-act{margin-top:14px}

/* Footer: the mockup splits it — dismiss on the left, Close on the right — and draws the
   checkbox as an 18px box on the hairline rather than the OS control. */
.tut-modal .modal-actions{justify-content:space-between;gap:12px}
/* .tut-modal .tut-dismiss, not .tut-dismiss: this is a <label>, and the light-mode block's
   `.modal label{color:…!important}` is (0,1,1) — !important alone does not beat it from a
   bare (0,1,0) class, it has to out-specify it too. Same trap as the shell's field captions. */
.tut-dismiss{font-size:14px;font-weight:var(--weight-body);gap:10px;margin-right:0}
.tut-modal .tut-dismiss{color:var(--color-muted)!important}
.tut-dismiss input[type=checkbox]{
  appearance:none;-webkit-appearance:none;width:18px;height:18px;flex:0 0 auto;margin:0;
  /* 5px is OFF the radius scale (chip 8 / control 10 / card 16 / modal 20) and it stays that
     way deliberately. It was moved to the chip rung on 2026-08-14 and reverted the same day:
     8px on an 18px box is 44% of the side, so the control rendered as a CIRCLE and read as a
     radio — "pick one of these" — instead of a checkbox. A wrong affordance costs a user more
     than an off-token radius costs the system.
     This is a REFERENCE GAP, logged for the rulebook rather than solved here: the scale's
     lowest rung is tuned for chips and badges, which are 22px+ objects where 8px reads as a
     rounded square. Small square controls — checkboxes, the 18px marks — have no legal rung.
     The book needs one (a ~4-6px mark rung), or it needs to say small controls are exempt. */
  border:1px solid var(--color-linestrong);border-radius:5px;background:transparent;cursor:pointer}
.tut-dismiss input[type=checkbox]:checked{
  background:var(--color-primary);border-color:var(--color-primary);
  /* drawn tick, same shape as the step marker */
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='M20 6 9 17l-5-5'/></svg>");
  background-size:12px 12px;background-position:center;background-repeat:no-repeat}

/* ===== SOP Analysis (screen 11) =====
   Screen 11 uses the same page head as screens 03/04 — a small SENTENCE-CASE eyebrow
   then a light 28px title. Note this is the OTHER eyebrow variant: screen 06's
   governance cards use tracked CAPS, and .analysis-head is shared between the two,
   which is why batch 6 scoped its caps rule to .comments-overview/.review-card-head
   rather than to .analysis-head. Keep both scopes narrow. */
.analysis-report .analysis-head .kicker{
  font-size:11px!important;font-weight:500;letter-spacing:.04em;line-height:16px;
  text-transform:none;color:var(--color-faint)}
.analysis-report .analysis-head h2{
  font-size:28px!important;font-weight:300!important;line-height:34px!important;margin-top:10px}
@media (max-width:720px){
  .analysis-report .analysis-head h2{font-size:24px!important;line-height:30px!important;margin-top:8px}
}

/* The outer card, for the THIRD time in this pass (screens 03 and 04 had the same one).
   The mockup has no page-level card at all: the head, the stat grid and each analysis card
   sit directly on the canvas. Here it did the same damage as on screen 04 — the report shell
   was --surface and the panels inside it were --control, so the page read as one big card
   with darker blocks in it instead of a row of cards. Shell to nothing; panels to --surface.
   Needs both barrels: the padding is set by `#mainView .card.page-card:not(...)` — an ID
   selector at (1,4,0) — and the radius is pinned !important by three separate radius groups. */
#mainView .analysis-report.card.page-card{
  background:transparent!important;border:0!important;border-radius:0!important;padding:0!important}
/* Fill and radius at every width; the PADDING increase is >=721 only. The mockups are a
   1440 spec and 24px a side on a 320 viewport is 48px the phone does not have — the same
   mistake screen 05 made and caught at 320. */
.analysis-panel,.analysis-insight{
  background:var(--color-surface);
  border-radius:var(--radius-card)}
@media (min-width:721px){.analysis-panel,.analysis-insight{padding:24px}}
.analysis-layout{gap:24px}

/* Page description. NOT the global .manager-note value: screens 03/04 spec a 13px lede and
   screen 11 specs 13.5/1.55 on a 78ch measure. They genuinely differ, so this is scoped —
   with !important, because screen 04's globalisation pins .manager-note's size that way. */
.analysis-report .analysis-head .manager-note{
  font-size:14px!important;line-height:1.55;max-width:78ch!important;margin:8px 0 0}

/* Stat tiles: cards in their own right, not the compact --control chips they were.
   Padding scoped >=721 — the tile goes full width on a phone (the standing rule). */
.analysis-metrics{margin:28px 0 0}
@media (min-width:641px){.analysis-metrics{gap:16px}}
.analysis-metrics>div{
  background:var(--color-surface);border-radius:var(--radius-card);padding:12px 14px}
@media (min-width:721px){.analysis-metrics>div{padding:18px 20px}}
.analysis-metrics>div>span{font-size:12px;color:var(--color-faint)}
.analysis-metrics>div>b{
  font-size:28px;font-weight:var(--fw-display);letter-spacing:var(--tracking-display);
  line-height:1.55;margin-top:8px;display:block}
@media (max-width:720px){.analysis-metrics>div>b{font-size:24px}}

/* Card head is a baseline row, not a stacked title + subtitle. */
.analysis-panel-head,.analysis-insight .analysis-panel-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:16px;margin-bottom:24px}
.analysis-panel-head p{font-size:12px;color:var(--color-faint);margin:0}

/* Donut: 240 box (see analysisDonutSVG for the ring band). Scoped >=901px because these
   rules sit AFTER the responsive block and would otherwise clobber its <=900 collapse to a
   single column — which is exactly what produced an 85px overflow inside the report at 320. */
@media (min-width:901px){
  .analysis-donut-grid{grid-template-columns:minmax(170px,240px) minmax(0,1fr);gap:40px}
  .analysis-donut{max-width:240px}
}
.analysis-donut-num{font-size:34px;font-weight:var(--weight-body);fill:var(--color-ink)}
.analysis-donut-label{
  font-size:11px;font-weight:var(--weight-eyebrow);letter-spacing:var(--tracking-eyebrow);
  fill:var(--color-faint)}

/* The ranked list is a table of rows separated by hairlines, not a run of bold numbers:
   every one of rank / code / value was at 700. */
.analysis-ranked-list{gap:0}
.analysis-rank-row{
  grid-template-columns:24px 12px minmax(0,1fr) 56px 88px;gap:12px;padding:12px 0;
  border-top:1px solid var(--color-line)}
.analysis-rank-row:first-child{border-top:0}
.analysis-rank-num{font-weight:var(--weight-body);text-align:right}
.analysis-role-dot{width:9px;height:9px}
.analysis-rank-role>b{font-weight:var(--fw-control)}
.analysis-rank-role>span{color:var(--color-faint)}
.analysis-rank-val{font-weight:600;text-align:right}
.analysis-rank-sub{font-size:12px;color:var(--color-faint);text-align:right}
@media (max-width:520px){
  .analysis-rank-row{grid-template-columns:20px 10px minmax(0,1fr) 44px}
  .analysis-rank-sub{display:none}
}

/* Two off-scale items in the panels the mockup does not draw (owner-confirmed scope):
   the insight heading was the only 700-weight section title in the app, and the flags sat
   on a raw rgba(255,255,255,.024) rather than a surface token. */
.analysis-insight h4{font-size:16px;font-weight:var(--fw-control)}
.analysis-flag{background:var(--color-surface2);border-color:var(--color-line)}

/* ===== Workflow table · Subpaths control (screen 14) =====
   Show All / Main Path Only were two loose buttons with the ACTIVE one rendered as a
   white primary — a third white button competing with Save in the table view. The
   locked screen draws them as the shared segmented control, active raised onto
   surface2. Same component as .seg / .viewseg; the active class here is .primary. */
.subpath-controls{
  display:inline-flex;align-items:center;flex-wrap:wrap;width:fit-content;max-width:100%;
  background:var(--color-control);
  border-radius:var(--radius-control);padding:3px;gap:2px}
.subpath-controls>.spc-label{
  padding:0 8px 0 6px;font-size:var(--text-eyebrow);font-weight:var(--weight-eyebrow);
  letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--color-faint)}
.subpath-controls>.btn.small{
  height:auto!important;min-height:var(--ctl-h-xs)!important;padding:0 14px!important;
  border:1px solid transparent!important;background:transparent!important;
  color:var(--color-muted)!important;font-size:12px;font-weight:500;
  border-radius:var(--radius-chip)!important;white-space:nowrap;
  transition:background .15s ease,color .15s ease,border-color .15s ease}
.subpath-controls>.btn.small:hover{color:var(--color-ink)!important}
.subpath-controls>.btn.small.primary{
  background:var(--color-surface2)!important;color:var(--color-ink)!important;
  border-color:var(--color-linestrong)!important}
@media (max-width:768px){
  .subpath-controls>.btn.small{min-height:44px!important;width:auto!important;flex:0 0 auto}
}

/* ============================================================================
   TYPE SCALE — align the app with AXD-I rulebook §3.
   The correct tokens already exist at the top of this file (--text-display,
   --text-eyebrow, --text-th, --text-button …). What actually drove the UI was a
   parallel legacy --fs-* family, redefined five times (L86, L349, L498, L768,
   L1805) with the last one winning at 15px body / 23px title / 13px meta /
   11.5px label. Same shape of problem as the radius aliases.
   Repointed here rather than at each of the five layers, so the winning values
   are in one place and the earlier layers stay as history.
   NOT changed: .section-title keeps its quiet muted treatment — it holds
   USER-TYPED section names and §3 says "Never uppercase user data".
   ========================================================================== */
:root{
  --fs-title:var(--text-display);   /* 28 */
  --fs-body:var(--text-body-axd);   /* 14 */
  --fs-meta:12px;
  --fs-label-sm:11px;     /* 10.5 */
  --fw-display:var(--weight-display); /* 300, was 350 */
}
/* the page title is display voice: 28/300 (24 on phones) */
.workflow-header h2{font-size:var(--text-display)!important;font-weight:var(--weight-display)!important;
  line-height:34px!important;letter-spacing:var(--tracking-display)}
@media (max-width:720px){ .workflow-header h2{font-size:24px!important;line-height:30px!important} }
/* the AI-review CARD heading is a card title, not a page title (screen 05: 20/400) */
.ai-check .legend-top h2{font-size:16px!important;font-weight:400!important;line-height:26px!important}
/* meta voice — this group previously pinned everything to 13px */
/* v1.5 §3: these are sentences a user READS, so they take Body (14), not Meta (12.5).
   .kicker left out on purpose — it is an eyebrow and is set on the eyebrow rung below. */
.manager-note,.item-meta,.filter-bar,.v-loc{font-size:var(--text-body-axd)!important}
/* table headers: 10.5 / .12em (the earlier pass set .09em) */
th{font-size:var(--text-th)!important;letter-spacing:var(--tracking-th)!important;font-weight:var(--weight-eyebrow)}
/* buttons: 13 / .01em (an earlier pass pinned 14px) */
.btn,label.btn{font-size:var(--text-button)!important;letter-spacing:var(--tracking-button)}
/* nav items: 12.5 / 500 / .02em */
.workflow-link{font-size:14px!important;font-weight:500;letter-spacing:.02em}
.workflow-link .workflow-label{font-weight:500}

/* MONO — dropped 2026-08-06 (owner call): tried on role chips, then on the step type,
   and it reads better without it. Roles, step types and workflow numbers all stay in
   Figtree, Title Case. The rulebook's "copy it -> mono" rule still holds for genuinely
   verbatim data (hashes, tokens, endpoints); it just does not earn its place on these
   three, which sit inline with body copy. No --font-mono override and no mono webfont. */
/* the table re-raised body size to 15px with !important (L1815); the step table is
   body copy and belongs on the 14px rung */
table{font-size:var(--text-body-axd)!important}

/* TOC width — 318px (chosen 2026-08-06). The mockup draws 240px but truncates
   workflow titles; 318 keeps most titles whole now that nav type is on-scale. */
:root{--toc-open:318px}

/* ===== Screen 01 · Workflow table ===== */
/* The icons column was 86px. Three 34px icons plus gaps need ~118, so they wrapped to
   three rows and tripled the row height. The fixed-layout widths use a `>` combinator
   inside a min-width media query, so match that selector exactly or it never applies. */
@media (min-width:1025px){
  .table-wrap>table th:nth-child(5),.table-wrap>table td:nth-child(5),
  .nested-table>table th:nth-child(5),.nested-table>table td:nth-child(5){width:134px}
}
/* The icons are bare inline-flex .svg-icon spans with whitespace between them, so they
   wrap like words. Do NOT make the td a flex container — that takes the cell out of
   table layout and made rows TALLER (147px -> 208px). Suppress the wrapping instead. */
.table-wrap>table>tbody>tr>td:nth-child(5),
.nested-table>table>tbody>tr>td:nth-child(5){white-space:nowrap;font-size:0}
.table-wrap>table>tbody>tr>td:nth-child(5)>.svg-icon,
.nested-table>table>tbody>tr>td:nth-child(5)>.svg-icon{margin:0 4px 0 0;vertical-align:middle}
/* Following-step cell: the step pill and its action icons share ONE row (mockup).
   The real structure is td.pen-cell > .pen-cell-inner > (.next-cell + .step-tools),
   and .pen-cell-inner was a COLUMN. Flex the inner div, never the td — making a td a
   flex container drops it out of table layout and inflates row height. 61+70+8 = 139,
   so the column also has to grow past its old 132px or it just wraps again. */
.pen-cell>.pen-cell-inner{flex-direction:row;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:nowrap}
@media (min-width:1025px){
  .table-wrap>table th:nth-child(6),.table-wrap>table td:nth-child(6),
  .nested-table>table th:nth-child(6),.nested-table>table td:nth-child(6){width:156px}
}
/* 721-1024 keeps the original 86/132 columns: the wider ones are a desktop change and
   between those widths they starved Description enough to overflow the subpath bar. */
/* row action icons are borderless in the mockup */
.pen-cell .edit-pen,.pen-cell .cmt-btn,td .edit-pen,td .cmt-btn{
  border-color:transparent!important;background:transparent!important}
.pen-cell .edit-pen:hover,.pen-cell .cmt-btn:hover,td .edit-pen:hover,td .cmt-btn:hover{
  background:var(--color-hover)!important;color:var(--color-ink)}
/* search: 36px, control radius, magnifier inset, and a focus ring at FULL ink
   (it was drawn at 50% opacity with a .35 border, which is why it read as a
   partial highlight rather than a focus state) */
.search-wrap{position:relative;display:block}
.search-ic{position:absolute;left:12px;top:50%;transform:translateY(-50%);width:15px;height:15px;
  color:var(--color-faint);pointer-events:none;z-index:1}
.search{height:36px!important;min-height:36px!important;border-radius:var(--radius-control)!important;
  background:var(--color-control);border:1px solid transparent!important;
  padding:0 12px 0 34px!important;font-size:14px!important}
.search:hover{border-color:var(--color-line)!important}
/* The ring is drawn INSIDE the field. .sidebar-body is overflow-x:hidden and its content
   edge is flush with the field's left edge, so an outline — which always paints outside the
   border box — lost its left 4px (ring at x=28, clip box at x=32). An inset ring cannot be
   clipped, and keeps the same 2px visual weight. */
.search:focus{border-color:var(--color-ink)!important;outline:none!important;
  box-shadow:inset 0 0 0 2px var(--color-ink)}
@media (max-width:768px){ .search{height:44px!important;min-height:44px!important;font-size:16px!important} }
/* subpath bar: the mockup has no left trim, control radius, surface2 ground */
.subpath-bar{border-left-width:1px!important;border-left-color:var(--color-line)!important;
  border-radius:var(--radius-control)!important;background:var(--color-surface2)!important;
  border-color:var(--color-line)!important}
/* SUBPATHS: the label sits OUTSIDE the segmented group (.subpaths-row is a flex row) */
.subpath-controls{background:transparent;border-radius:0;padding:0;gap:16px;width:auto}
.subpath-controls>.spc-label{padding:0;color:var(--color-faint)}
/* TRACK is surface2, THUMB is control — these were the wrong way round (2026-08-14).
   The rulebook puts "options on a surface2 track" with a thumb that travels. The build had
   the track on --color-control (#1C1C1C) and the thumb on --color-surface2 (#171717), which
   is inverted: the thumb was DARKER than the rail it sits in, so the chosen option read as a
   hole punched into the track rather than a raised marker on top of it. */
.subpath-controls>.seg{display:inline-flex;align-items:center;background:var(--color-surface2);border-radius:var(--radius-control);padding:3px;gap:2px}
.subpath-controls>.seg>.btn.small{
  height:auto!important;min-height:var(--ctl-h-xs)!important;padding:0 14px!important;
  border:1px solid transparent!important;background:transparent!important;color:var(--color-muted)!important;
  font-weight:500;border-radius:var(--radius-chip)!important;white-space:nowrap}
.subpath-controls>.seg>.btn.small:hover{color:var(--color-ink)!important}
/* The engaged option is a SOLID, BORDERLESS thumb. It carried a --color-linestrong border,
   which is the Secondary BUTTON recipe — so a selection was dressed as an action, the exact
   confusion the seven-controls table exists to prevent: "an action does something and does
   not persist; a selection chooses something and does persist." Fill alone marks it now. */
/* thumb tone aligned with .modeseg (the view toggle), which already had this right:
   overlay-hover on a surface2 track. g318 used --color-control here, which is only ~5 points
   off the track and reads as barely-there. Same control family, same thumb, one language. */
.subpath-controls>.seg>.btn.small.primary{background:var(--color-overlay-hover)!important;
  color:var(--color-ink)!important;border-color:transparent!important}
/* Screen 01 puts the workflow action buttons on their OWN row BELOW the title.
   Once the title went to 28px, sharing a row with .head-actions forced it to wrap. */
.workflow-header{flex-direction:column;align-items:stretch;gap:14px}
.workflow-header>div:first-child{min-width:0;flex:0 0 auto}  /* the 1 1 300px basis
   was a WIDTH in the old row layout; in column direction it became a 300px HEIGHT and
   left a dead gap between the title and the action row */
.workflow-header .head-actions{justify-content:flex-start;flex:0 0 auto}

/* icons inside toolbar buttons */
.btn>.btn-ic{width:14px;height:14px;flex:none;margin-right:2px}
/* decision hint: arrow + plain text, no second route pill */
.dec-arrow{color:var(--color-faint);margin-right:6px}
@media (max-width:768px){
  .subpath-controls>.seg>.btn.small{min-height:44px!important;width:auto!important;flex:0 0 auto}
}

/* ===== Screen 01 · second pass — measured against the mockup's own numbers =====
   Gemini flagged the remaining gaps; each value below is the mockup's spec, not an
   estimate. Colour is deliberately untouched: step-type, role-chip and step-icon tints
   are user-configurable (D1) and were excluded from this pass. */

/* row rhythm — mockup .trow is padding:16px 0 with a 16px column gap */
.table-wrap>table>tbody>tr>td,.nested-table>table>tbody>tr>td{padding-top:16px;padding-bottom:16px}
.table-wrap>table>thead>tr>th,.nested-table>table>thead>tr>th{height:40px;padding-top:0;padding-bottom:0}

/* column widths — mockup --cols: 56px 116px 128px minmax(0,1fr) 128px 176px */
@media (min-width:1025px){
  .table-wrap>table th:nth-child(1),.table-wrap>table td:nth-child(1),
  .nested-table>table th:nth-child(1),.nested-table>table td:nth-child(1){width:56px}
  .table-wrap>table th:nth-child(2),.table-wrap>table td:nth-child(2),
  .nested-table>table th:nth-child(2),.nested-table>table td:nth-child(2){width:116px}
  .table-wrap>table th:nth-child(3),.table-wrap>table td:nth-child(3),
  .nested-table>table th:nth-child(3),.nested-table>table td:nth-child(3){width:128px}
  .table-wrap>table th:nth-child(5),.table-wrap>table td:nth-child(5),
  .nested-table>table th:nth-child(5),.nested-table>table td:nth-child(5){width:128px}
  .table-wrap>table th:nth-child(6),.table-wrap>table td:nth-child(6),
  .nested-table>table th:nth-child(6),.nested-table>table td:nth-child(6){width:176px}
}

/* step icons — mockup .tile is 28x28, radius --r-chip (8), 14px glyph, 8px apart */
.table-wrap>table>tbody>tr>td:nth-child(5)>.svg-icon,
.nested-table>table>tbody>tr>td:nth-child(5)>.svg-icon{
  width:28px!important;height:28px!important;min-width:28px!important;
  border-radius:var(--radius-chip)!important;margin:0 8px 0 0}
/* GLYPH 14 -> 16 (2026-08-14). The 14 came from the mockup, same as the 11px chips did, and the
   rulebook is explicit that icons "snap to 16 / 20 / 24 ... never free-size an icon" — 16 being
   the inline-and-rows rung. The 28px TILE is unchanged: the scale governs the icon, not the chip
   it sits in, and 28 still leaves an even 6px around a 16px glyph. */
.table-wrap>table>tbody>tr>td:nth-child(5)>.svg-icon svg,
.nested-table>table>tbody>tr>td:nth-child(5)>.svg-icon svg{width:16px!important;height:16px!important}

/* following-step chip — mockup .nextchip: 12.5/500, padding 4px 10px, chip radius */
.step-link{font-size:12px!important;font-weight:500!important;padding:4px 10px!important;
  border-radius:var(--radius-chip)!important;font-variant-numeric:tabular-nums}

/* step number — mockup .c-step: 13/500, tabular */
.table-wrap>table>tbody>tr>td:first-child,.nested-table>table>tbody>tr>td:first-child{
  font-size:14px;font-weight:500;font-variant-numeric:tabular-nums}

/* "+ Add Subpath" sits 12px under the description (mockup .addsub) */
.table-wrap td .btn.small[data-action="add-subpath"],
.nested-table td .btn.small[data-action="add-subpath"]{margin-top:12px}

/* description keeps a readable measure — mockup .c-desc p max-width:66ch */
.table-wrap>table>tbody>tr>td:nth-child(4),
.nested-table>table>tbody>tr>td:nth-child(4){max-width:66ch}

/* ============================================================================
   Screen 01 · THIRD pass — 2026-08-06. Every value below is read off the
   mockup's own stylesheet (docs/design/axdi-v12-mockups/html/kimi-screen01.html),
   not estimated from the PNG, and was confirmed against the build's COMPUTED
   values before being written.

   Three deliberate divergences are preserved and must not be "fixed" later:
     · functional colour on role chips / step types / step icons  (decision D1)
     · sidebar 318px, not the mockup's 240 (240 truncates workflow titles)
     · no monospace anywhere — roles, step types and version strings stay Figtree
   ========================================================================== */

/* ---- hairline tiers. The legacy --line/--line2 aliases (L3736) were pinned to
   .14/.22, one whole step stronger than the rulebook's .08/.14, which is why
   every border in the app read hotter than the mockup and why three separate
   later layers had to patch individual borders back down (.07 on .table-wrap,
   .06 on .role-panel). Repointed at the alias so the tiers are correct once,
   in both modes, instead of per element. ---- */
:root{--line:var(--color-line);--line2:var(--color-linestrong)}
:root[data-mode="light"]{--line:var(--color-line);--line2:var(--color-linestrong)}
.role-panel,.legend-panel{border-color:var(--color-line)!important}
.table-wrap{border-color:var(--color-line)!important}

/* ---- control heights. Mockup: .btn 36, .btn-sm 28, .topbar .btn 34. The build
   ran 40/32/40. Desktop + tablet only — phones keep the 44px touch target.
   g391: that last clause was aspiration, not fact. The base `--ctl-h` a few thousand lines up
   is 40px, so "phones keep the 44px touch target" described a value the sheet never set — and
   §5 is unambiguous ("Tap targets never below 44px on touch"). The base is now 44 and the
   comment is true. `--ctl-h-sm` goes 32 -> 36 with it; the compact 28 ghost survives as
   --ctl-h-xs, which §5 permits for card-header actions only.
   THE POINTER RUNG ALSO MOVES, 721 -> 1025. 768 and 1024 are iPad portrait and landscape:
   §7 files them under touch, and they were being served the 36px POINTER height, further from
   the minimum than the phones were. The book does not say in so many words where "touch" stops
   — that ambiguity is raised with the rulebook separately — so this takes §7's own breakpoint
   table at its word and treats everything through 1024 as touch. ---- */
@media (min-width:1025px){ :root{--ctl-h:36px;--ctl-h-sm:28px} }

/* ---- top bar: 56px tall, 16px gutters, buttons 34, meta pills 22 and FILLED
   (they were transparent with a --line2 border, which read as two more outlined
   buttons instead of quiet metadata). ---- */
@media (min-width:1101px){
  .toolbar{min-height:56px;padding:0 16px}
  .toolbar .btn:not(.menu-item){min-height:34px!important}
}
.toolbar{border-bottom:1px solid var(--color-line)!important}
/* the SOP name is a bar label, not a page title: 13/500 (it was 18px, which ate
   the middle of the bar and pushed truncation past the other two groups) */
/* The SOP title sits on the Section rung (§3: 16/500/0), not Body. It is the document's name —
   the one piece of identity in the chrome — and at the Body rung it read as just another label
   rather than the thing everything else belongs to. */
.toolbar-sop-title{font-size:var(--text-section)!important;font-weight:500;letter-spacing:0;line-height:22px}
.content-ver,.ai-posture,.ai-posture.is-off{
  height:22px;min-height:22px;padding:0 10px;
  background:var(--color-control)!important;border:1px solid transparent!important;
  border-radius:var(--radius-chip)!important;color:var(--color-muted)}
/* the pill got an explicit 22px height but stayed display:block, so its 16px line
   box sat at the top of it — "v47" rode 2px under the top edge with 7px of air
   below. Centre it in the box like the posture pill next to it already is.
   Tracking back to normal too: 0.02em inherited from the nav voice, and on three
   glyphs at 11px it spaced them far enough apart to read as monospace. */
.content-ver{font-size:11px!important;font-weight:500!important;letter-spacing:.12em!important;text-transform:uppercase;padding:0 8px;
  display:inline-flex;align-items:center;justify-content:center;letter-spacing:normal}
.ai-posture{font-size:12px!important;font-weight:400}
/* The group divider is gone (owner call 2026-08-14) — it only ever rendered at 1440+, and a
   1px rule between two button groups is decoration, not state: the line language reserves the
   line for state, and grouping is the job of space. The space it occupied stays, or the two
   groups collide — the divider carried 33px of separation (12 gap + 4 margin + 1px + 4 margin
   + 12 gap) while the bare container gap is only 12. Restored as margin at the same breakpoint
   the line used to appear, so the tight 1101-1439 band is untouched. */
@media (min-width:1440px){ .toolbar-toggles>.viewseg{margin-left:20px} }

/* ---- page gutters: mockup .main is 24px, the build ran 40, costing 32px of
   table width at every desktop size. Top inset moves from 14 to 24 to match. ---- */
@media (min-width:1101px){
  .main{padding-left:24px!important;padding-right:24px!important;padding-bottom:64px}
  .app-shell .main>#mainView{padding-top:24px}
}

/* ---- Workflow Roles card: --surface, not --surface2. A card is a panel, and
   surface2 is the raised tier (active rows, wells, insets). ---- */
.role-panel{background:var(--color-surface)!important;padding:20px}
@media (max-width:720px){ .role-panel{padding:16px} }

/* ---- role chips + step types: mockup 11px, .04–.05em, padding 5px 10px /
   3px 8px. The build ran both at 13/500 in a 32px box, which is why they read
   chunkier than the reference. Colour untouched (D1).
   WEIGHT, 2026-08-11 (owner): the rulebook's Badge row is 11 / 500, so .role-chip moves
   400 -> 500 and now matches .step-type. Same decision as the nav rows above: rulebook
   beats mockup on weight only. Sizes, tracking and padding still follow the mockup. ---- */
/* Type audit 2026-08-14. These two chips were both 11px, but they carry different CONTENT and
   the rulebook's 11px rung is conditional on case, not on being a chip: "12px is the floor for
   lowercase text; below it only tracked uppercase survives."
     .role-chip holds uppercase role CODES (CLIENT, CEO, LCE) -> survives at 11.
     .step-type holds Title Case WORDS (Start, Process, Decision) -> lowercase letterforms below
     the floor, so it moves to 12. */
/* ROLE CHIP, owner call 2026-08-14 — a DELIBERATE one-rung departure, recorded so it does not
   later read as drift. The rulebook's Badge is 11px/500/uppercase/0.08em; this is 12px/0.08em.
   Two reasons: 12 is a real rung on the type scale (28-16-14-12-11), and it puts the chip level
   with .step-type beside it, so Role and Step Type read as the peers they are — two parallel
   categorical labels in adjacent columns — rather than one outranking the other.
   THE TRACKING WAS A STRAIGHT BUG: .role-chip shipped at 0.05em against the Badge spec's 0.08em.
   On uppercase, tracking IS legibility — tight caps read smaller and denser than their size, and
   short codes like LCE suffered most. Part of "the chips feel small" was never about size. */
.role-chip{font-size:12px!important;letter-spacing:.08em!important;line-height:1.4!important;min-height:0!important}
.step-type{font-size:12px!important;line-height:1.4!important;min-height:0!important}
.role-chip{font-weight:500;letter-spacing:.05em;padding:5px 10px}
.step-type{font-weight:500;letter-spacing:.04em;padding:3px 8px}
@media (max-width:720px){
  .role-chip,.step-type{font-size:12px!important;padding:6px 10px}
}

/* ---- the steps table sits FLAT on the canvas. The mockup has no container at
   all: the structure is carried by the header hairline and the row hairlines.
   The build wrapped it in a bordered 16px card filled --surface, which read as a
   second panel stacked under the roles card. Scoped to the workflow view so the
   nested subpath tables keep their boxes. ---- */
.wf-view>.table-wrap{border:0!important;border-radius:0!important;background:transparent!important}
.wf-view>.table-wrap>table{background:transparent!important}
@media (min-width:1101px){ .wf-view>.table-wrap>table{border-radius:0} }
/* header hairline drops to the default tier with the aliases above; state it
   explicitly because an earlier layer may re-raise it */
.wf-view>.table-wrap>table>thead>tr>th{border-bottom-color:var(--color-line)}

/* ---- following-step chip: mockup .nextchip is a filled --control chip with a
   default hairline; the build drew a 4%-white fill with a --line2 edge ---- */
.step-link{background:var(--color-control)!important;border-color:var(--color-line)!important}

/* ---- sidebar nav rows: 12.5px on a 10px radius, weight 500.
   DECISION 2026-08-11 (owner): where the locked mockups and the AXD-I rulebook disagree
   on type WEIGHT, the rulebook wins. The mockups draw these rows at 400; the book's
   "Nav item" row is 12.5 / 500 / 0.02em, and at 400 on a dark ground the rail reads thin
   enough to be mistaken for a smaller size. Size and radius are unchanged and still match
   the mockup — this is a weight-only divergence. Do not "restore" 400 to match the
   reference screens; that is the thing this decision overrides. ---- */
.workflow-link{font-weight:500!important;border-radius:var(--radius-control)!important}
/* the brand line is centred in the mockup */
.sidebar-footer{text-align:center;letter-spacing:.12em}

/* ---- icons inside buttons: mockup .btn svg is 15px with an 8px gap ---- */
.btn>.btn-ic{width:15px;height:15px;margin-right:0}
.btn{gap:8px}
.btn.small>.btn-ic{width:13px;height:13px}

/* ---- residual row/nav rhythm. Three earlier rules out-specified the second
   pass and were still winning; each is restated here at the specificity that
   actually applies, with the mockup's number.
     · thead was 14/12 padding inside a min-width:1101 block -> 43px, not 40
     · the FIRST body row alone had padding-top:14 (rows 2+ were already 16)
     · td line-height was pinned 1.5 !important; the mockup's body is 1.55
     · .workflow-link carried min-height:36 + line-height:1.35; the mockup row
       is 7+7 padding around a 1.55 line = 33.4, and .on does NOT go semibold
   The "+ Add Subpath" wrapper carried an inline margin-top:10px (mockup 12);
   it is now .addsub-wrap. The pass-2 rule that tried to set this targeted
   [data-action="add-subpath"] — the real attribute is add-branch — so it was
   dead code and is superseded here. ---- */
@media (min-width:1101px){
  .wf-view .table-wrap>table>thead>tr>th{padding-top:0;padding-bottom:0;height:40px}
  .wf-view .table-wrap>table>tbody>tr:first-child>td{padding-top:16px}
  .workflow-link{min-height:0!important;height:auto!important;line-height:1.55!important}
}
td{line-height:1.55!important}
@media (max-width:720px){ td{line-height:1.55!important} }
.addsub-wrap{margin-top:12px}
/* the header cells still carried the wrapper-card treatment (a --surface fill and
   a 14px top-left/right radius) which drew a faint band across the head once the
   container behind it went away. The mockup's .thead is transparent.

   ...but `transparent` was reasoned about AT REST, and this header is position:sticky.
   Once it pinned, the step rows scrolled visibly THROUGH the column labels — text over
   text. Fixed by filling with --paper, which is the colour of the .card sitting directly
   behind it (.table-wrap and the table itself are both transparent, so --paper is what
   shows). Same pixel value at rest, so the band the original note was avoiding does not
   come back — that band came from --surface, a LIGHTER fill, not from opacity as such.
   The existing hairline below stays as the separator; no shadow, since line carries state
   in this language. */
.wf-view>.table-wrap>table>thead>tr>th{background:var(--paper)!important;border-radius:0!important}

/* ---- 721-1024 overlap (pre-existing on g259, caught by this pass's sweep).
   The widened Icons/Following-Step columns are scoped to >=1025px because at
   tablet widths they starve Description. Below that the columns stay 86/132,
   but three 28px tiles need 100px and the following-step row (chip + 2 tools)
   needs 140 — so both spilled out of their cells and the last icon landed on
   top of the "Step n" chip. Nothing to widen into here, so the two cells wrap
   instead. Rows get taller at tablet widths; nothing collides. ---- */
@media (min-width:721px) and (max-width:1024px){
  .table-wrap>table>tbody>tr>td:nth-child(5),
  .nested-table>table>tbody>tr>td:nth-child(5){white-space:normal;padding-left:8px;padding-right:8px}
  .table-wrap>table>tbody>tr>td:nth-child(5)>.svg-icon,
  .nested-table>table>tbody>tr>td:nth-child(5)>.svg-icon{margin:0 6px 6px 0}
  .pen-cell{padding-left:8px;padding-right:8px}
  .pen-cell>.pen-cell-inner{flex-wrap:wrap;row-gap:8px;column-gap:6px}
}

/* ============================================================================
   Screen 03 · Legend — measured against kimi-screen03.html, 2026-08-06.
   Scoped to .legend-view throughout: .pagehead-view and .item are SHARED with
   the Structure Manager (screen 04), which has not been reviewed yet.
   NOT changed, and worth knowing why:
     · only four step types render (mockup draws five). The app deliberately
       drops anything outside start/process/decision/end — see migrateData —
       and the Legend's own copy says "Step types are fixed". The mockup is the
       stale one. The "Fixed" tag added to that column header says so in the UI.
     · roles and icons show no description because THIS fixture carries none;
       the renderer supports `desc` and step types (which have hardcoded ones)
       do render them. Not a rendering gap.
   ========================================================================== */

/* the mockup has no outer container: the page head and the three columns sit
   directly on the canvas, and the columns are the only cards */
.legend-view{background:transparent!important;border:0!important;border-radius:0!important;padding:0!important}

/* page head — display type takes the display tracking (the screen-01 pass scoped
   that fix to .workflow-header h2, so this title kept the +0.01em body tracking),
   and the lede gets a real measure instead of running the full width */
/* moved to the global block at the end of this file — .legend-top and .manager-note are the
   SHARED page-head component, and the same two misses turned up again on screen 04. */

/* columns: --surface, not the raised --surface2 tier; the mockup's own insets */
.legend-view .grid-3{gap:24px}
.legend-view .legend-panel{background:var(--color-surface)!important;padding:8px 8px 12px}
.legend-view .legend-panel .list{margin-top:0!important}

/* column header + its add button */
.legend-view .legend-panel-head{padding:12px;gap:12px}
.legend-view .legend-head-tag{margin-left:auto;font-size:11px;font-weight:500;letter-spacing:.06em;
  color:var(--color-faint);white-space:nowrap}
.legend-view .legend-panel-head .btn{border-radius:var(--radius-control)!important;padding:0 10px!important;gap:6px}

/* rows: the chip sits BESIDE the name, not above it. The markup is
   chip + name + (desc) as siblings, so the chip spans both text rows of a
   two-column grid rather than needing a wrapper element.
   Step-type rows are deliberately excluded — the mockup stacks those (pill over
   description), and they carry neither data attribute. */
.legend-view .item{padding:13px 12px!important;gap:12px;align-items:flex-start}
.legend-view .item[data-role-code]>div:first-child,
.legend-view .item[data-icon-key]>div:first-child{display:flex;gap:12px;align-items:flex-start}
.legend-view .item>div:first-child>.role-chip,
.legend-view .item>div:first-child>.svg-icon{flex:none}
.legend-view .lg-body{min-width:0}

/* the NAME is primary ink, not a second description. Both were .item-meta, so the
   name was rendering at the description's 12.5/muted — the row read as a stack of
   grey labels. Name and desc are wrapped in .lg-body (added in renderLegend, same as
   the mockup's own structure) so the chip has ONE sibling to sit beside; an earlier
   attempt used a grid with the chip at grid-row:1/-1, which resolves against an EMPTY
   explicit grid and exploded the row to 508px as soon as a description existed. */
.legend-view .lg-body>.item-meta:nth-of-type(1){font-size:14px!important;color:var(--color-ink);line-height:1.55}
.legend-view .lg-body>.item-meta:nth-of-type(2){font-size:12px!important;color:var(--color-muted)}

/* row action pencil is borderless in the mockup (same call as screen 01's table) */
.legend-view .item-right .btn,.legend-view .item-right button{
  width:28px!important;min-width:28px!important;height:28px!important;min-height:28px!important;
  border-width:0!important;background:transparent!important;color:var(--color-muted)}
.legend-view .item-right .btn:hover,.legend-view .item-right button:hover{
  background:var(--color-hover)!important;color:var(--color-ink)}

/* 12th item, not in the agreed list of 11 — flagged to Abdulla. An earlier pass pinned
   .legend-panel .item{min-height:88px}, taken from the mockup's row height. But the
   mockup's rows are 87px BECAUSE each carries a two-line description; the height was a
   RESULT, not a spec. Pinned, a row with no description is 27px of content in an 88px
   box, which is why the Legend reads as sparse — and why it got more obvious once the
   chip moved up beside the name. Height follows content again, with a floor that still
   clears a comfortable drag/touch target. */
.legend-view .legend-panel .item{min-height:56px}
@media (max-width:768px){ .legend-view .legend-panel .item{min-height:60px} }

/* ============================================================================
   Screen 04 · Structure Manager — measured against kimi-screen04.html, 2026-08-06.
   This screen was built well: section-card metrics (8/8/12, r16), row height 72,
   row padding 14/12, list gap 24, row-divider colour and action-link colour all
   already matched and are NOT touched. Gemini additionally claimed the dividers,
   the link brightness and the row padding differed — all three measure identical,
   so they were rejected rather than "fixed".
   ========================================================================== */

/* ---- GLOBAL: the shared page head. Both of these were fixed scoped-to-Legend on
   screen 03 and turned up again here, which makes them component bugs rather than
   screen bugs. .legend-top / .manager-note are one component used by every
   pagehead view, so they are corrected once, here. ---- */
.legend-top h2{letter-spacing:var(--tracking-display)}
.manager-note{font-size:var(--text-body-axd)!important;max-width:72ch}

/* ---- the mockup has no outer container: the page head and the section cards sit
   directly on the canvas. Wrapping them in a --surface card ALSO made the section
   cards vanish, because they are --surface too — card on card, same tone. ---- */
.structure-panel{background:transparent!important;border:0!important;border-radius:0!important;padding:0!important}

/* ---- type hierarchy, the substance of this screen. A workflow row title was
   rendering at the SECTION title's 15.5/500, so "Section 1 · …" and "1.1 · …" carried
   identical weight and the list read flat. The mockup steps down: section 15/500,
   workflow rows 14/400 (body voice). ---- */
.structure-panel>.list>.card>.item .item-title{font-size:16px}
.structure-panel .card>.list>.item .item-title{font-size:14px;font-weight:400}
/* meta lines are 12/faint, not 12.5/muted — they are counts, not content */
.structure-panel .item-meta{font-size:12px!important;color:var(--color-faint)}

/* ---- page-level actions are full-size buttons in the mockup, not .btn.small ---- */
.structure-panel .legend-top .head-actions .btn{
  min-height:36px!important;height:auto!important;padding:0 16px!important;
  font-size:var(--text-button)!important;border-radius:var(--radius-control)!important}

/* ---- section card hairline back to the default tier ---- */
.structure-panel>.list>.card{border-color:var(--color-line)!important}

/* ---- row actions take the control radius; the pencil is borderless, as on screen 03.
   Kept scoped rather than global: row pencils appear in contexts (screens 05-16) that
   have not been reviewed yet. Worth one global sweep once they have. ---- */
.structure-panel .actions .btn{border-radius:var(--radius-control)!important}
.structure-panel .actions .edit-pen{border-width:0!important;background:transparent!important;color:var(--color-muted)}
.structure-panel .actions .edit-pen:hover{background:var(--color-hover)!important;color:var(--color-ink)}

/* ---- Screen 05 · the BETA tag beside "AI review".
   It measured 32px tall against the mockup's 24.5. The height was not padding: the tag is
   inline-block with no line-height of its own, so it inherited the 32px line box of the h2
   it sits next to. Giving it its own line-height (and inline-flex, so the box is the text)
   fixes the height; the rest brings it onto the mockup's badge recipe — 11/500 at .04em on
   --control with a --line hairline, rather than 10/700 at .08em on 8% white. ---- */
.ai-beta-tag{display:inline-flex;align-items:center;line-height:1.5;
  font-size:11px;font-weight:500;letter-spacing:.04em;
  color:var(--color-muted);background:var(--color-control);
  border:1px solid var(--color-line);padding:3px 8px;border-radius:var(--radius-chip)}

/* ============================================================================
   Screen 05 · Review — measured against kimi-screen05.html, 2026-08-06.
   Already matching and left alone: card fill/radius, the segmented control,
   the Run button, check-row height and radius, and the 2px severity stripe.
   ========================================================================== */

/* the mockup stacks the controls: Check row -> workflow picker -> context badge
   -> Depth + Run. The build packed all three groups into one wrapping row. */
.ai-check .ai-check-controls{display:grid;grid-template-columns:minmax(0,1fr) auto;
  align-items:center;gap:16px;row-gap:14px}
.ai-check .ai-check-controls>.ai-scope{grid-column:1/-1;display:grid;
  grid-template-columns:auto minmax(0,1fr);align-items:center;gap:12px}
.ai-check .ai-check-controls>.ai-scope>.seg{justify-self:start}
.ai-check .ai-check-controls>.ai-scope>.ai-wf-pick,
.ai-check .ai-check-controls>.ai-scope>.ai-weight{grid-column:1/-1}
.ai-check .ai-check-controls>.ai-scope>.ai-weight{justify-self:start}
.ai-check .ai-check-controls>.ai-depth{grid-column:1;justify-self:start}
.ai-check .ai-check-controls>.ai-run{grid-column:2;justify-self:end}

/* card padding 24 (was 18). Scoped to >=721px: the mockup is a 1440 spec, and at 320
   the extra 12px of inset squeezed the "Fix All with Axi (169)" summary 8px past the
   viewport. Phones keep the tighter 18. */
@media (min-width:721px){ .ai-check,.ai-check + .card.page-card{padding:24px} }
/* field labels are 12.5/faint — they name a control, they are not content */
.ai-check .ai-scope-label{font-size:12px!important;color:var(--color-faint)}
/* the context badge is a status badge: 11/500, same recipe as the others */
.ai-weight{font-weight:500}
/* segmented active state takes the mockup's inset + chip radius */
/* lifted to the global segment block at the end of this file — see screen 06. */
/* "What this check looks for" lost its padding entirely */
.v-rules,.expandrow{padding:14px 16px}
.v-rules>summary,.expandrow>summary{padding:0}
/* check rows are body text at 14, not 14.5 */
.validate-row{font-size:14px}

/* ============================================================================
   STATUS PALETTE — app-wide, 2026-08-06. Found on screen 05 and NOT a screen-05
   bug: app.css referenced the rulebook's four status colours exactly ONCE, while
   an off-palette amber (#E8D3A8, rgba(197,179,88,…), rgba(150,120,40,…)) and red
   (rgba(180,35,24,…), #F1C4C0) family ran through ~48 declarations.

   AXD-I §Color fixes both the hues and the recipe:
     success #7FA287 · warning #AE9674 · danger #BC8F8B · info #839CBC
     badge = 14% fill · 35% border · full-strength text
   The tokens were already correct (--color-success/warning/danger/info and the
   --ok/--warn/--err aliases); the literals simply bypassed them. Repointed by
   COMPONENT here rather than by editing 48 literals in place, so the recipe
   lives in one readable block.

   DELIBERATELY EXCLUDED — these are functional colour, not severity:
     · .step-type.* — step-type pills are user-configurable (D1)
     · .svg-icon.*  — legend icon colours are user-configurable (D1)
     · .do-main-badge / .do-seg-yes — the decision Main-path Yes/No keeps its
       own green by an earlier explicit decision (D4)
   ========================================================================== */
:root{
  --sev-ok-fill:color-mix(in srgb,var(--color-success) 14%,transparent);
  --sev-ok-line:color-mix(in srgb,var(--color-success) 35%,transparent);
  --sev-warn-fill:color-mix(in srgb,var(--color-warning) 14%,transparent);
  --sev-warn-line:color-mix(in srgb,var(--color-warning) 35%,transparent);
  --sev-err-fill:color-mix(in srgb,var(--color-danger) 14%,transparent);
  --sev-err-line:color-mix(in srgb,var(--color-danger) 35%,transparent);
}
/* ---- warning ---- */
.v-pill.warn,.v-badge.warn,.ai-sev.medium,.rr-sev.med,.ai-posture.is-warn,
.ai-weight.w-heavy,.ai-inferred,.kb-warn,.analysis-note,.analysis-empty,
.readiness-report.rr-review,.rr-review .rr-icon,.review-soft-nudge{
  color:var(--color-warning)!important;
  background:var(--sev-warn-fill)!important;
  border-color:var(--sev-warn-line)!important}
.ai-prov-warn{color:var(--color-warning)!important}
.validate-row.warn{border-left-color:var(--sev-warn-line)!important}
.analysis-flag.med,.ai-issue.sev-medium{border-left-color:var(--sev-warn-line)!important}
/* ---- danger ---- */
.v-pill.error,.v-badge.error,.ai-sev.high,.rr-sev.high,.ai-weight.w-too-heavy,
.ai-warn,.ai-check-error,.kb-error,.readiness-report.rr-blocked,.rr-blocked .rr-icon{
  color:var(--color-danger)!important;
  background:var(--sev-err-fill)!important;
  border-color:var(--sev-err-line)!important}
/* ---- success ---- */
.ai-weight.w-light,.ai-src-chip,.kb-kind-sop{
  color:var(--color-success)!important;
  background:var(--sev-ok-fill)!important;
  border-color:var(--sev-ok-line)!important}
.ai-key-detect.ok,.ai-embed-note.ok,.ai-models-note.ok,.kb-status.ok,
.ai-check-clean,.ai-applied,.ai-prov-line.is-local .ai-prov-note{
  color:var(--color-success)!important}

/* ============================================================================
   Screen 06 · Comments / Governance — measured against kimi-screen06.html.
   Already matching and untouched: card fill/radius/hairline, the eyebrow
   (11/500/.12em faint), title size and weight, the readonly well (#111, r10,
   16px) and the "Approval unavailable" chip — that one is an EXACT match; a bad
   selector made it look absent on the first pass.
   ========================================================================== */

/* ---- SHARED segmented control. Screen 05 scoped this to .ai-check; screen 06
   showed why that was wrong twice over: the rule did not reach the Comments
   filter, and the Comments filter is not even .seg — it is .cmt-ov-filters.
   Both are the same component, so both are specified here, once. ---- */
/* Specificity, diagnosed with the CSSOM rather than guessed: an earlier pass already
   pairs these two as one component at (0,2,0) with !important —
     .seg>.ai-scope-btn,.cmt-ov-filters>.cmt-ov-chip{padding:0 14px!important;border-radius:var(--radius-chip)!important}
   and the gap is pinned by .comments-overview .cmt-ov-filters, also (0,2,0). A plain
   .cmt-ov-filters>button rule is (0,1,1) and loses to both no matter how late it sits,
   so these match the existing selector SHAPE and win on order instead. */
.seg,.comments-overview .cmt-ov-filters{gap:2px}
.seg>.ai-scope-btn,.cmt-ov-filters>.cmt-ov-chip,
.seg>button,.cmt-ov-filters>button,
.ai-check .ai-scope-btn,.ai-check .ai-depth-btn{
  padding:5px 14px!important;border-radius:var(--radius-chip)!important}

/* ---- content-card padding. The mockups spec 24px for main-view content cards,
   but a blanket rule would be wrong: Structure's section cards are deliberately
   8/8/12, and screen 01's table card and the Legend/Structure page shells are
   deliberately 0. Those exceptions are named rather than relied on. ---- */
@media (min-width:721px){
  #mainView .card.page-card:not(.drag-item):not(.wf-view):not(.pagehead-view){padding:24px}
}
/* stack gap between governance cards */
.review-view{gap:24px}

/* ---- card titles. These four are the ONLY headings in the app missing the
   display tracking: 01/03/04 are 28/-0.28 and 05 is 20/-0.2 and 23/-0.23, all
   already correct. A blanket heading rule would have "fixed" four views that
   did not need it. ---- */
.review-card-head h2,.analysis-head h2{letter-spacing:-0.01em}

/* ---- meta is 12.5/faint: it annotates a control, it is not content ---- */
.review-cadence-meta,.review-checks-foot .muted{font-size:12px!important;color:var(--color-faint)}

/* ---- empty state is centred on a readable measure, not full-bleed ---- */
.cmt-ov-empty{font-size:14px!important;max-width:56ch;margin-left:auto;margin-right:auto;text-align:center}

/* ---- the cadence control reads as a secondary button in the mockup. Styled,
   not rewired: it stays the read-only element it is when governance is off. ---- */
.review-readonly-value{display:inline-flex;align-items:center;min-height:36px;padding:0 16px;
  font-size:var(--text-button);font-weight:500;letter-spacing:var(--tracking-button);
  border:1px solid var(--color-linestrong);border-radius:var(--radius-control);
  background:transparent;color:var(--color-muted)}

/* ===== Axi assistant panel (screen 14 + 14b) =====
   The panel is already the mockup's 380px. Everything here is the interior. */

/* Head: the glyph sits in a 30px tile (see the .chat-badge wrapper in renderChat), the row is
   68 tall on 16px padding, and a hairline separates it from the body — the build had a bare
   22px glyph in a 54px head with no rule under it. */
/* 2026-08-14: the tile is gone. It gave the Axi mark a surface2 fill and a hairline, which the
   logo kit forbids outright — "Never recolor, add a gradient, glow, OUTLINE, or shadow" — and it
   framed a mark that is not interactive, so the box promised a button that was never there.
   The wrapper stays as a sizing slot (the mark keeps its 30px box and stays optically centred);
   only the fill and the line come off. */
.chat-badge{
  width:30px;height:30px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center}
/* .ai-glyph carries margin-right:.4em for its INLINE use (a glyph set before a run of text).
   .chat-badge is an icon-only tile, so that margin just shoved the mark off-centre inside it:
   measured 3.7px of space on the left against 9.3px on the right. Same root cause as the
   toolbar toggle fixed in g283 — that fix was scoped to .tog-ico and missed this slot. Audited
   every .ai-glyph in the app: .tog-ico, .chat-badge and .chat-empty-glyph are icon-only; the
   in-button glyphs legitimately keep the margin because text follows them. */
.chat-badge .ai-glyph{width:17px;height:17px;margin:0}
.chat-head{padding:16px;gap:12px;border-bottom:1px solid var(--color-line)}
.chat-head-title{gap:12px}
.chat-head-name b{font-size:16px;font-weight:var(--fw-control)}
.chat-head-name .chat-sub{font-size:12px;color:var(--color-faint)}

/* Close is a borderless icon button, not an outlined one — same treatment as the borderless
   row pencils on screens 03/04.
   SIZE, 2026-08-12 (owner): 36px, not the mockup's 28. Rulebook v1.4 §Controls is explicit —
   "Control height 36px with pointer input; 44px on touch" — and at 28 with a 12.5px text "×"
   this read as a stray character rather than a control. Same mockup-vs-rulebook call already
   made for type weight: the book wins. The glyph is now a 16px stroked SVG like every other
   icon button, instead of a text character that inherited .btn.small's 12.5px. */
.chat-head .chat-close{
  width:36px;min-width:36px;height:36px;min-height:36px!important;padding:0;
  border-color:transparent;color:var(--color-muted);border-radius:var(--radius-chip)}
.chat-head .chat-close>svg{width:16px;height:16px;display:block}
@media (max-width:720px){
  .chat-head .chat-close{width:44px;min-width:44px;height:44px;min-height:44px!important}
}
.chat-head .chat-close:hover{background:var(--color-hover);color:var(--color-ink);border-color:transparent}

/* Context row: the mockup's .axi-context is a --surface2 well at control radius on the body's
   12.5, not a --control chip at 12 with a 700-weight name. */
.chat-sees{
  padding:8px 12px;border-radius:var(--radius-control);background:var(--color-surface2);
  font-size:12px;line-height:1.55;color:var(--color-muted);gap:8px}
.chat-sees b{font-weight:var(--fw-control);color:var(--color-ink)}
.chat-sees-note{font-size:12px;color:var(--color-faint)}

/* Empty state: a real heading (16/500) over a 13/1.55 body on a readable measure. */
.chat-empty-h{font-size:16px;font-weight:var(--fw-control);color:var(--color-ink);margin-bottom:8px}
.chat-empty-sub{font-size:14px;line-height:1.55;color:var(--color-muted);max-width:34ch;margin:0 auto}

/* Starters are OUTLINED prompts, not filled chips — filled, they competed with the message
   bubbles for weight. 34 tall at the control radius, 12.5/500. */
.chat-starters{gap:8px}
/* Vertical padding is 7px, not 0. With `padding:0 12px` the 34px min-height was doing the
   optical padding, which works only while the label fits one line — the moment it wraps the
   box grows to exactly the text height and the air disappears. Measured on the narrow Axi
   panel: the "Next Setup Step (3/7 done)…" starter was 41px tall around 38.75px of text,
   ~1px top and bottom, against 7.3px on every single-line starter beside it. 7px matches
   that optical padding, and single-line starters are unchanged because 19.375 + 14 = 33.4
   still lands under the 34px min-height. */
.chat-starter{
  min-height:34px;padding:7px 12px;border-radius:var(--radius-control);
  background:transparent;border:1px solid var(--color-linestrong);
  color:var(--color-ink);font-size:12px;font-weight:var(--fw-control)}
.chat-starter:hover{background:var(--color-hover)}
/* the nudge variant keeps its own full-width shape; only the skin follows */
.chat-starters.chat-nudge .chat-starter{text-align:left}

/* Composer — owner-confirmed to follow the mockup. It was r18 on a translucent
   rgba(255,255,255,.035) fill with two 50%-round buttons: none of r18, 50% or the translucency
   is on the AXD-I scale, and the panel's most-used control should be the plain 48px field. */
.composer{
  min-height:48px;border-radius:var(--radius-control);
  background:var(--color-control);border-color:var(--color-line);
  /* 34px row + 6 + 6 padding + 2 border = the mockup's 48 exactly. The vertical padding is
     what centres the row: an earlier layer sets align-items:flex-end (correct — the textarea
     auto-grows to 160px and the buttons should stay pinned to the bottom as it does), so with
     0 padding everything sat 1px off the floor with a 13-16px gap above it. */
  /* Padding is even on all four sides so each control is equidistant from the edges it
     touches: measured, 6px padding + 1px border = a 7px gap, matching the 7px above and
     below. It was 6/8/6/14, which put the + 15px from the left against 7px top and bottom
     (visibly inset) and the arrow 9px from the right. The textarea's inset is now the
     button plus the 10px gap rather than a hard-coded left pad. */
  padding:6px;gap:10px}
.composer-plus,.composer-send{
  width:34px;height:34px;border-radius:var(--radius-chip);flex:0 0 auto}
/* The glyphs were 21px at stroke 2.2 — heavier and larger than every other icon in the app,
   which draws at 16px / 1.5. 16px in a 34px button also gives the same 9px inset the toolbar's
   icon-only squares use, so the composer now reads at the same weight as the rest of the UI. */
.composer-plus svg,.composer-send svg{width:16px;height:16px;stroke-width:1.5}
/* Send carried a surface2 FILL and a linestrong HAIRLINE at once, which is off the tier ladder:
   "Secondary carries a hairline. Primary carries a solid fill" — a control sits on one rung or
   the other, never half of each. It drops the fill and stays Secondary rather than climbing to
   Primary, because the primary is rationed one-per-view and this screen already spends it on
   Draft Steps; a send arrow in a docked panel is not the thing that should outrank it.
   Owner call 2026-08-14. */
.composer-send{
  background:transparent;border:1px solid var(--color-linestrong);color:var(--color-ink)}
/* The field has to be the same 34px as the buttons at rest, or flex-end lines up three items
   of different heights. An earlier layer left it at min-height 24 + 5/6 padding = 31px, which
   is why the placeholder sat 3px low against the + and the arrow. 34 = 21px line + 6 + 6 + ~1. */
.composer textarea{
  font-size:14px;line-height:21px;min-height:34px;padding:6px 6px;align-self:flex-end}
.composer-meta{text-align:center;font-size:12px;color:var(--color-faint)}

/* ---- conversation (screen 14b) ---- */
/* Only the USER gets a bubble; Axi's replies flow. That was already right — these are metrics. */
.chat-msg.me{
  max-width:86%;padding:10px 14px;border-radius:var(--radius-card);
  background:var(--color-surface2);line-height:1.55}
.chat-msg.ai{line-height:1.6}
.chat-msg.ai li{font-size:14px;color:var(--color-muted)}

/* ===== Settings panel (screen 16) =====
   Built from the mockup's own class names, so nearly everything already measured: panel 720,
   top bar 73, title 22/400/-0.01em, search 360x36, rail 216, navrow 195, main 503, cat head
   16/500, cat summary 12.5/faint, row divider .08, footer hairline, version eyebrow.

   The two divergences are both an inherited 20px line-height where the mockup leaves it at the
   font's normal — which inflated the row heights by 4px each. */
.setpanel .navrow{line-height:16px}
.setpanel .sr-t{line-height:16px}

/* ---- Toolbar: one rule for icon+text vs icon-only ------------------------------------
   OWNER RULE 2026-08-11: when there is room, every toolbar control shows its icon NEXT TO
   its label. When there is not, all three collapse to the icon ALONE in a square button
   with the icon centred — sandwich for Contents, the Axi glyph for the assistant, a gear
   for Settings. Previously only Contents and Axi collapsed while "Settings" kept its label,
   which is the inconsistency this fixes; Settings had no icon at all until now.

   The threshold is 1440 because that is where the labelled bar actually fits: measured,
   .toolbar-toggles is 602px and .toolbar-actions 521px with labels on, which leaves the
   title group 17px at 1200 and crushes the SOP name, the v47 chip and the posture pill to
   a sliver. Below 1440 — tight desktop, iPad and phone alike — icons only.

   Squareness is `aspect-ratio:1` rather than a hard width, so the button stays exactly as
   wide as it is tall at every breakpoint (34px desktop, 44px touch target on phones)
   without hard-coding either number. padding:0 plus centred flex puts the glyph in the
   middle on both axes. ---- */
@media (max-width:1439px){
  .toolbar-tog>span:not(.tog-ico){display:none}
  .toolbar-tog{
    aspect-ratio:1;
    width:auto!important;min-width:0!important;
    padding:0!important;gap:0!important;
    /* !important is load-bearing: at <=720 `.toolbar-actions > .btn` sets flex:1 1 0 at
       (0,2,0) to spread the action buttons across the row, which outranks a plain
       `.toolbar-tog` and stretched Settings to 131x44 while the other two stayed square. */
    flex:0 0 auto!important;
    display:inline-flex!important;align-items:center;justify-content:center;
  }
}

/* Reclaiming the rest of the width in the tight DESKTOP band only. Below 1101 the bar
   already wraps to two rows, so it does not need the switcher tightened — scoping this
   away from mobile leaves those surfaces untouched. */
@media (min-width:1101px) and (max-width:1439px){
  .viewseg button{padding:0 8px}
  /* A1: the underline is inset to the padding, so it has to follow the tightened padding */
  .viewseg button::after{left:8px;right:8px}
  .toolbar{gap:12px!important}
}

/* Icon+text state: one gap between glyph and label for all three controls, and symmetric
   side padding so the icon side is not visually tighter than the label side. */
@media (min-width:1440px){
  .toolbar-tog{gap:8px!important;padding-left:14px!important;padding-right:14px!important}
}
/* The glyph box is a fixed 16px square in both states, so centring is the same maths
   whether or not a label is present. */
.toolbar-tog>.tog-ico{width:16px;height:16px;flex:0 0 auto;display:inline-flex;
  align-items:center;justify-content:center;font-size:16px;line-height:1;margin:0}
/* .ai-glyph carries margin-right:.4em for its INLINE use (a glyph set before a run of text).
   Inside an icon slot that margin is wrong twice over: in the square icon-only button it
   pushed the mark 6px left of centre (measured L6/R12 against L9/R9 for the hamburger and
   gear), and in the labelled state it added to the button's own gap, so Axi sat 14px from
   its label where Contents and Settings sat 8px. The slot is a fixed 16px box and the gap
   to the label is the button's `gap`, so the glyph needs no margin of its own here. */
.tog-ico .ai-glyph{margin:0!important}
/* All three toolbar marks are now vector: the Contents glyph was the text character U+2630,
   which drew only ~8.7px of ink inside its 16px slot against the gear's 15.7px, and sat 0.8px
   low because a text glyph is placed on a baseline rather than centred in its box. Measured
   in icon mode at 34px: ink margins were L12.67/R12.66/T14.11/B12.50 for the character against
   L9.17/R9.17/T9.67/B8.67 for the gear. The replacement is a symmetric 3-line SVG (rows at
   7/12/17 in a 24 box) at the same 1.5 stroke, so it centres exactly and matches the weight. */
.toolbar-tog>.tog-ico svg{width:16px;height:16px;display:block}

/* ---- Legend · Roles: give the badge its own column so the descriptions line up ----------
   The row is `.item > div` as a flex, so the role chip sits at its natural width and the
   description starts wherever the chip happens to end. Measured across the sample: chips
   37–54px wide, so the text began at 61 / 64 / 68 / 70 / 71 / 78 — a 17px ragged edge, which
   is the "in and out" look. The Icons panel does not have this because `.svg-icon` is a fixed
   32px box, and Step Types do not because their description sits on its own line.

   A fixed track fixes the text edge while `justify-self:start` keeps each chip its own natural
   width — the chip is NOT stretched to fill the column. minmax(58px,auto) so an unusually long
   role code widens its own row instead of overflowing. Scoped to the roles list only; the icon
   and step-type lists are untouched. ---- */
.legend-panel .item[data-drag-type="legend-role"]>div:first-child{
  display:grid;grid-template-columns:minmax(58px,auto) 1fr;align-items:center;gap:12px}
.legend-panel .item[data-drag-type="legend-role"]>div:first-child>.role-chip{justify-self:start}

/* Comments closed — the read-only notice shown in place of the composer, and as a banner on the
   Comments page. Uses the same well treatment as .note-box rather than a warning colour: closing
   comments is a normal editorial state, not an error. */
.cmt-closed-note{
  margin:10px 0 0;padding:10px 12px;
  border-radius:var(--radius-control);background:var(--color-surface2);
  color:var(--color-muted);font-size:12px;line-height:1.5}
.comments-overview>.cmt-closed-note{margin:0 0 14px}

/* ===================================================================================
   B1 "Collapse The Header Stack" — phone layout (<=720px)
   Measured baseline at 375x812 on the pre-B1 build: 888px of chrome before the first
   step row (109% of the viewport), from a 4-row toolbar (209) + workflow header (371,
   of which 252 was five stacked action buttons) + roles card (190) + subpath row (52).
   Every band below is compressed to one 44px touch row. Nothing is dropped — what
   leaves the surface moves into a disclosure that is CSS-hidden above 720px.
   Appended at the end of the sheet on purpose: these must win on order over the
   per-screen sections that sit below the earlier media queries.
   =================================================================================== */
@media (max-width:720px){
  /* --- toolbar: four wrapped rows -> one (back + context + overflow) --- */
  /* The bar is a single row again, so these go back on .toolbar itself. */
  .toolbar{flex-wrap:nowrap!important;gap:8px!important;padding:8px 16px!important}
  .toolbar-toggles{flex:0 0 auto!important;gap:8px!important}
  .toolbar-toggles > [data-action="chat-toggle"]{display:none!important}   /* -> overflow */
  .toolbar-title{flex:1 1 auto!important;min-width:0!important;gap:8px!important}
  /* version + AI posture are not lost: they head the overflow menu */
  .toolbar-title .content-ver,.toolbar-title .ai-posture{display:none!important}
  /* width:auto is load-bearing: an earlier <=720 rule sets .toolbar-actions{width:100%}, and
     `flex:0 0 auto` resolves its basis FROM that width — so the group claimed the whole bar and
     squeezed the title to 0px while pushing the overflow button 44px off-screen. */
  .toolbar-actions{flex:0 0 auto!important;width:auto!important;margin-left:0!important;gap:0!important}
  .toolbar-actions > *{display:none!important}
  .toolbar-actions > .tb-more{display:block!important}
  .tb-more-sum{width:44px!important;height:44px!important;min-width:44px!important;
    padding:0!important;justify-content:center!important;flex:0 0 44px!important}
  .tb-more-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}

  /* --- workflow title: one display moment at the mobile Display rung --- */
  .workflow-header h2{font-size:24px!important;line-height:1.2!important;letter-spacing:-.01em}

  /* --- five authoring actions -> one primary + "All Actions" --- */
  .head-actions{gap:8px!important;flex-wrap:nowrap!important;width:100%}
  .head-actions > .btn:not(.ha-primary){display:none!important}
  .head-actions > .ha-primary{display:inline-flex!important;flex:1 1 auto!important;min-width:0}
  .head-actions > .ha-more{display:block!important;flex:0 0 auto!important}
  .ha-more-sum{min-height:44px!important;display:inline-flex!important;align-items:center;
    gap:6px;white-space:nowrap}

  /* --- roles legend -> a 44px disclosure row, names still glanceable in the subline --- */
  .role-panel{padding:0!important;margin-bottom:12px!important}
  .role-panel h3{display:none!important}
  .role-panel-toggle{display:flex!important;align-items:center;gap:10px;width:100%;
    min-height:44px;padding:0 16px;background:none;border:0;color:var(--color-ink);
    font:inherit;text-align:left;cursor:pointer}
  .rpt-text{display:flex;align-items:baseline;gap:8px;min-width:0;flex:1 1 auto}
  .rpt-label{font-size:12px;font-weight:500;letter-spacing:.02em;flex:0 0 auto}
  .rpt-sub{font-size:12px;font-weight:400;color:var(--color-faint);min-width:0;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .rpt-chev{width:16px;height:16px;flex:0 0 16px;color:var(--color-muted);
    transition:transform .15s ease}
  .role-panel.is-open .rpt-chev{transform:rotate(180deg)}
  .role-panel:not(.is-open) .role-panel-body{display:none!important}
  .role-panel.is-open .role-panel-body{padding:0 16px 16px}

  /* --- step cards: three lines instead of six stacked full-width blocks ---
     The table already collapsed to a card here, but every cell took its own full-width
     line, so one step was 357px and barely more than one card cleared the fold. Laid out
     in natural DOM order (no `order` juggling): identity on line 1, prose on line 2,
     icons + following-step/tools on line 3. ALL SIX FIELDS SURVIVE — number, role badge,
     step-type badge, description, icons, following step (plus the inline edit tools that
     share the last cell). Nothing is hidden; only the wrapping changed.

     SPECIFICITY, not importance: a rule inside @media(max-width:600px) sets
     `.wf-view .table-wrap tr{display:block!important}` at (0,2,1), which beats a bare
     `.step-row{...!important}` at (0,1,0) — !important does NOT substitute for specificity
     when both sides are important. Hence `.page-card.wf-view tr.step-row` at (0,3,1), which
     also covers the nested subpath tables. */
  /* g391 — THE CARD REJOINS THE SPACING CONTRACT. It was `padding:6px 2px` on the row and
     `5px 10px` on every cell: 6, 2, 5 and 10 are none of them on the 4px ramp, and §5 invariant 2
     allows exactly one ramp step of compression below 768 (the desktop cell is 16px 12px, so the
     floor is 12px 8px) — not an ad-hoc set of four new numbers.
     It also spent spacing the way invariant 4 forbids: the rhythm between the card's three
     stacked lines came from each CELL's own padding rather than once from the parent, so the
     gap between siblings was really two paddings added together and could never be read off a
     single value. Now the parent owns it — `gap:12px` is gap-item at mobile on the cross axis
     and gap-inline on the main axis, which is the same 12 either way — and the cells carry no
     spacing at all. The card's own inset is 16px, which is inset-card at mobile. */
  .page-card.wf-view tr.step-row{display:flex!important;flex-wrap:wrap;align-items:center;
    gap:12px!important;padding:16px!important}
  .page-card.wf-view tr.step-row > td{display:block!important;width:auto!important;
    flex:0 0 auto;padding:0!important;border:0!important;
    /* §3 — Body is 14px. The 16px rung is the Section heading, and the only thing the book
       forces to 16 below 768 is FORM FIELDS, for iOS zoom (see the block at the end of this
       sheet, which lists the input types by name). This is read-only description text, and it
       was inheriting 16 from the blanket `table{font-size:16px!important}` at ~L1868 — so the
       same sentence rendered a rung LARGER on a phone than on the desktop it was written for. */
    font-size:14px!important}
  .page-card.wf-view tr.step-row > td:nth-child(4){flex:1 1 100%;min-width:0}  /* description */
  .page-card.wf-view tr.step-row > td:nth-child(5){flex:0 0 auto}              /* icons */
  .page-card.wf-view tr.step-row > td:nth-child(6){flex:1 1 auto;min-width:0}  /* next + tools */
  .page-card.wf-view tr.step-row > td:nth-child(6) .pen-cell-inner{display:flex;
    align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}

  /* --- subpath filter -> one 44px row --- */
  /* mobile keeps the separation, just less of it: 16 rather than the desktop 24. This rule used
   to zero the top margin outright, which put the selector back against the action bar on
   every phone — the exact adjacency being fixed above. Vertical space is scarce here, so it
   drops a rung instead of disappearing. */
.subpath-controls{min-height:44px;display:flex;align-items:center;gap:10px;
    flex-wrap:nowrap!important;margin:16px 0 12px!important}
  .subpath-controls .spc-label{flex:0 0 auto}
  .subpath-controls .seg{flex:1 1 auto;min-width:0;justify-content:flex-end}
}
/* The disclosures exist only on the phone surface; above 720 the real controls are on show. */
@media (min-width:721px){
  .role-panel-toggle{display:none!important}
  .tb-more,.ha-more{display:none!important}
}

/* ===================================================================================
   AXD-I v1.5 §3 — form fields at or below 768px are forced to 16px (iOS zoom prevention).
   This was being violated: the generic field rule at ~L290 sizes fields with
   var(--fs-body), and the reskin repointed --fs-body to 14px inside @media(max-width:768px),
   so fields resolved to 14px on phones and iOS zoomed the page on focus. The rule is
   restated here, last in the sheet, so it survives the ladder rather than depending on it.
   =================================================================================== */
@media (max-width:768px){
  select,
  input[type=text],input[type=password],input[type=email],input[type=number],
  input[type=search],input[type=url],input[type=tel],input[type=file],
  textarea{ font-size:16px }
}



/* ===================================================================================
   ONE BAR — the view switcher rejoins the top bar as ICONS.
   Five text labels cannot share a 375px row with the commands; five icons can. The label
   rides along where the width allows, and `title` names each one at every width.
   Active state keeps the 2px ink underline (owner decision — see the §6 note in the log).
   =================================================================================== */
.toolbar .viewseg button{gap:8px;padding:0 10px;height:36px;border-radius:var(--radius-chip)}
.toolbar .viewseg button::after{left:8px;right:8px}
.vs-ico{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex:0 0 18px}
.vs-ico svg{width:18px;height:18px;display:block}
.vs-lbl{display:none}                       /* icons by default; the label is opt-in by width */
/* Labels at >=1600, not 1440. Measured: with labels on at 1440 the SOP title collapses to a
   61px box against the 405px it needs — the labels were eating the document's identity. At 1600
   the title keeps 221px, at 1920 it fits whole. Below 1600 the icons carry it, with `title` on
   hover naming each one. */
@media (min-width:1600px){ .toolbar .vs-lbl{display:inline} }

/* §6 Components — Buttons: the GHOST variant is "borderless, muted text; hover fills" and its
   stated home is toolbars. The bar's controls were bordered boxes, which is the Secondary
   variant's look, so the chrome read as a row of competing buttons. */
.toolbar .toolbar-tog{background:transparent!important;border-color:transparent!important;
  color:var(--color-muted)!important}
/* Ghost hover FILLS — the line above quotes the spec, the old rule contradicted it by forcing
   `background:transparent` on hover, so the bar's controls only brightened their text.
   No border: ghost carries no box, it just takes tone (faded -> brighter).
   The fill sits one rung BELOW the engaged fill (see the panel-open block) so hovering an
   un-engaged control can never read as more "on" than an engaged one. */
.toolbar .toolbar-tog:hover:not([disabled]){background:var(--color-surface)!important;
  border-color:transparent!important;color:var(--color-ink)!important}
.toolbar .toolbar-tog[disabled]{opacity:.4}
/* §15 "one primary action per view": parked in permanent chrome, a primary Save would spend
   every view's primary slot forever. Save is Secondary; the page keeps the white. */
.toolbar #saveBtn{background:transparent!important;color:var(--color-ink)!important;
  border:1px solid var(--color-linestrong)!important}
.toolbar #saveBtn:hover{background:var(--color-hover)!important}

/* The rendering-mode control now sits on the workflow title row, right-aligned into the space
   beside the title — level with the step list it governs. */
.wf-title-row{display:flex;align-items:center;gap:20px;flex-wrap:wrap}
.wf-title-row h2{flex:1 1 auto;min-width:0;margin:0}
/* ===== SELECTOR (v1.8 named-control table) =========================================
   Table View / Workflow View is a SELECTOR — "one of several: views, ranges, modes" —
   not a pair of buttons. The spec is a `surface2` TRACK whose options sit on it, and an
   engaged state carried by a THUMB that TRAVELS to the chosen option. It was built as a
   `control` track whose active option took a surface2 fill plus a linestrong border, which
   is three violations at once: wrong track tone, a fill where the thumb belongs, and a line
   on an engaged state (v1.8 retired those entirely).

   Two options only, so the thumb is pure CSS: an inline-GRID of two equal columns means the
   thumb is exactly half the inner width and one translate lands it on option 2. The deck
   positions its thumb with JS (offsetLeft/offsetWidth) because it supports N options; equal
   columns get the same result here without shipping script. Options stay transparent at all
   times — the thumb is the only fill, which is what makes it read as travelling rather than
   as two independently-lit buttons. ===== */
.modeseg{position:relative;display:inline-grid;grid-template-columns:1fr 1fr;align-items:center;
  background:var(--color-surface2);border-radius:var(--radius-control);padding:3px;gap:3px;flex:0 0 auto}
.modeseg::before{content:"";position:absolute;top:3px;left:3px;height:28px;
  width:calc((100% - 9px) / 2);background:var(--color-overlay-hover);
  border-radius:var(--radius-chip);pointer-events:none;z-index:0;
  transition:transform 200ms var(--ease-soft,cubic-bezier(.2,.85,.22,1))}
.modeseg:has(button:nth-of-type(2).on)::before{transform:translateX(calc(100% + 3px))}
.modeseg button{position:relative;z-index:1;height:28px;padding:0 12px;border:0;background:transparent;
  color:var(--color-muted);font:inherit;font-size:14px;font-weight:500;letter-spacing:.02em;
  border-radius:var(--radius-chip);cursor:pointer;white-space:nowrap;
  transition:color 160ms var(--ease-soft,ease)}
/* hover on a selectable control brightens the text and paints nothing (§6 rule 2) */
.modeseg button:hover{color:var(--color-ink);background:transparent}
.modeseg button.on{background:transparent;color:var(--color-ink)}

/* The rail is permanent at every width now, so the drawer copy would be a duplicate. */
@media (max-width:1100px){ .viewseg-drawer{display:none!important} }
@media (max-width:720px){
  .toolbar .viewseg button{padding:0 7px}
  .toolbar .viewseg button::after{left:5px;right:5px}
  /* Two tokens, not one. Down the cross axis it separates the title block from the selector
     below it: stack-heading, 12. Across the main axis it separates the title from its own edit
     pencil: gap-icon, 8 — the same 8 the desktop layer spends there. */
  .wf-title-row{gap:12px 8px}
}

/* §6 Containers — Badge: "11px/500 uppercase 0.08em". The v1.5 pass put .v-badge on the
   eyebrow's 0.12em; badges have their own tracking rung. */
.v-badge{letter-spacing:.08em}

/* The bar stays one row all the way down: the toggles are squares below 1440 and the rail is
   icons below 1440, so nothing needs to wrap. Without this 768 wrapped to 103px. */
@media (max-width:1100px){
  .toolbar{flex-wrap:nowrap!important}
  .toolbar .toolbar-actions{flex-wrap:nowrap!important}
  .toolbar .toolbar-title{flex:1 1 auto;min-width:0}
}

/* On phones the title row cannot afford the mode control: it wraps to its own line and pushes
   the first step row 380 -> 422px, undoing a tenth of what B1 won. Mode is already reachable in
   the ⋯ overflow at this width, so it is not lost — just not spent twice. */
/* ...AND IT COMES BACK (g391). That note stopped being true at g362, which rebuilt the toolbar
   as the phone bottom bar and set `.toolbar .tb-more{display:none!important}` — the ⋯ overflow
   this rule leaned on has not existed on a phone since. Two rules, each defensible alone, that
   together deleted the feature.
   MEASURED before this change: at 320, 390, 430, 560, 700 and 720 there was NO visible control
   of any kind for Table View / Workflow View. It became reachable only at 721. The flowchart is
   half the product on a decision-heavy SOP, and no phone user could open it.
   So it takes its own row rather than competing for the title's line. That is the 42px the
   original note was trying to save, and a feature nobody can reach is not a saving. It matches
   the SUBPATHS selector immediately below, which has always sat on its own full-width row at
   this size and reads fine. */
@media (max-width:720px){
  .wf-title-row{flex-wrap:wrap}
  /* THE PENCIL STAYS WITH THE TITLE (g393). `h2` is `flex:0 1 auto`, so its box grows to the
     full row width the moment the title wraps to a second line — leaving nothing for the pencil,
     which then wrapped onto a flex line of its own. Measured at 390: the title's last text line
     ended at x=108 and the pencil sat at x=18, 90px to its LEFT and 13px below, alone on a row.
     It read as a stray control belonging to nothing, which is exactly what it had become.
     Giving `h2` a zero basis makes it take the space that is LEFT after the pencil rather than
     all of it, so the pencil holds its place on the first line and the title wraps inside what
     remains. `align-self:flex-start` keeps it beside the title's first line: against a two- or
     three-line heading, centring it vertically strands it in the middle of the text block. */
  /* The doubled class is specificity, not superstition: `.wf-title-row h2{flex:0 1 auto}` lives
     ~440 lines BELOW this block at equal specificity (0,1,1), so it won on source order and the
     pencil kept orphaning. `.wf-title-row.wf-title-row` is (0,2,1) and wins wherever it sits,
     which is steadier than !important — a later !important would beat that too, but nothing here
     needs to out-shout anything, only to be more specific.
     A CAP, NOT A GROW. `flex:1 1 0` was tried first and overshot in the other direction: the h2
     then took ALL the leftover width even when the title was short, so at 560 the title ended at
     x=391 and the pencil sat at x=498 — 107px adrift on a single line, a worse orphan than the
     one being fixed. Shrink-to-content with a max-width that reserves the pencil's 44 plus its
     8px gap does both jobs: a short title keeps the pencil at its shoulder, and a long one wraps
     inside the cap while the pencil holds the first line. */
  .wf-title-row.wf-title-row>h2{flex:0 1 auto;min-width:0;max-width:calc(100% - 52px)}
  .wf-title-row .wf-title-edit{flex:0 0 auto;align-self:flex-start;margin-left:0}
  /* margin-top is gone: the row's own 12px row-gap already separates this from the title, and
     carrying both stacked them to 24. §5 invariant 4 — sibling spacing is set once, on the
     parent, and two different gaps between same-level siblings is a bug. */
  .wf-title-row>.modeseg{
    flex:1 0 100%;width:100%;margin-left:0;
    padding:4px}
  /* the desktop control is 28px, a pointer size; on touch it takes the 44 rung (§5) */
  .wf-title-row>.modeseg button{height:44px}
  .wf-title-row>.modeseg::before{top:4px;left:4px;height:44px;width:calc((100% - 11px) / 2)}
}

/* The diagram's copy of the workflow head: same title row, so the mode control is reachable from
   both renderings. It has no page-card around it, so it carries its own gutter. */
.diagram-head{padding:0 0 14px}
.diagram-head .kicker{margin-bottom:6px}

/* ===================================================================================
   BAR HIERARCHY — option A: tone only, nothing outlined.
   Three tiers carried by colour rather than chrome, and TWO active languages so the two
   kinds of navigation can never be confused:
     · panel toggles (Contents, Axi, Settings) FILL when their panel is open  — §6 Chrome
     · view tabs UNDERLINE when their view is showing                          — the rail
   Steps left the rail, so "nothing lit" now means "you are in the document"; that is only
   unambiguous because the two languages differ.
   =================================================================================== */
/* tier 1 — the majors sit at ink */
.toolbar #saveBtn{color:var(--color-ink)!important}
/* The three panel toggles are SELECTABLE controls (they have a persistent engaged state), so
   v1.7.2/§6 rule 2 applies: hover brightens the text and paints nothing. A control resting at
   ink has no brighten left to give, so they rest at muted and go ink on hover — the same
   language the view rail already speaks. This narrows #196's "majors sit at ink" to Save,
   deliberately: the alternative was a hover fill, and on this bar the hover fill (#212121)
   was BRIGHTER than the engaged fill (#171717), so hovering an unselected toggle read as more
   selected than the selected one. That collision is the reason v1.7.2 exists. */
.toolbar [data-action="toggle-toc"],
.toolbar [data-action="chat-toggle"],
.toolbar [data-action="settings-open"]{color:var(--color-muted)!important}
/* tier 2 — undo/redo are real but secondary, and are disabled much of the time */
.toolbar #undoBtn,.toolbar #redoBtn{color:var(--color-muted)!important}
/* tier 3 — the view rail stays muted until it is the active view */
.toolbar .viewseg button{color:var(--color-muted)}
/* ...but the ENGAGED tab keeps ink after the pointer leaves. `.viewseg button.on` (0,2,1) and
   `.toolbar .viewseg button` (0,2,1) tie on specificity, so the muted rule — being later in the
   file — was winning and the selected tab fell back to muted the moment you moused away. It kept
   its underline, which is why it read as "half selected". Selection is persistent (§states); it
   does not depend on the pointer being there. */
.toolbar .viewseg button.on{color:var(--color-ink)}
.toolbar .viewseg button:hover{color:var(--color-ink)}
/* §6 Buttons · Text tier: hover takes the text to ink AND "a linestrong line draws in
   underneath". Without it the four icon-only Tabs read as loose glyphs with no hint that
   they are a set, or that clicking underlines them. The line is the tell. */
.toolbar .viewseg button:hover::after{background:var(--color-linestrong)}
.toolbar .viewseg button.on:hover::after{background:var(--color-ink)}

/* Panel-open state. Keyed off the shell/body classes, NOT a rendered class: toggle-toc flips
   `.toc-collapsed` directly without a re-render, so a class baked into the button's markup
   would go stale the moment the drawer is opened. */
.app-shell:not(.toc-collapsed) .toolbar [data-action="toggle-toc"],
.app-shell:not(.toc-collapsed) .toolbar [data-action="toggle-toc"]:hover,
.app-shell.chat-open .toolbar [data-action="chat-toggle"],
.app-shell.chat-open .toolbar [data-action="chat-toggle"]:hover,
body.settings-open .toolbar [data-action="settings-open"],
body.settings-open .toolbar [data-action="settings-open"]:hover{
  background:var(--color-control)!important;color:var(--color-ink)!important;
  /* v1.8 names this control a TOGGLE: "a standalone on/off chip, one per idea" — rest no box,
     engaged `control` fill + ink text. It was on surface2, which is the SELECTOR's track tone,
     not the toggle's fill.
     Engaged stays `control` (L* 10.27) and the ghost hover fill sits a rung BELOW it on
     `surface` (L* 5.46) — measured, not assumed. Rest -> hover is ΔL* 5.46 (the §2 contract
     step, so the hover is genuinely visible) and hover -> engaged is ΔL* 4.81, so an engaged
     control always reads brighter than a merely hovered one and the two never collapse.
     Engaged is a STEP OF TONE and nothing else. The 1px linestrong ring that used to sit
     here was the v1.7.1 marker, retired in v1.7.2 — "even the 1px ink outline [was] harsh on a
     segmented control". The re-derived ladder makes surface2-on-canvas a real step (ΔL* 7.7),
     so the fill alone carries the state. */
  box-shadow:none;
}
/* Save stays bare — it is an action, not a toggle, so it has no "on" state to show. */
/* keeps the 1px transparent border its neighbours carry, or it sizes 2px smaller than them */
/* Secondary = transparent + a `linestrong` hairline + ink text (§6). The rule above already
   calls Save "Secondary"; it was drawing a TRANSPARENT border, so it rendered as a Ghost at ink
   — neither tier. The hairline is what makes it the one weighted action in the bar without
   spending the view's Primary slot on permanent chrome. */
.toolbar #saveBtn{background:transparent!important;border:1px solid var(--color-linestrong)!important;box-shadow:none!important}
.toolbar #saveBtn:hover{background:var(--color-hover)!important}

/* The view rail brightens on hover; it does not fill.
   The panel toggles and the document commands are BUTTONS — they take the hover tile. The rail
   is a set of nav labels, and its selected state is already the ink underline (§6 Chrome, dark),
   so a hover tile on top of that reads as a second, competing surface. Tone alone is enough:
   muted -> ink. This is the one place in the bar where hover is colour-only. */
.toolbar .viewseg button:hover{background:transparent!important;color:var(--color-ink)!important}
.toolbar .viewseg button:hover .vs-ico{color:var(--color-ink)}

/* The diagram's title row was added in #195 but never inherited the page-title styling: a bare
   <h2> fell through to the UA default and rendered 21px/700 — off the scale entirely, and visibly
   smaller than the same title in the table view. It is the same page title, so it takes the same
   Display rung: 28px / 300 / -0.01em (24px on mobile, per §3). */
.diagram-head h2{
  font-size:var(--fs-title);
  font-weight:var(--fw-display,300);
  letter-spacing:var(--tracking-display,-.01em);
  line-height:34px;
  color:var(--color-ink);
  margin:0;
}
@media (max-width:720px){ .diagram-head h2{font-size:24px;line-height:29px} }

/* =====================================================================================
   §5 invariant 7 — HOVER SURFACES NEVER TOUCH.
   Spacing is measured between PAINTED EXTENTS, not layout boxes, and a hover fill counts
   as part of the element. The bar's controls were ghost-with-no-fill, so at rest their
   painted extent was only their text and an 8px sibling gap read as generous. Giving the
   ghost tier its hover fill (the tier's stated behaviour) turned each control's whole box
   into a painted extent, and the same 8px became 8px between two touching fills — under
   the 12px `gap-interactive-min` contract on four pairs: Contents|Axi, Undo|Redo,
   Redo|Save, Save|Settings.
   The gap goes on the PARENT (invariant 4: siblings share one gap, never per-item
   margins) and does not compress on small screens (invariant 1: micro spacing of 12px
   and below is identical at every size).
   The <=720px overflow state keeps gap:0 on .toolbar-actions — exactly one child is
   visible there, so there is no sibling pair to separate.
   ===================================================================================== */
:root{--gap-interactive-min:12px}
.toolbar .toolbar-toggles{gap:var(--gap-interactive-min)!important}
@media (min-width:721px){
  .toolbar .toolbar-actions{gap:var(--gap-interactive-min)!important}
}

/* =====================================================================================
   Search fields take a HAIRLINE focus ring.
   A text input announces focus twice already — a blinking caret and its own border going
   to ink — so the control-weight ring lands as a third, heavy edge around a control that
   is mostly outline to begin with. The settings search was the worst of them: a 2px ring
   in `ink`, the brightest value in the system, at the exact moment the field is already
   the only lit thing in the panel.
   1px at `linestrong` still reads unambiguously as focus for keyboard users, and the
   border-to-ink swap carries the rest. Appended so it wins on order over the earlier
   per-component rules (.set-search input:focus was 2px ink at :4027) and over the global
   :focus-visible ring — matching the ordering convention this sheet already uses.
   ===================================================================================== */
/* The ring is INSET (negative offset), not outside the field. .sidebar-body clips on
   overflow-x and its clip edge lands exactly on the search input's left edge (both at x=32),
   so any ring drawn outside the border box loses its left side to that clip — which is the
   "search bar cut off on the left" symptom. Insetting keeps the whole ring inside the field's
   own box: nothing to clip, no layout shift, and the sidebar's spacing is untouched. */
/* `.set-search input` dropped from this list at g369 — the settings search no longer exists.
   The other two fields still do, and still need the inset ring. */
.search:focus,.search:focus-visible,
.icon-lib-search:focus,.icon-lib-search:focus-visible{
  outline:1px solid var(--color-linestrong)!important;
  outline-offset:-2px!important;
  border-color:var(--color-linestrong)!important;
  box-shadow:none!important;
}

/* =====================================================================================
   Save is the PRIMARY control (§6 · "The primary earns its white", v1.8).
   It rests at the top of the tone ladder — `overlay-hover` fill with `ink` text, plainly
   the heaviest control in the bar — and turns `primary` white only on hover/focus, with
   `onprimary` text. A permanently white button is the brightest object on a black screen
   whether or not anyone intends to press it; reaching for it is what lights it.
   Written in tokens, so light mode inverts on its own (primary #0F0F0F / onprimary #FFF).
   Supersedes the three earlier #saveBtn overrides (:5769, :5846, :5903) that pinned Save to
   the Secondary look. Appended last, per this sheet's ordering convention.
   ===================================================================================== */
.toolbar #saveBtn{
  background:var(--color-overlay-hover)!important;
  color:var(--color-ink)!important;
  border:1px solid transparent!important;
  box-shadow:none!important;
}
.toolbar #saveBtn:hover,.toolbar #saveBtn:focus-visible{
  background:var(--color-primary)!important;
  color:var(--color-onprimary)!important;
  border-color:var(--color-primary)!important;
}

/* =====================================================================================
   File inputs: the picker becomes a SECONDARY control (§6).
   A bare <input type=file> renders the OS "Choose File" chrome — a grey system button that
   belongs to none of the seven named types, ignores the tone ladder and the radius scale, and
   pushed the field to 52px against the 36px control height. ::file-selector-button lets the
   button half be restyled without touching markup, so the seven file inputs across the app all
   read as the Secondary they should have been: transparent, linestrong hairline, ink label,
   `hover` fill on hover.
   The input's own box keeps `control` as an input ground (§2 assigns `control` to inputs).
   ===================================================================================== */
input[type=file]{height:auto;min-height:var(--ctl-h,40px);padding:6px;font-size:14px;color:var(--color-muted)}
input[type=file]::file-selector-button{
  appearance:none;-webkit-appearance:none;
  height:36px;margin:0 12px 0 0;padding:0 16px;
  border:1px solid var(--color-linestrong);border-radius:var(--radius-control);
  background:transparent;color:var(--color-ink);
  font:inherit;font-size:14px;font-weight:500;letter-spacing:.01em;
  cursor:pointer;transition:background .15s ease,border-color .15s ease,color .15s ease;
}
input[type=file]::file-selector-button:hover{background:var(--color-hover);border-color:var(--color-linestrong)}
input[type=file]::file-selector-button:active{transform:scale(.98)}
:root[data-mode="light"] input[type=file]::file-selector-button:hover{background:rgba(20,24,30,.05)}

/* Logo drop-zone hint text is META (12/400, no tracking), not a tracked 500. Its size and
   colour were already set, but weight and letter-spacing were INHERITED from .logo-drop-text
   and landed it at 12/500 with 0.04em — which is no rung on the scale at all: Meta is 12/400/0
   and the only 500-weight small rung is the 11px UPPERCASE eyebrow. Tracking belongs to tracked
   caps; on lowercase sentence text it just loosens the word. */
.logo-drop-text small{font-weight:var(--weight-body,400);letter-spacing:normal;text-transform:none}

/* The file input's own box is not a field — it is a button plus a filename. Giving it the
   input treatment (control fill, hairline, 6px padding around a 36px button) stacked to 52px,
   past the 36/44 control heights. Strip the box; the ::file-selector-button above carries the
   control, and the filename sits beside it as body text. */
input[type=file]{background:transparent;border:0;padding:0;min-height:36px;display:inline-flex;align-items:center}

/* =====================================================================================
   Rulebook conformance sweep across the settings overlays (§3 type, §9 icons).
   ===================================================================================== */

/* Figtree ships 300 / 400 / 500 / 600 (§3). <b>/<strong> fall to the UA default of 700, which
   the family does not have — the browser then SYNTHESISES it by smearing the 600, which is why
   emphasised words in the AI capability copy looked heavier and blurrier than the rest. 600 is
   the real top weight. */
b,strong{font-weight:600}

/* 15px icons -> 16. §9: "sizes snap to 16 / 20 / 24 … never free-size an icon". 15 on a 24 grid
   also puts the 1.5 stroke on a half-pixel, which is what makes these read soft next to their
   16px neighbours. Four separate rules had drifted to 15. */
.cmt-btn svg{width:16px;height:16px}
.ai-cap-summary .caps .ck{width:16px;height:16px}
.search-ic{width:16px;height:16px}
.btn>.btn-ic{width:16px;height:16px}

/* Template Library meta text: 11px is the EYEBROW rung and is only legal as tracked UPPERCASE
   at weight 500 (§3: "below [12px] only tracked uppercase survives"). These three are lowercase
   sentence fragments — category, visibility, card meta — so they belong on Meta (12/400). */
.tmpl-cat,.tmpl-vis-badge,.tmpl-card-meta{font-size:12px;font-weight:var(--weight-body,400)}

/* Template Library type, finishing the sweep. 11px is the EYEBROW rung and §3 allows it ONLY as
   tracked uppercase at 500 — "below [12px] only tracked uppercase survives". Three controls sat
   off that:
   .tmpl-cat / .tmpl-card-meta / .tmpl-vis-badge are lowercase fragments -> Meta (12/400).
   .tmpl-role-pip IS a badge, so it earns 11px by becoming a real eyebrow: 500, tracked, caps.
   .tmpl-tab is a TAB (§6 sub-navigation) and takes the Nav item rung, 14/500 at 0.02em — it was
   12/500, which is the "shrinking a control below its prose" the v1.6 note calls out by name.
   Scoped through .modal so these beat the earlier component rules on specificity. */
.modal .tmpl-cat,.modal .tmpl-card-meta,.modal .tmpl-vis-badge{font-size:12px;font-weight:var(--weight-body,400)}
.modal .tmpl-role-pip{font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase}
.modal .tmpl-tab{font-size:14px;font-weight:500;letter-spacing:.02em}

/* Getting Started step badges. font-weight:700 is not a weight Figtree ships (§3: 300/400/500/
   600), so it synthesised — the numerals rendered heavier and softer than every other numeral in
   the app. 500 is the label weight and the one the badge wanted. The tick that replaces the
   numeral on a completed step was 14px, off the §9 scale, and now matches its 16px siblings. */
.tut-step-num{font-weight:500}
.tut-step-num svg{width:16px;height:16px}
/* .tut-step-num .tut-tick is (0,2,0) and outranked the `.tut-step-num svg` rule above, so the
   completed-step tick stayed at 14px. Named explicitly to land it on 16. */
.tut-step-num .tut-tick{width:16px;height:16px}

/* =====================================================================================
   Table of contents column — one ground, one type contract, and the mark at its foot.
   ===================================================================================== */

/* ONE COLOUR at every width. The column was `--bg` (canvas, L* 0) on desktop and
   `surface2` (#171717, L* 7.74) once it became the mobile drawer — the same component
   reading as two different surfaces depending on screen size. It takes `surface2`
   throughout: a structural tone under §2, and the one the drawer already had. A drawer
   floating over the step table needs a ground to stay readable, so matching downward to
   transparent was never available — the column comes up to meet the drawer instead.
   Specificity has to clear `:root[data-mode] .sidebar{background:var(--bg)!important}`. */
/* SPLIT, owner call 2026-08-14 — the note above unified rail and drawer onto surface2 because a
   drawer floating over the step table needs a ground. That is true OF THE DRAWER and not of the
   rail: they are different objects. A rail is structure and takes the canvas like every other
   structural surface; a drawer is a floating panel over content and keeps its own ground.
   Unifying them upward made the whole left column read a rung lighter than the app for the sake
   of a state that only exists below 1100.
   It also cost the selected state: .workflow-link.active is surface2, the exact colour the rail
   had become, so the active workflow was invisible against its own background. On canvas it
   reads again — which is why the fix is one change, not two.

   SPLIT WITHDRAWN, owner call 2026-08-14 (g330). The drawer goes to canvas too, so the
   table of contents is one colour at every width. The ground the split was protecting is
   not actually needed: the drawer opens over a scrim (.toc-backdrop), and the scrim is what
   separates it from the content behind — a fill of its own was belt and braces.
   The selected-state argument applies at BOTH ends, and only the rail half had been fixed:
   below 1100 .workflow-link.active (surface2) sat on a surface2 drawer, so the selected
   workflow was invisible there — measured identical backgrounds, the one open finding from
   this screen's audit. Dropping the drawer to canvas closes it, and the split collapses
   back to a single rule. */
:root .app-shell .sidebar,:root[data-mode] .app-shell .sidebar{background:var(--bg)!important}

/* The footer is an EYEBROW and now spells it correctly: 11px earns its place only as
   tracked uppercase at weight 500 (§3 — "below [12px] only tracked uppercase survives").
   It was 11/400 at .14em: right shape, wrong weight, and tracking a notch wide of the
   0.12em the rung specifies. */
.sidebar-footer{font-weight:500;letter-spacing:.12em}

/* The mark sits with the version, per the brand book's own sanctioned use of the bare
   mark "for headers and documents". It is the stacked AXD-over-SOP monogram — the kit has
   no horizontal lockup, and the standalone wordmark treatments were retired deliberately,
   so this is the mark the brand actually has. Two files rather than a filter: the glyph is
   a bitmap composited from real family letterforms, and inverting it with CSS would smear
   the strokes it was built to keep exact. */
.sf-brand{display:flex;align-items:center;gap:10px;min-width:0}
.sf-mark{height:24px;width:auto;flex:0 0 auto;display:block}
.sf-mark-light{display:none}
:root[data-mode="light"] .sf-mark-dark{display:none}
:root[data-mode="light"] .sf-mark-light{display:block}
.sf-ver{min-width:0;overflow:hidden;text-overflow:ellipsis}
/* Mark left, version right — the mark carries the name, so the text no longer repeats it.
   "AXD SOP" beside a mark that already reads AXD SOP said it twice. */
.sf-brand{justify-content:space-between;width:100%;gap:12px}
.sf-ver{flex:0 1 auto;text-align:right}

/* Contents column typography, completing the §3 pass (tracking and case, not just size/weight).
   .section-title is a group LABEL over a list of workflows — the eyebrow role exactly. It sat at
   11/500 with 0.02em and sentence case, which is 11px without the two things that earn 11px:
   §3 allows the rung ONLY as tracked uppercase ("below [12px] only tracked uppercase survives"),
   because lowercase stops being legible there. Now 0.12em and uppercase.
   .brand small is the company/subtitle line under the SOP name — lowercase prose, so it belongs
   on Meta (12/400), not on an 11px rung it cannot satisfy. It was 11/400: too small AND the
   wrong weight for its size.
   .brand h1 is a Section heading, whose row in the table specifies tracking 0; it carried the
   Nav item's 0.02em. */
/* !important because two earlier .section-title rules pin letter-spacing:.02em with it, and
   !important beats specificity outright — the eyebrow needs its .12em to stay legible in caps. */
.sidebar .section-title{font-size:11px;font-weight:500;letter-spacing:.12em!important;text-transform:uppercase}
.sidebar .brand-text small{font-size:12px;font-weight:var(--weight-body,400);letter-spacing:normal}
.sidebar .brand-text h1{letter-spacing:normal}

/* Breathing room between the search field and the scrolling workflow list.
   #tocWorkflowList is the scroll container and had padding-top:0, while .sidebar-body carries
   padding-bottom:30px — so the list had a generous gap at the BOTTOM as you scroll and almost
   none at the top, where rows scrolled up to within 14px of the search field and read as
   crowding it. The 14px was .search-wrap's margin, i.e. fixed chrome, not daylight for the
   moving content. Matching the bottom: 24px of static separation below the search plus 8px of
   scroll padding inside the container, so the first row clears the field whether the list is at
   rest or scrolled. !important because an earlier .search-wrap rule pins margin with it. */
.sidebar #tocWorkflowList{padding-top:8px}

/* ==========================================================================
   TOC vertical rhythm — one block, on the rulebook scale (owner-approved 2026-08-14)
   ==========================================================================
   Supersedes an alignment rule that used to live here. That rule pinned the search bar so the
   sidebar's first section rule met the workflow header's rule at y=203. It was correct when
   written and is now wrong: the workflow header was restructured (roles moved up, the action bar
   moved out), so it SHRANK and its rule sits at 153. Aligning to it again would only re-break the
   next time that header changes, so the sidebar now keeps its own rhythm and aligns to nothing.

   Every gap below is a rulebook token. Before this, the column measured 14 / 18 / 35 / 30 / 13 /
   24 — six numbers, none of them on the 8-12-16-20-24-32 scale.

     16  sidebar top padding          16  title -> search        32  search -> first section
     16  above the footer divider     16  below it               16  wordmark -> bottom edge

   THE DIVIDER was the complaint, and the cause was not the footer: .sidebar-body carried
   padding-bottom:30px, so the line sat 30px below the list and only 13px above the wordmark.
   Lopsided, which reads as "too much space" even though the total is unremarkable. Both sides are
   now 16 and the line sits centred in its own gap.

   LEFT PADDING STAYS 32 (owner call) while the other three sides go to 16. The deeper indent
   separates the list from the rail edge and is doing real work; making all four sides equal would
   shift every workflow row 8px left for symmetry that nobody asked to see.

   Scoped to the desktop rail. Narrower widths keep their own padding rules — the sidebar becomes
   an overlay drawer at <=1100 with different needs, and past regressions here came from desktop
   spacing leaking into mobile. */
@media(min-width:1101px){
  .sidebar{padding:16px 16px 16px 32px!important}
  /* the brand's own bottom margin stacks on top of the search's margin-top, which is what made
     this gap 22 instead of 16 — the two spacings were being added, not chosen between */
  .sidebar .brand{margin-bottom:0!important}
  .sidebar .search-wrap{margin-top:16px!important;margin-bottom:16px!important}
  .sidebar #tocWorkflowList{padding-top:0!important}
  .sidebar .sidebar-body{padding-bottom:16px!important}
  .sidebar .sidebar-footer{padding-top:16px!important;padding-bottom:0!important}
}
/* The footer mark is now the HORIZONTAL wordmark, not the stacked monogram — the stack read
   too small in a 240px column because its cap height is half the image height. The wordmark is
   ~4.2:1, so at 26px tall it gives the letters roughly the same cap height the stack had at 40px
   while fitting the row. Width is left to the aspect ratio. */
.sf-mark{height:26px}

/* Workflow view: the diagram's own title and role filter are now duplicates.
   The shared header (wfHeadHTML) carries the workflow title and the Workflow Roles filter for
   BOTH views, so the copies WorkflowView draws inside the diagram repeat them a few hundred
   pixels lower — the title twice, the role chips twice. Hidden here rather than edited out of
   the diagram, which is deliberately out of scope: this is a presentational override on the
   host page, so the component keeps working standalone (exports, print, the public view) where
   its own title and filter are the only ones present.
   .wfv-zoomslot lives in the same titlebar and is NOT a duplicate, so the title button is
   hidden individually rather than the whole bar — zoom and Fit stay. */
.diagram-view #wfvPanel .wfv-titlebtn{display:none}
.diagram-view #wfvPanel .wfv-filter{display:none}

/* ==========================================================================
   Step-table icons on the rulebook grid (2026-08-14)
   ==========================================================================
   The rulebook is explicit: 24x24 grid, monoline 1.5px stroke, round caps and joins,
   currentColor always, and "sizes snap to 16 / 20 / 24 (16 inline and rows)... never free-size
   an icon." Its adoption notes have carried the instruction since 2026-08-06 and it was never
   done: "SOP Builder: restroke chip icons from 2.2 to 1.5."

   Measured before this block, in the step table:
     .svg-icon svg   14px, stroke 2.45   (base 2.2, then two !important overrides: 2, then 2.45)
     .edit-pen svg   18px, stroke 2.1
     .cmt-btn svg    16px, stroke 1.8
   Three sizes, none of them 16/20/24, and three different strokes — so the row's icons did not
   even agree with each other, let alone the grid. Consolidated here rather than edited in place
   because the strokes came from scattered !important rules that would each need unpicking.

   COLOUR IS DELIBERATELY UNTOUCHED. The tinted step icons are sanctioned: "Category-tinted icon
   chips (SOP's workflow legend) are dataviz, not UI iconography: the icon stays currentColor;
   the tint lives on the chip." Ours already work that way — the hex sits on .svg-icon and the
   glyph inherits — so the tint stays and only geometry changes.

   Scoped to .table-wrap so the DIAGRAM keeps its own icon rendering, which is out of scope. */
.wf-view .table-wrap .svg-icon svg,
.wf-view .table-wrap .edit-pen svg,
.wf-view .table-wrap .cmt-btn svg{
  width:16px!important;height:16px!important;
  stroke-width:1.5!important;
  stroke-linecap:round;stroke-linejoin:round;fill:none;
}

/* ==========================================================================
   Workflow screen, owner pass 2026-08-14
   ==========================================================================
   1 · COLUMN HEADERS WERE NOT CLIPPED — they were TOP-ALIGNED. Measured: text 14px tall sitting
   1px from the top of a 40px cell with 26px of empty space below it, because th inherits
   vertical-align:top from the `th,td` base rule that exists so step CONTENT aligns to the top of
   a tall row. A label pinned to the ceiling of its own cell reads as cut off, and it reads worse
   once the header pins on scroll and that edge becomes the boundary of the viewport. Headers are
   labels, not content: they centre. */
.wf-view .table-wrap>table>thead>tr>th{vertical-align:middle}

/* 2 · THE TITLE PENCIL WAS STRANDED — exactly 20px from the end of the title and 20px from the
   view toggle, so it belonged to neither and read as a third, orphaned control. Pulled in tight
   to the title it edits. gap-icon (8px) is the token for "an icon and its label". */
.wf-title-row .wf-title-edit{margin-left:-12px}
.wf-title-row h2{margin-right:0}
/* …and the pull-in above could not land, because the h2 was `flex:1 1 auto` and STRETCHED to
   the full row. The pencil was 8px from the heading's BOX and ~340px from its last letter,
   which is what read as stranded — the token was right, the box was wrong. Shrink the title
   to its text and the 8px falls where it was always meant to. The selector then keeps the
   right edge by auto margin, which is alignment rather than a second sibling gap (§5
   invariant 4: siblings share one gap). Owner call 2026-08-14. */
.wf-title-row h2{flex:0 1 auto}
.wf-title-row>.modeseg{margin-left:auto}

/* 3 · SUBPATH SELECTOR SHARES THE ACTION BAR'S ROW when there is width for it (owner call), and
   drops to its own line only when there is not. g318 gave it 24px of separation ABOVE, which was
   right while it sat on its own row; on a shared row that separation becomes the row gap and the
   two controls sit level instead. Below 1100 it returns to its own line with the mobile 16.
   The wrap is content-driven — flex-wrap plus a min-width on the actions — so the break happens
   when the two genuinely stop fitting, not at a width guessed in advance. */
@media(min-width:1101px){
  /* 2026-08-14: the margins were 24 above / 12 below — 24 is the page-gutter token, not a gap,
     so this block sat off the spacing grid. It belongs WITH the table it controls, so the pair
     is gap-group above and gap-item below, which is also exactly the 2:1 that invariant 5 asks
     for ("groups separate more than their members: at least twice the gap inside them"). */
  .wf-view .subpath-actions-row{display:flex;align-items:center;justify-content:space-between;
    gap:16px;flex-wrap:wrap;margin:32px 0 16px}
  .wf-view .subpath-actions-row>.subpath-controls{margin:0!important;flex:0 0 auto}
  .wf-view .subpath-actions-row>.head-actions{margin:0!important;flex:1 1 auto;
    display:flex;justify-content:flex-end;gap:12px;min-width:520px}
  /* The SUBPATHS eyebrow is dropped whenever the row is SHARED, at every width, not behind a
     viewport breakpoint. A breakpoint was the wrong lever: the row measures 915px at BOTH 1280
     and 1440 because the content column is max-width-bound, not viewport-bound, so a rule keyed
     to viewport hid the label exactly where it was not needed and showed it where it was. With
     the label out the row needs 863 of 915 and holds from 1101 up.
     The label is the cheapest thing in the row to lose: the two options name themselves, and
     they sit directly above the table they filter. */
  .wf-view .subpath-actions-row>.subpath-controls>.spc-label{display:none}
}

/* ==========================================================================
   The step table gets a backing (owner call 2026-08-14)
   ==========================================================================
   Every other block on this screen sits on a rounded surface — the Workflow Roles panel is
   surface at radius-card — while the table sat directly on the page with square corners and no
   ground. With the TOC column dropping to canvas, that left the table as the only sharp-edged,
   unbacked region on the screen, which is what read as unfinished.
   Same recipe as the roles panel, so they are visibly the same kind of object: `surface` fill,
   radius-card, no border — the edge is the tone step, per §containers.
   The sticky header already paints --paper (#121212), the same value, so it continues to sit
   flush against this ground instead of banding across it. */
@media(min-width:1101px){
  /* The card's own inset. It read 16 at the top (via the header cell's padding) against 8 at the
     bottom, so the card was visibly heavier under the last row than over the first — the
     asymmetry the owner caught. inset-card is 20, so the BOTTOM takes 20 here and the TOP takes
     20 through the header cell's padding-top below. Horizontal stays 16 for now: raising it to
     20 narrows the table by 8px, which the g333 column widths were measured against — that is a
     re-measure, not a one-line change. Owner call 2026-08-14. */
  .wf-view>.table-wrap{
    background:var(--color-surface)!important;
    border-radius:var(--radius-card)!important;
    padding:0 16px 20px!important;
  }
  /* the pinned header must round with the container, or its square fill paints over the corners */
  .wf-view>.table-wrap>table>thead>tr>th:first-child{border-top-left-radius:var(--radius-card)}
  .wf-view>.table-wrap>table>thead>tr>th:last-child{border-top-right-radius:var(--radius-card)}
}

/* =====================================================================================
   THE STEP CARD EXISTS AT EVERY WIDTH (g397)
   =====================================================================================
   Owner: on mobile the workflow steps "not being in a big card is a problem matching with the
   desktop view." Measured, and he is right — the card was scoped to `min-width:1101px`, so
   BOTH tablet and phone fell back to the base rule at ~L5315
   (`background:transparent!important; border-radius:0!important`) and the steps sat loose on the
   canvas with only their dividers. At 390: table-wrap background rgba(0,0,0,0), radius 0,
   padding 0, against rgb(18,18,18) / 16px / `0 16px 20px` at 1440. Not a narrow-screen variant
   of the desktop design — the absence of it.

   THE INSET MOVES TO THE CARD, and this is the part worth reading. g391 gave each stacked step
   row `padding:16px` because at that point the row was the only box there was. Hand the card its
   own 16px inset now and the two stack: 32px of gutter each side on a 354px-wide pane, a fifth
   of the screen spent twice on the same edge. §5 invariant 4 says spacing is set once, so the
   CONTAINER takes the horizontal inset (inset-card, 16 at mobile) and the row keeps only its
   vertical rhythm. Dividers then run the full inner width of the card, which is what makes a
   stack of rows read as one card rather than as separated tiles.

   721-1100 keeps the desktop padding exactly: the thead is still rendered there and supplies the
   top inset through its own cell padding, the same trick the >=1101 block relies on. Below 720
   the thead is display:none, so the card has to supply its own top edge. */
@media (max-width:1100px){
  .wf-view>.table-wrap{
    background:var(--color-surface)!important;
    border-radius:var(--radius-card)!important;
    padding:0 16px 20px!important}
}
@media (max-width:720px){
  .wf-view>.table-wrap{padding:4px 16px 8px!important}
  /* horizontal inset now belongs to the card above; the row keeps the vertical rhythm only */
  .page-card.wf-view tr.step-row{padding:16px 0!important}
}

/* ==========================================================================
   The column headers take the Workflow Roles voice (owner call 2026-08-14)
   ==========================================================================
   The header row read as faint chrome — 12px/500 at --color-faint — while every other
   label that introduces a block on this screen ("Workflow Roles") is 14px/400 in --text.
   The owner wants the columns to carry that same voice so they read as a heading FOR the
   table rather than as a watermark above it. Same face, size, weight and colour; the CASE
   stays uppercase, which is the owner's call on top of the match.
   The SIZE stays on the 12px rung it already held (the scale is 28/16/14/12/11, no 13).
   Matching the heading's 14px was built and looked at, and it read too heavy for a column
   label: the emphasis wanted here comes from weight and colour, which are the cheaper
   signals. So the weight and colour take the Workflow Roles value and the size does not.
   Tracking holds at .08em — the badge rung, not the 0.12em eyebrow rung — because that is
   what keeps small uppercase legible.
   Padding was 0 against a fixed 40px height; it becomes a real 16px inset top and bottom.
   Scoped to the desktop table: at 721-1100 the columns are already tight (see the
   721-1024 overlap block above) and any rise there would cost width the cells need. */
/* g391 — ONE HEADER SPEC, AT EVERY WIDTH. The block above is the g326 reasoning and it applied
   only here, inside min-width:1101px. The consequence was never intended: the same column header
   shipped two different specs depending on the window.
       >=1101   12px / 400 / 0.08em
       <=1100   12px / 500 / 0.12em     (rule at ~L5033, from the --text-th tokens)
   Measured on staging at 1440 and 768. Rulebook §3 puts Table header at 500 / 0.12em, and §6 is
   explicit that 0.08em is the BADGE rung and "is not the 0.12em eyebrow tracking" — so the wide
   window was the one off-book, and the narrow breakpoints the owner asked me to look at were
   right all along. Owner's call, 2026-08-16: desktop moves to match.
   WHAT THIS REVERSES: g326's weight-400 decision, taken so the header would read in the same
   voice as "Workflow Roles". The SIZE (12px, off the book's 11) and the COLOUR (--text, not
   --color-faint) are g326's and both stay — only weight and tracking rejoin the book. If the
   render loses the heading quality g326 was after, the lever to pull is colour, not weight. */
@media(min-width:1101px){
  .wf-view>.table-wrap>table>thead>tr>th{
    font-size:12px!important;
    font-weight:500!important;
    letter-spacing:.12em!important;
    text-transform:uppercase!important;
    color:var(--text)!important;
    /* These two are DIFFERENT jobs and so take different tokens: padding-top is the card's own
       top inset (inset-card 20, matching the 20 now under the last row), padding-bottom is the
       gap from the column label to the first row (gap-item 16). They were both 16, which is what
       made the card read heavier at the bottom than the top. Owner call 2026-08-14. */
    padding-top:20px!important;
    padding-bottom:16px!important;
    height:auto!important;
  }
}

/* ==========================================================================
   Pane-aware step-table columns when Axi is open (owner call 2026-08-14)
   ==========================================================================
   The column widths above are keyed to the VIEWPORT (>=1101, widened again at >=1025), but
   the table does not live in the viewport — it lives in .main, and opening the Axi panel
   takes 380px out of that pane. At 1440 with the panel open the pane is 745px, and because
   the table is table-layout:fixed with every column but Description pinned, Description is
   the only one that can give: it collapsed to 51px and rendered its text ONE CHARACTER PER
   LINE, narrower than every other column on screen. This is the same failure the SUBPATHS
   eyebrow note further up describes — a rule keyed to viewport cannot see a pane-width change.

   Keyed to .app-shell.chat-open rather than a container query: the app already sets that
   class, and container-type on .main would introduce containment on the scroll container the
   sticky header depends on. Capped at 1560 because above it the pane is wide enough without
   help (1560 - 315 rail - 380 panel = 865 and climbing).

   The numbers are measured, not guessed. ICONS holds 128px because that is exactly what the
   widest icons cell needs — a swept comparison put 100px at THREE overlapping rows, where the
   "Step n" chip painted over the icon tiles, and 116/128/132 at zero. FOLLOWING STEP keeps the
   documented 140 (chip + two tools). Only Step / Role / Step Type give ground, and Description
   goes 51 -> 160. */
@media(min-width:1101px) and (max-width:1560px){
  .app-shell.chat-open .wf-view>.table-wrap>table th:nth-child(1),
  .app-shell.chat-open .wf-view>.table-wrap>table td:nth-child(1){width:44px!important}
  .app-shell.chat-open .wf-view>.table-wrap>table th:nth-child(2),
  .app-shell.chat-open .wf-view>.table-wrap>table td:nth-child(2){width:84px!important}
  .app-shell.chat-open .wf-view>.table-wrap>table th:nth-child(3),
  .app-shell.chat-open .wf-view>.table-wrap>table td:nth-child(3){width:96px!important}
  .app-shell.chat-open .wf-view>.table-wrap>table th:nth-child(5),
  .app-shell.chat-open .wf-view>.table-wrap>table td:nth-child(5){width:128px!important}
  .app-shell.chat-open .wf-view>.table-wrap>table th:nth-child(6),
  .app-shell.chat-open .wf-view>.table-wrap>table td:nth-child(6){width:140px!important}
  .app-shell.chat-open .wf-view>.table-wrap>table th:nth-child(7),
  .app-shell.chat-open .wf-view>.table-wrap>table td:nth-child(7){width:140px!important}
}

/* ==========================================================================
   TOC rows: the hover/selection tile is inset, not edge-to-edge (g334)
   ==========================================================================
   §5 invariant 7: "Hover surfaces never touch. Spacing is measured between PAINTED extents,
   not layout boxes... Full-width row and menu hovers never paint edge to edge: the hover tile
   is inset by hover-inset (4px) on all sides, so two adjacent highlighted rows keep 8px of
   untouched surface between them."
   These rows measured ZERO apart: 36px boxes with no gap and a fill painting the whole box,
   so a hovered row and the selected row directly above it met with no daylight at all and
   read as one two-tone block.
   The fill moves onto a ::before tile inset 4px. Doing it with margin would have worked too
   and cost 4px per row — about 120px of extra scroll on a 30-workflow TOC — for no visual
   gain; the tile costs nothing because the row box, the label position and the list height
   are all unchanged. Measured after: box 36, painted tile 28, 8px between extents.
   isolation:isolate keeps the z-index:-1 tile inside the row's own stacking context, so it
   paints behind the label but in front of the rail rather than disappearing behind it.
   The three older rules painted the fill on the element itself and each carried !important,
   so the states are neutralised here at equal specificity rather than out-shouted. */
/* THE BOX BELONGS TO SELECTION, AND HOVER ANSWERS IN TYPE (g437)
   =======================================================================================
   Owner: "the box isn't evenly spaced around that hover… it's just a little bit too tight. We
   can only have one box highlighted at a time, so I don't think it's an issue of any overlap."
   Measured, he is describing a real number: the row is 36px, the tile was inset 4px on every
   side, and the label inside it is 22px — so the box cleared the text by 3px top and bottom.
   That 4px inset was not arbitrary. It existed because HOVER painted a box too, and a hovered
   row sitting directly above the selected one met it with no daylight at all (§5 invariant 7 —
   hover surfaces never touch). The inset bought that daylight at the cost of a cramped box.
   Retiring the hover box removes the constraint at its root rather than tuning around it: with
   only ONE painted box on screen, ever, there is nothing for it to touch, so it can fill the row.
   inset 0 takes the box 28 -> 36 and the text now clears it by 7px top and bottom, evenly.
   Hover moves to the treatment the settings lists and the rail already use (g433/g434): pointing
   at the list fades every row and lifts the one under the cursor. Same gesture, same reading,
   across every list in the app — and the selected row holds its box and its ink throughout,
   because selection answers "where am I", not "where am I pointing".
   The 2px between rows is what keeps a full-bleed box from meeting its neighbours' rows, and it
   is the same 2px the settings rail already puts between its own. */
.workflow-link,
.workflow-link:hover,
.workflow-link.active{background:transparent!important;position:relative;isolation:isolate}
.workflow-link+.workflow-link{margin-top:2px}
.workflow-link::before{
  content:'';position:absolute;inset:0;border-radius:var(--radius-control);
  background:transparent;z-index:-1;
  transition:background .15s ease}
.workflow-link.active::before{background:var(--accent)}
/* !important throughout, and not for convenience. Two pre-reskin rules still own this colour at
   that weight — `.workflow-link{color:var(--text-soft)!important}` and
   `.workflow-link:hover{color:var(--text)!important}` — and `!important` beats specificity, so
   an ID selector loses to a bare class here. Measured before matching their weight: every row
   read rgb(242,242,242) with the fade rule live and matching, ΔL* 0.00. */
@media (hover:hover){
  #tocWorkflowList:hover .workflow-link{color:var(--color-faint)!important}
  #tocWorkflowList:hover .workflow-link .workflow-number{color:var(--color-faint)!important}
  #tocWorkflowList .workflow-link:hover{color:var(--color-ink)!important}
  #tocWorkflowList .workflow-link:hover .workflow-number{color:var(--color-muted)!important}
  /* selection is not hover: it keeps its box and its ink while you point elsewhere */
  #tocWorkflowList:hover .workflow-link.active,
  #tocWorkflowList:hover .workflow-link.active .workflow-number{color:var(--text)!important}
}

/* ==========================================================================
   Phone: the title's edit pencil stops stretching (owner call 2026-08-14)
   ==========================================================================
   At 375 the heading takes 327 of the row's 339, so the pencil cannot share the line and
   wraps — that part is unavoidable and not worth wrapping a 24px display title to avoid.
   What was wrong is that it wrapped as a FULL-WIDTH button (measured 339px), so its glyph
   sat dead-centre under the title looking like a stray mark belonging to nothing.
   It becomes content-sized and left-aligned, so it reads as an action under the heading it
   edits. The -12px pull-in is dropped here: that trick exists to cancel the row's 20px gap
   on ONE line, and on a wrapped line it just shoves the icon off the text's left edge. */
@media (max-width:720px){
  .wf-title-row .wf-title-edit{flex:0 0 auto!important;width:44px!important;margin-left:0!important}
}

/* ==========================================================================
   Axi chat: the wordmark lockup, and the model control in the composer (g340)
   ==========================================================================
   THE LOCKUP. The header set "Axi" in Figtree beside the mark, so the product's own name was
   the one thing on screen NOT drawn in its own letterforms. The kit ships axi-wordmark for
   exactly this — "app headers, anywhere compact" — and the letters are drawn shapes, not a
   font, so it has to be art rather than type. Masked with currentColor like the glyph, so it
   inherits its slot's colour and needs no light/dark variant. Ratio is the file's own 490:224;
   height drives width so the letterforms can never be stretched, which the kit forbids. */
.axi-wordmark{
  display:block;height:15px;width:calc(15px * (490 / 224));
  background-color:currentColor;
  -webkit-mask:url(../img/axi-wordmark.png) left center/contain no-repeat;
          mask:url(../img/axi-wordmark.png) left center/contain no-repeat;
}
.chat-head-name{display:flex;flex-direction:column;gap:3px;min-width:0}

/* THE MODEL CONTROL. Axi EA carries the model picker in the composer rather than behind
   settings, and this line was already printing the live model — prettyModelLabel(cfg.model) —
   as dead text. It becomes the control: same information, now the thing you press, wired to
   the edit-ai action the toolbar's posture pill already uses, so there is no second sheet to
   build or keep in step. Skinned like Axi EA's .composer-model: the `control` fill, no border
   — a fill and a hairline together would be off the tier ladder — and the ink text it had. */
.composer-meta{justify-content:flex-start!important}
button.chat-prov{
  display:inline-flex;align-items:center;gap:6px;
  min-height:32px;padding:0 10px;
  border:0;border-radius:var(--radius-control);
  background:var(--color-control);color:var(--color-muted);
  font-size:12px;font-family:inherit;cursor:pointer;max-width:100%;
}
button.chat-prov:hover{background:var(--color-hover);color:var(--color-ink)}

/* ==========================================================================
   Axi chat: the lockup goes horizontal, the composer takes Axi EA's geometry (g341)
   ==========================================================================
   THE LOCKUP. Owner call: glyph, then the AXI wordmark, then "SOP assistant" — one line,
   left to right, instead of the name stacked over its label. The head row has the width for
   it, and read across it names the product before it qualifies it. Baseline alignment rather
   than centre, so the wordmark's flat feet and the label's baseline sit on one line; the
   label drops to 12px (Meta) so the lockup keeps a clear first and second voice. */
.chat-head-name{flex-direction:row!important;align-items:baseline!important;gap:8px!important}
.chat-head-name .chat-sub{font-size:12px;color:var(--color-muted);white-space:nowrap}

/* THE COMPOSER. Ported from Axi EA's, but only the parts that belong here.
   TAKEN: the control size. Axi EA runs its composer buttons at --control-lg (44px); ours were
   34px, which is also exactly what the mobile audit flagged as under the touch rung. So one
   change closes both — the composer grows to 56 to seat them (44 + 6 + 6 padding).
   LEFT BEHIND, deliberately: the backdrop-filter glass, because translucency is a standing
   owner rejection for SURFACES in this app and is not to be reintroduced on them — the composer
   is a surface, so this still holds (the scrim exception added 2026-08-16 does not reach it);
   and the box-shadow, because on
   a true-black canvas nothing is darker than the ground, so a shadow is invisible and the
   book separates floating surfaces by tone and hairline instead. Copying either would have
   been importing Axi EA's context rather than its idea. */
.composer{min-height:56px}
.composer-plus,.composer-send{width:44px!important;height:44px!important;border-radius:var(--radius-control)!important}
.composer textarea{min-height:44px}

/* ==========================================================================
   Axi chat: the composer follows Axi EA, and the lockup takes a type rung (g342)
   ==========================================================================
   1 · THE MODEL CONTROL SITS IN THE INPUT. It was a line under the composer; in Axi EA it is
   in the composer itself, so switching model never leaves the field. It now sits between the
   + and the textarea and truncates rather than pushing the input around — the model name is
   the useful half, so the box shrinks before the field does.
   2 · THE TEXT CENTRES. The textarea was align-self:flex-end, so a single line sat on the
   floor of a 68px box with the air above it. Centred, it reads as one row of controls.
   3 · SEND LOSES ITS HAIRLINE. Axi EA's send carries no border, and ours had one from the
   tier work in g333 — correct for a standalone Secondary, wrong for a control sitting inside
   a field that already has its own edge. A box inside a box.
   4 · HOVER STOPS SPREADING. `.composer:hover .composer-plus` and `.composer:hover
   .composer-send` lit BOTH controls whenever the pointer was anywhere in the box — including
   on one of them — so hovering Send also lit +. Hover is feedback for the thing under the
   pointer; each control now answers only for itself. The rest state stays as it was, so
   nothing is hidden until hover. */
.composer textarea{align-self:center!important}
.composer-send{border:0!important}
.composer:hover .composer-plus,.composer:hover .composer-send{opacity:inherit}
.composer-plus,.composer-send{opacity:1}
.composer .chat-prov{
  flex:0 1 auto;min-width:0;max-width:132px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  min-height:32px;align-self:center;
}

/* 5 · THE LOCKUP TAKES A RUNG. "SOP assistant" was 12px against a 15px-tall wordmark, which
   read as small and sat on no rung the book names for a label of this weight. The label goes
   to Body (14px) and the wordmark to 16px so its caps lead the label rather than matching it,
   and the three parts align on a shared centreline instead of a baseline the artwork cannot
   honestly sit on. Worth recording: the wordmark is ARTWORK, so the type scale does not bind
   its height — what the scale governs is the text beside it, which is now on 14. */
.chat-head-name{align-items:center!important;gap:10px!important}
.chat-head-name .chat-sub{font-size:14px!important}
.axi-wordmark{height:16px;width:calc(16px * (490 / 224))}

/* ==========================================================================
   Axi chat: the lockup takes the rung above (g344)
   ==========================================================================
   The panel's own name was the smallest thing in its own header. g342 had already moved
   "SOP assistant" 12 → 14 and the wordmark 15 → 16, but 14 is Body — the rung the step
   DESCRIPTIONS are set in — so the product's name read at the same size as body copy in a
   table, and the glyph beside it drew at 17px against a 20px+ icon elsewhere in the app.
   THE LABEL takes 16, which is the next rung up and the last one below Display; there is no
   fractional rung between 14 and 16 to hedge with, so this is the move or nothing.
   THE WORDMARK goes to 20. It is ARTWORK, so the type scale does not bind its height — what
   the scale governs is the text beside it. 20 against a 16px label keeps the same proportion
   the 16/14 pair had, so the name still leads the label rather than matching it.
   THE SLOT follows the mark: 30 → 36 keeps the glyph's optical inset unchanged (17-in-30 and
   20-in-36 are the same ratio), so nothing needs re-centring. */
.chat-head-name .chat-sub{font-size:16px!important}
.axi-wordmark{height:20px;width:calc(20px * (490 / 224))}
.chat-badge{width:36px;height:36px}
.chat-badge .ai-glyph{width:20px;height:20px}

/* ==========================================================================
   The SOP name becomes a control (g345)
   ==========================================================================
   Shaped after AXD Quote's `.topbar__entity` — an outline pill carrying a name and a
   chevron-updown, whose click opens the switcher. Ported: the geometry, the truncation
   discipline (min-width:0 on the name so a long title ellipses instead of widening the bar) and
   the chevron. NOT ported: Quote's `background: var(--color-surface)` under the border. A fill
   AND a hairline at once is off this book's tier ladder — Secondary carries a hairline, Primary
   carries a solid fill — the same rule that took the fill off Send in g342. Outline here means
   hairline only.
   The height is 34, not Quote's 36, because that is what every other control in this bar
   measures; matching Quote's number would have left the title a rung taller than the row it
   sits in. Under the phone rule the bar's controls go to 44 and this follows them.
   The chevron is the only part that never truncates: it is what says the name is pressable, so
   the name gives up width before the affordance does. */
.toolbar-sop-btn{
  display:inline-flex;align-items:center;gap:8px;
  flex:0 1 auto;min-width:0;max-width:100%;
  /* g349: was a hard 34, which only matched the bar above 1101. The bar's controls run on
     --ctl-h (36) with `@media (min-width:1101px) .toolbar .btn{min-height:34px}` over the top,
     so between 721 and 1100 this pill sat 2px shorter than everything beside it. It follows the
     same ladder now instead of guessing one number. */
  min-height:var(--ctl-h);padding:0 10px;
  border:1px solid var(--color-linestrong);border-radius:var(--radius-control);
  background:transparent;color:var(--color-ink);
  font:inherit;cursor:pointer;
  transition:background .14s,border-color .14s}
.toolbar-sop-btn:hover{background:var(--color-hover)}
.toolbar-sop-btn .toolbar-sop-title{flex:0 1 auto;min-width:0}
.sop-btn-cv{flex:0 0 auto;width:14px;height:14px;color:var(--color-muted)}
/* The phone touch rung. The bar's `.btn` controls take it from the @720 rule above, but this
   pill is not a `.btn` — measured at 390 it sat at 34 in a row of 44s, both under the touch
   target and a rung shorter than the row it belongs to. Same breakpoint, same number. */
@media (min-width:1101px){
  .toolbar-sop-btn{min-height:34px}
}
@media (max-width:720px){
  .toolbar-sop-btn{min-height:44px}
}
/* At the smallest width the name has nowhere to go. Measured at 320: .toolbar-toggles takes 184
   (Contents 44 + the four view icons 128) and the overflow menu 44, which leaves the title group
   44 — exactly the pill's own padding and chevron, and a title 0px wide. Forcing a min-width does
   NOT fix it: the pill then overflows a parent that has overflow:hidden, so it is clipped rather
   than laid out, and page-level scrollWidth still reports clean. That is the trap — the box looks
   fine and the name is gone.
   So below 360 the box comes off. The name was already invisible there before this change (a
   0-width span), and an EMPTY bordered pill is worse than no pill: it is a control that names
   nothing. The real fix is the approved two-bar mobile layout, which moves the view icons to a
   bottom bar and gives the title its own row — still an injected mock, still needs the owner's
   word. The Dashboard stays reachable at this width from Settings > Account, as it was. */
@media (max-width:360px){
  .toolbar-sop-btn{display:none}
}

/* ==========================================================================
   Axi chat: the model picker becomes an inline sheet, and the chip moves right (g343)
   ==========================================================================
   1 · THE SHEET. The chip opened the "AI & API key" MODAL, which closes the panel and buries a
   one-tap switch inside a form with a key field, a sub-provider filter and a data-handling essay.
   Axi EA opens a picker over the composer instead, so the model changes without leaving the field.
   It is a FLOATING surface, so it takes the floating ground the book gives them — --color-overlay
   (#212121) plus a linestrong hairline — and NOT a shadow: on a true-black canvas nothing is
   darker than the ground, so a shadow separates nothing. No backdrop-filter: translucency is a
   standing owner rejection for SURFACES, and the sheet is a surface, so it is opaque by design.
   (The 2026-08-16 scrim exception is scoped to `.modal-backdrop` alone and does not reach here.)
   2 · THE ENGAGED ROW IS A TONE STEP. The app's older dropdown (.cdd-opt.sel) marks its selection
   with a fill AND a border at once, which is off the tier ladder. This one steps the row up one
   tone (#212121 → #2A2A2A) and adds a check; hover reaches the same tone, so the check — not the
   fill — is what says "this is the one you are on", and a pointer passing over a row can never be
   mistaken for the selection.
   3 · 44px ROWS. The mobile audit's touch rung, met here rather than retrofitted later. */
.composer-models-bd{position:fixed;inset:0;z-index:40;background:transparent;border:0;padding:0;margin:0;cursor:default;display:block;width:100%}
.composer-models{
  position:absolute;right:14px;bottom:100%;margin-bottom:8px;z-index:50;
  width:min(320px,calc(100% - 28px));max-height:min(60vh,420px);overflow:auto;
  background:var(--color-overlay);border:1px solid var(--color-linestrong);
  border-radius:var(--radius-card);padding:8px 6px 6px}
.cm-title{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--color-faint);padding:0 10px 6px}
.cm-prov{font-size:12px;color:var(--color-muted);padding:8px 10px 2px}
.cm-row{
  display:flex;align-items:center;gap:8px;width:100%;min-height:44px;padding:0 10px;
  border:0;border-radius:var(--radius-control);background:transparent;color:var(--color-ink);
  font:inherit;font-size:14px;text-align:left;cursor:pointer}
.cm-row:hover,.cm-row.on{background:var(--color-overlay-hover)}
.cm-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cm-tier{flex:0 0 auto;font-size:12px;color:var(--color-faint)}
.cm-mk{flex:0 0 auto;width:16px;height:16px;display:inline-flex;visibility:hidden}
.cm-row.on .cm-mk{visibility:visible}
.cm-mk svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.cm-sep{height:1px;background:var(--color-line);margin:6px 10px}
.cm-settings{
  display:block;width:100%;min-height:44px;padding:0 10px;
  border:0;border-radius:var(--radius-control);background:transparent;color:var(--color-muted);
  font:inherit;font-size:14px;text-align:left;cursor:pointer}
.cm-settings:hover{background:var(--color-overlay-hover);color:var(--color-ink)}

/* 4 · THE CHIP MOVES RIGHT. g342 put it between the + and the field, which pushed the message —
   the thing you are actually writing — off the centre of the pill. In Axi EA the field owns the
   middle and the model sits with Send on the right, where the two "how this gets sent" controls
   read as one group. Owner call. The chevron says it opens something; it was a bare label before,
   which gave a control the look of a caption. */
.composer .chat-prov{margin-left:auto}
.chat-prov-txt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}

/* ==========================================================================
   The model chip is skinned from Axi EA's own, rung for rung (g346)
   ==========================================================================
   g343 moved it to the right and gave the message the middle — the layout half of the ask.
   This is the skin half, read off Axi EA's `.composer-model-chip` rather than approximated:

   FILL OUT, HAIRLINE IN. Ours carried `background: var(--color-control)` and `border:0`. Axi
   EA's is the exact inverse — transparent ground, `1px solid --line-strong` — and its own note
   says why the borderless version was rejected there: "the model read as loose text in the bar
   rather than a control you can press… the chip is the pressable thing, so it carries the
   defined edge, not its container."
   This is in tension with g342, which took the hairline OFF Send with "a box inside a box".
   Both are right, and the difference is what the control is: Send is an icon in a round slot
   that reads as a control without help, the chip is a run of text that does not. Axi EA landed
   on the same split. Following it, per the owner.

   THE CHEVRON COMES OFF. g343 added one because a bare label read as a caption. Axi EA solves
   that with the hairline instead and is explicit that the chip is text-only — "owner delta:
   text-only — no icon, no brand mark… just drops the <select> chevron". The border does the
   work the chevron was doing, so keeping both would be saying it twice.

   INK, NOT MUTED. It states the live model; that is not secondary information.
   RADIUS ONE TOKEN BELOW THE BAR, as Axi EA has it: the composer is --radius-control, so the
   chip is --radius-chip. A control nested in a container takes the tighter corner.
   HEIGHT INSET FROM THE BAR. Axi EA uses control-md inside a control-lg bar so the chip reads
   as an object placed IN the field rather than filling it. Ours is 36 in a 44 content box —
   the same relationship, on this app's numbers. It was 32, which was inset but off any rung.
   MAX-WIDTH 172, Axi EA's number, up from 132: the model name is the useful half. */
/* `.composer .chat-prov` (0,2,0), not `button.chat-prov` (0,1,1): g342 set min-height:32 and
   max-width:132 at the two-class weight, and the first pass of this block lost to it silently —
   the colours applied and the metrics did not. Same weight, later in the file, so this wins. */
.composer .chat-prov{
  min-height:36px;max-width:172px;padding:0 10px;
  border:1px solid var(--color-linestrong);border-radius:var(--radius-chip);
  background:transparent;color:var(--color-ink)}
button.chat-prov:hover{background:var(--color-hover);color:var(--color-ink)}
/* Open is a tone step, the same language the sheet's own engaged row speaks. */
.chat-prov.on{background:var(--color-hover)}

/* ==========================================================================
   Audit fixes — the two findings that survived triage (g347)
   ==========================================================================
   1 · THE CHIP HAD NO FLOOR. `flex:0 1 auto; min-width:0` let it shrink without limit, and
   because the textarea is `flex:1 1 auto` the chip lost every contested pixel first. Measured in
   a default-width panel at 1440: a 76px box holding "Gemini 2.5 Pro · Strong". It rendered as
   "Loc…" / "Gemi…" — a control whose entire job is to state the live model, stating nothing.
   The floor is 96px, which holds roughly "Gemini 2.5 P…" and is small enough to still fit at
   320 (measured: field 71px, no overflow). Dropping the tier suffix in app.js does most of the
   work; this stops the rest.
   2 · THE LOCKUP GAPS WERE 12 AND 10. g344 raised the wordmark to 20 and the label to 16 and
   left the gaps alone, so glyph->wordmark sat at 12 against wordmark->label at 10. Two gaps that
   close together read as a mistake rather than as grouping — too similar to signal hierarchy,
   too different to read as even. They go equal, which is what the g341 owner decision asks for:
   glyph, wordmark, label, one line, left to right, one lockup. */
/* 112 is measured, not chosen: the longest label the curated lists can produce is
   "Claude Opus 4.8" at 90px, plus the chip's 22px of padding. Every model name in the sheet
   therefore fits its chip without an ellipsis on tablet and desktop.
   Phones get 88 instead. At 320 the composer has 161px to split between the field and the chip
   once the +, Send and the gaps are paid for; a 112px chip would leave the message field 49px,
   and the field is the thing you are actually using. 88 keeps the field at 73 and clips the
   name by a few characters, which the tooltip and the sheet both cover. The real fix at that
   width is the approved two-bar mobile layout, which is still an unbuilt mock. */
.composer .chat-prov{min-width:88px}
@media (min-width:721px){
  .composer .chat-prov{min-width:112px}
}
.chat-head-title{gap:10px}

/* ==========================================================================
   The subpath filter stops wearing a Primary (g348)
   ==========================================================================
   "Show All / Main Path Only" marked its chosen option by handing it the `primary` CLASS —
   the action-family Primary variant — as a selection state. Three things wrong at once, and
   the book names all three:
     · §6 rule 3: "Primary takes no engaged state… A primary that is also a toggle is two
       ideas wearing one control."
     · The seven controls (v1.7.4): this is a Selector — "one of several — views, ranges,
       modes" — whose engaged state is "a thumb TRAVELS to the chosen option", not a fill on
       the option itself.
     · Consequence: the engaged filter rendered at the same overlay-hover #2A2A2A as Draft
       Steps, the committing action beside it, so a filter you have set and an action you are
       about to take read identically.
   The fix is to REUSE the Selector this app already has rather than restyle `.seg`: .modeseg
   (Table View / Workflow View) is the same shape — two mutually-exclusive options, a mode —
   and is already correct, thumb and all. One component, two users, and mobile and desktop
   stop diverging because there is now only one thing to diverge from.
   `.seg` is deliberately NOT restyled: its other two users are the AI Check groups, which
   mark selection with `.sel` on `.ai-scope-btn` and never touch `.primary`, and one of them
   mixes a standalone Toggle (Auto-run on Open) into the same row — a different control in
   the book's terms, and a different fix. Out of scope here rather than swept in.
   `.wf-title-row`-scoped modeseg rules (the auto margin, the <=720 hide) don't reach this
   instance, which lives in .subpath-controls — checked, not assumed. */
.subpath-controls>.modeseg{flex:0 0 auto}
/* THE OUTER TRACK COMES OFF. `.subpath-controls` was not a plain row — it was a home-grown
   segmented control in its own right: a --color-control track with 3px padding, transparent
   options, and an active option raised onto surface2 with a linestrong border. That is exactly
   the pattern the .modeseg block above describes retiring — "wrong track tone, a fill where the
   thumb belongs, and a line on an engaged state (v1.8 retired those entirely)" — and leaving it
   under a real Selector would nest a track inside a track.
   So the container returns to being a row: an eyebrow, then the Selector. Its
   `>.btn.small` rules are now inert (it has no .btn children any more) and are left in place
   rather than deleted, because they are reachable only through markup that no longer exists.
   THE ROW MUST WRAP, and the @720 layer forces `flex-wrap:nowrap!important`. That was safe when
   the control was two independent buttons that could break between themselves; a Selector is one
   inline-grid with nowrap labels and a hard minimum width, so at 320 it measured a right edge of
   352 inside a 302 parent — clipped rather than laid out, with page-level scrollWidth still
   reporting clean. Same false pass that hid the title pill. Wrapping drops the Selector under
   its eyebrow with its labels intact, rather than truncating "Main Path Only". */
.subpath-controls{
  background:transparent;border-radius:0;padding:0;gap:10px;width:auto}
@media (max-width:720px){
  .subpath-controls{flex-wrap:wrap!important}
}

/* =====================================================================================
   Save goes back to Secondary; the page keeps the primary (g349)
   =====================================================================================
   §15 rule 5, "one primary action per view". Two controls carried the primary fill at once:
   Save in the permanent chrome and Draft Steps on the page. Both were deliberate and neither
   knew about the other — the v1.8 pass promoted Save to primary and did not demote anything,
   so the screen has been spending its primary slot twice ever since.

   Save loses it, on the owner's call. The argument is the one the superseded comment already
   made and the v1.8 change never actually answered: a button parked in permanent chrome is
   present in EVERY view, so a primary Save spends every view's primary slot forever, including
   views where saving is not the thing to do. Draft Steps is the strong action of the workflow
   screen and keeps the white.

   Secondary per §6: transparent, `linestrong` hairline, `ink` text; `hover` fill on hover.
   Note it keeps INK text, not muted — Secondary is still an action you are meant to find, and
   muted would drop it to the Ghost tier alongside Undo/Redo/Settings, which is a demotion too
   far. The hairline is what separates it from those three.

   Geometry is untouched: 34px to match the bar, and the icon-only collapse below 1440 that it
   picked up in g344. This changes tier, not shape.

   Supersedes the primary block above (":6213"), which itself superseded three earlier Secondary
   overrides. Appended last, per this sheet's ordering convention. If Save is ever promoted
   again, demote Draft Steps in the same commit — that is the whole lesson of this one.
   ===================================================================================== */
.toolbar #saveBtn{
  background:transparent!important;
  color:var(--color-ink)!important;
  border:1px solid var(--color-linestrong)!important;
  box-shadow:none!important;
}
.toolbar #saveBtn:hover,.toolbar #saveBtn:focus-visible{
  background:var(--color-hover)!important;
  color:var(--color-ink)!important;
  border-color:var(--color-linestrong)!important;
}

/* =====================================================================================
   Axi panel: the lockup splits, the field's TEXT centres, the chip takes a mark (g350)
   =====================================================================================
   1 · "SOP ASSISTANT" GOES DEAD CENTRE, and Axi tightens to its glyph. Owner call. The three
   parts were one left-aligned run; now the identity (glyph + wordmark) holds the left and the
   descriptor sits on the panel's centreline.
   `1fr auto 1fr` rather than absolute positioning: with both side columns at 1fr they are
   equal by construction, so the middle column is genuinely centred on the PANEL — not centred
   in whatever space the two unequal side groups happen to leave. It also cannot overlap them
   the way an absolutely-positioned label would once the actions grow.
   The glyph-to-wordmark gap drops 10 -> 6 so "Axi" reads as attached to its mark rather than
   as the first item in a row; the two are one lockup now that the descriptor has left.
   min-width:0 on the side columns keeps a long descriptor from forcing the header wider. */
.chat-head{display:grid!important;grid-template-columns:1fr auto 1fr;align-items:center}
.chat-head>.chat-head-title{justify-self:start;min-width:0}
.chat-head>.chat-sub{justify-self:center;white-space:nowrap;font-size:16px;color:var(--color-muted)}
.chat-head>.chat-head-actions{justify-self:end;min-width:0}
.chat-head-title{gap:6px}

/* 2 · THE FIELD'S TEXT CENTRES — the thing the owner kept seeing and I kept mis-measuring.
   Every BOX shared a centreline at 857, which is what I checked and reported as aligned. The
   TEXT did not: the textarea was 44px tall with 6px padding and a 21px line, so a single line
   sat at the top of a 32px content box — 6 + 10.5 = 16.5 from the top against a box centre of
   22. Exactly the 5.5px the eye caught.
   The box now starts at ONE line's height (21 + 6 + 6 = 33) and `align-self:center` centres
   that box in the 44px row, which puts the line itself on the row's centreline. It still grows
   to max-height as you type, and growth stays bottom-anchored because the row is centred.
   Measure the TEXT next time, not the box. */
.composer textarea{min-height:33px!important}

/* 3 · THE CHIP LEADS WITH A MARK AND GIVES THE FIELD ITS WIDTH BACK. g347 put a 112px floor on
   it so the model name never ellipsed — correct for legibility, wrong for the panel: the owner's
   point is that the message field is what the width is FOR. The vendor mark carries the identity
   in 14px, so the label is free to truncate to nothing without the control going mute — you can
   always still see WHICH provider you are on. The floor therefore drops to the mark plus its
   padding, and the label takes whatever is left up to 172. */
.chat-prov .prov-mark{flex:0 0 auto;width:14px;height:14px;display:block}
/* Content-sized, not shrinkable. The chip was `flex:0 1 auto` against a textarea at `1 1 auto`,
   so the field grew into the free space and then the chip absorbed the shortfall — measured, it
   sat at 51px while its own content needed 79, which is what produced the "L…" stub. It now takes
   exactly its content width and the field absorbs the remainder.
   The 120px CAP is the other half. Measured in a 367px composer: 355 inner, less 88 for the + and
   Send and 30 of gaps, leaves 237 to split. At the common label the chip is 79 and the field 158;
   at the worst id this app can produce ("Haiku 4.5.20251001", 107px of text) an uncapped chip
   would take 149 and leave the field 88. Capped at 120 the field never drops below 117, and the
   long id truncates — which costs nothing, because the mark still names the vendor and the full
   id is one hover away. The field is what the width is for. */
.composer .chat-prov{flex:0 0 auto;min-width:0;max-width:120px;gap:6px}
/* The picker rows carry the same mark, so the sheet and the chip name the provider identically. */
.cm-row .prov-mark{flex:0 0 auto;width:16px;height:16px;color:var(--color-muted)}
.cm-row.on .prov-mark{color:var(--color-ink)}

/* 4 · THE SMALLEST PANEL DROPS WHAT THE MARK AND THE WORDMARK ALREADY COVER. Measured at 320,
   where the panel is 291 rather than the ~370 it gets on desktop:
     · the centred descriptor OVERLAPPED the close button (its right edge ran past the actions
       column). A centred label in a 1fr/auto/1fr grid cannot collide with the side columns while
       they have room — but at 291 they do not, and grid lets the auto column win. It goes; the
       glyph and the wordmark still say whose panel this is.
     · the chip's label left the message field 68px. Mark-only takes the chip to ~40 and gives
       the field back ~50 of those, which is the owner's whole point — the width is for the
       message. The provider is still named by its glyph, and the model is one tap away in the
       sheet, which is where the full id has always lived.
   360 is the threshold because 390 measured clean on both counts (no clash, field 138). */
@media (max-width:360px){
  .chat-head>.chat-sub{display:none}
  .composer .chat-prov .chat-prov-txt{display:none}
  .composer .chat-prov{max-width:none;padding:0 8px}
}

/* =====================================================================================
   Settings becomes a two-stage panel (g352)
   =====================================================================================
   Ported from Axi EA's settings drawer, which the owner has asked for twice: it opens as the
   nav column ALONE and widens to seat the detail beside it only once you choose something,
   collapsing again when you press the same row.

   Ours could never show that. Above 900px `settings-open` preselected 'axi', so the panel
   appeared with rail and detail already side by side — the partial state existed in the markup
   and was unreachable. Phones were already opening on the list, so the two lanes disagreed as
   well. app.js now opens with no category at any width, and re-picking the active row clears it.

   THE RAIL IS WHAT MOVES, not the panel. The drawer keeps its locked 720px edge-anchored shape
   (that decision is settled and not reopened here); the nav column goes from filling it to
   30% of it — 216/720, the rail's existing width expressed as the fraction it always was.

   BOTH ENDS ARE PERCENTAGES, and that is load-bearing rather than stylistic. flex-basis
   interpolates between two percentages; against `auto` — the value this rail had — it does not
   animate at all and the layout snaps. Axi EA hit exactly this and left the same note.

   The border-right only exists once there is something to its right; a full-width rail with a
   rule down its far edge is a line dividing nothing from nothing. */
/* SUPERSEDED BY g369, kept because the reasoning above still explains the two STATES — only the
   thing that moves between them changed. The owner reopened the locked-720 decision by name:
   "the settings pull-out needs to be a bit wider… it should not remain the same size."
   So the DRAWER animates its own width now, rail-only → open, and the rail holds a fixed width
   throughout instead of swinging 100% → 30%. That also makes the rail resizable in a way the
   percentage version could not be: a percentage of a fixed panel cannot be dragged to an
   absolute width without fighting the very transition that animates it.
   Both widths come from custom properties set inline by settingsPanelHTML (so they survive the
   panel being rebuilt on every category change) and stored per person in localStorage.
   The collapsed width takes whichever is larger — the rail, or 304px — so a dragged-narrow rail
   still leaves room for the title and the X, and a dragged-wide one is not cropped. */
@media (min-width:901px){
  .setpanel{
    /* g380: the collapsed panel IS the rail, nothing more. The old `max(rail,304)` floor was
       written when the rail sat at the panel's LEFT edge, where the extra width fell harmlessly
       to its right. Mirrored at g377 the rail is pinned RIGHT, so that floor became a 64px strip
       of empty ground down the rail's left side — the owner's "when collapsed, isn't left-aligned
       and isn't the right size". Both symptoms, one cause. The header fits 240 comfortably: title
       61 + close 36 + padding 40 + gap 12 = 149. */
    width:var(--set-rail,240px);
    transition:width 320ms cubic-bezier(.2,.85,.22,1)}
  /* g432 — STAGE TWO IS GONE. `.setpanel.has-cat{width:var(--set-w,920px)}` lived here and was
     the whole second stage: picking a category widened the panel from the rail to 920 (600 in
     practice) to show a `.set-detail` column. Every destination is an overlay now, so that column
     had nothing left to hold — and for the sole-item categories, which is nearly all of them, it
     had been widening onto an EMPTY pane behind the centred dialog since g423.
     The panel is one width at every row. `has-cat` stays on the element because the rail still
     uses it to light the row you are in; it just no longer changes the panel's size.
     The width transition below is kept deliberately — the panel still animates open and shut. */
  /* Dragging fights a 320ms ease on every frame; suspend it for the duration of the drag. */
  .setpanel.is-resizing{transition:none}
  .set-rail{
    width:var(--set-rail,280px);flex:0 0 auto;border-right-color:transparent;
    transition:border-right-color 160ms linear}
  /* No second column to divide from, so the rail's trailing hairline goes with it. */
  /* THE PANE IS REVEALED, NOT REFLOWED (g370). Left as `flex:1` it re-measures on every frame of
     the width transition, so the drawer's content visibly re-wrapped itself all the way open —
     text reflowing while the panel slides is the thing that made the motion read as cheap even
     once it was animating at all. Pinned to the width it will HAVE when open, it is simply
     clipped by the panel's own `overflow:hidden` edge and uncovered as that edge travels: the
     layout inside is settled before the first frame. It tracks the drag too, since both halves
     of the sum are the same custom properties the handles write to.
     Scoped to desktop — below 901 the panel is a 100vw sheet and the pane must fill it. */
  .set-main{flex:0 0 auto;width:calc(var(--set-w,920px) - var(--set-rail,280px) - 1px)}
  /* The detail pane is present but has nothing to show until a destination is chosen; hiding it
     keeps its empty footer and version stamp from sitting in the collapsed state. The rail
     divider goes with it — a handle between a column and nothing is not a divider.
     `is-collapsing` holds both on screen for the length of the slide so the pane is clipped away
     by the closing edge instead of blinking out a frame before it moves. */
  /* g432 — unconditional now, not `:not(.has-cat)`. There is no state in which a desktop panel
     shows a second column, so the pane and its divider are simply not part of the desktop panel.
     `is-collapsing` still exempts them for the length of the closing slide. */
  .setpanel:not(.is-collapsing) .set-main,
  .setpanel:not(.is-collapsing) .set-split{display:none}
}
/* Reduced motion gets the same two states without the slide. */
@media (min-width:901px) and (prefers-reduced-motion:reduce){
  .setpanel{transition:none}
}

/* =====================================================================================
   Settings: the type gets a second rung, and the rows get their hover tile (g353)
   =====================================================================================
   The standing brief called the panel "typographically flat — 119 elements at 14px" and named
   it the biggest visible win. Measured now, the flatness is not general: `.set-title` is on 28,
   `.cat-h` on 16, `.sr-t` on 14 and `.set-ver` on 11, all correct. It is exactly TWO elements,
   and they are the same element twice — the descriptive second line under a title:
     · `.sr-desc`, the row description ("Choose a provider and paste your key…")
     · `.cat-sum`, the category summary ("Provider, memory and context.")
   Both sat on 14 — Body, the rung their own TITLES use — so each pair read as one undifferentiated
   block and the panel had no second voice anywhere. They are Meta and take 12, which is a rung
   the scale already has and this panel was not using at all.
   Nothing else moves. The other 14s are titles and rail labels, and Body is where they belong;
   shrinking them would have made the panel quieter, not clearer.

   THE ROWS GET A HOVER TILE, INSET. §5 invariant 7: "hovers never paint edge to edge: the hover
   tile is inset by hover-inset (4px) on all sides, so two adjacent highlighted rows keep 8px of
   untouched surface between them." `.sr` painted NOTHING on hover — it only brightened its text,
   which is the rule for a SELECTABLE control (§6 rule 2). These rows are not selections: they
   open a modal or drill into a screen and nothing about them persists. They are actions, and an
   action shows a fill.
   The fill rides a ::before inset 4px rather than margins, so the row's own box and rhythm are
   untouched and only the paint moves in. Children are lifted to z-index 1 so the tile sits under
   the text rather than over it.

   AND THE DIVIDERS GO. `.sr~.sr` drew a 1px rule between every pair. Once each row carries its
   own tile the rule is doing a job that is already done, and v1.7's line language reserves a line
   for STATE, not for structure — the brief anticipated this exact swap. */
.setpanel .cat-sum{font-size:12px;line-height:17px}
.setpanel .sr-desc{font-size:12px;line-height:17px}
.setpanel .sr::before{
  content:"";position:absolute;inset:4px;border-radius:var(--radius-control);
  background:transparent;pointer-events:none;z-index:0;transition:background .15s ease}
/* g434 — the tile goes, and the type does the work, matching the rail above and the category
   overlays. The `::before` box stays in the sheet (it is what `.is-open` still paints its
   selection with) but it no longer fills on hover.
   Note the comment above: the row dividers were REMOVED at the time this tile was introduced, on
   the reasoning that a tile makes a rule redundant. Taking the tile away does not bring them back
   — v1.7 reserves a line for state, and these rows are separated by their own rhythm plus the
   fade, which is exactly what the category overlays do. */
.setpanel .sr>*{position:relative;z-index:1}
.setpanel .sr~.sr{border-top:0}
@media (hover:hover){
  .setpanel .set-root:hover .sr .sr-t,
  .setpanel .set-detail:hover .sr .sr-t{color:var(--color-faint)}
  .setpanel .set-root:hover .sr .sr-desc,
  .setpanel .set-root:hover .sr .sr-ic,
  .setpanel .set-root:hover .sr .sr-chev,
  .setpanel .set-detail:hover .sr .sr-desc{color:color-mix(in srgb,var(--color-faint) 62%,var(--color-surface))}
  .setpanel .set-root .sr:hover .sr-t,
  .setpanel .set-detail .sr:hover .sr-t{color:var(--color-ink)}
  .setpanel .set-root .sr:hover .sr-desc,
  .setpanel .set-root .sr:hover .sr-ic,
  .setpanel .set-root .sr:hover .sr-chev,
  .setpanel .set-detail .sr:hover .sr-desc{color:var(--color-muted)}
  /* Destructive rows hold their tier through a neighbour's hover, as in the overlays. */
  .setpanel .set-root:hover .sr.danger .sr-t,
  .setpanel .set-detail:hover .sr.danger .sr-t{color:color-mix(in srgb,var(--color-danger) 70%,var(--color-surface))}
  .setpanel .set-root .sr.danger:hover .sr-t,
  .setpanel .set-detail .sr.danger:hover .sr-t{color:var(--color-danger)}
}

/* =====================================================================================
   Settings: the close button goes to the top RIGHT on phones too (g354)
   =====================================================================================
   Owner: the X sat in the wrong place on mobile. Measured at 390 it was at left 132 — 258px
   short of the right edge — because the header wraps there (title 102 + gap + a 190px minimum
   search + gap + the button exceeds the 350px of content width), the search drops to its own
   full-width second line, and row one is left as [title][X] with nothing between them. On
   desktop the search sits between the two and carries `margin-left:auto`, which is what puts
   the X against the right edge; once the search leaves the row, nothing does.

   The fix is the rule the stylesheet already SAYS exists. The note above `.set-search` reads
   "`.set-x` keeps its own auto for the mobile row, where `.set-search` goes full-width on its
   own line" — but only the `.set-search` half was ever written. This is the other half.

   Scoped to the wrapped layout on purpose: giving `.set-x` an auto margin at desktop width is
   the exact arrangement that note warns against, where the free space lands between the title
   and the search instead of before them. Same 720px breakpoint as the `.set-search` rule it
   pairs with, so the two can never disagree about when the header has wrapped.
   With a category open the row is [back][title][X] and the auto margin still resolves the X
   to the right edge. */
/* g369 UNSCOPES IT. The 720px scope existed for one reason — at desktop the search field sat
   between title and X carrying the auto margin, and a second auto on `.set-x` would have split
   the free space between title and search instead of banking it all before the X. The search is
   gone, so nothing carries that margin at any width and nothing competes for it either. The row
   is [title][X] everywhere now, and the X needs the auto everywhere to reach the right edge. */
.set-x{margin-left:auto}

/* =====================================================================================
   Settings on phones becomes an accordion, and the back chevron goes (g355)
   =====================================================================================
   Owner: "we have both a back button and an X — we don't need the back chevron."
   True, but the two were not duplicates: the X closed the panel and the chevron stepped back
   from a category to the list. Measured with a category open at 390, the rail AND the root list
   were both display:none, so the chevron was the ONLY route back — deleting it alone would have
   stranded you one level deep, able to reach a different category only by closing Settings and
   reopening it.
   So the NEED goes rather than the control. The list no longer gets replaced: it stays, and the
   chosen category expands beneath its own row, collapsing when you press it again — the same
   toggle desktop took in g352, and what the owner described wanting in the first place. With
   nothing to come back from, the chevron is deleted honestly and the X is the panel's only
   chrome control at every width.

   The rail stays hidden here (it is the desktop affordance) and `.set-detail` is now only for
   SEARCH results, which legitimately do replace the list — a result set spans categories, so
   there is no single row for it to live under. */
/* =====================================================================================
   The phone settings list goes flat (g357) — the accordion from g355 is retired
   =====================================================================================
   Measured: 8 categories holding 23 destinations, three of which hold exactly one. An accordion
   over that much content asks you to open one category at a time and remember which is open, to
   save a scroll that was never expensive — 23 rows is about two screens, unremarkable for a
   settings screen. Owner: "I can only open one at a time, that doesn't really make sense."
   Everything is on the list now: one tap from anywhere, no expand state to track or get wrong.
   The `.set-acc` rules and the chevron rotation from g355 are gone with the markup that used
   them — nothing emits `.set-acc` any more.

   The group heading is the Eyebrow rung the rest of the app already uses for section labels
   (11px/500, 0.12em, uppercase, faint), so it reads as a label ABOVE content rather than as
   another row you could press. */
.set-group{
  padding:18px 10px 6px;
  font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;
  color:var(--color-faint)}
.set-group:first-child{padding-top:6px}

/* =====================================================================================
   Settings sub-pages render inside the panel (g358)
   =====================================================================================
   The dialog shell stops being a dialog when it is docked. `#modalRoot` is physically moved into
   `.set-detail`, so the same markup every sub-page already emits — `.modal-backdrop > .modal` —
   has to stop behaving like a floating overlay and start behaving like the pane's content:
     · the backdrop drops `position:fixed`, its scrim and its z-index. Fixed positioning would
       escape the panel entirely and re-centre on the viewport, and a scrim inside a panel would
       dim the panel it lives in.
     · the modal drops its width cap, its radius, its border and its viewport-height ceiling. It
       is not a card on a page any more; it IS the pane, and it scrolls with it.
   Both are scoped to `body.settings-docked` so an ordinary modal — one opened from anywhere
   else in the app, or from Settings on a phone — is completely untouched. */
body.settings-docked .set-detail{padding:0;overflow:auto;min-height:0}
body.settings-docked .set-detail #modalRoot{display:block;height:100%}
/* g424 — and the blur comes off with the background. `background:none` does NOT cancel a
   `backdrop-filter`; the filter is a separate property and would survive, so the docked pane
   would sit there quietly blurring the settings panel it is a part of. Docked, this element is
   not a scrim at all — it is the pane's own content box — and a pane does not blur its parent. */
body.settings-docked .set-detail .modal-backdrop{
  position:static;inset:auto;display:block;background:none;padding:0;z-index:auto;
  -webkit-backdrop-filter:none;backdrop-filter:none}
/* !important on radius/border/shadow only: `.modal` carries `border-radius:var(--radius-modal)
   !important` and a shadow from the modal-shape layer, which a docked pane must out-rank. The
   rest needs no force. */
body.settings-docked .set-detail .modal{
  width:100%;max-width:none;max-height:none;overflow:visible;
  background:transparent;
  border:0!important;border-radius:0!important;box-shadow:none!important;
  padding:18px 20px 24px}
/* The sub-page titles itself; the panel's own header already says "Settings", so the h2 takes
   the Section rung rather than competing as a second Display. */
/* =====================================================================================
   Docked sub-pages read as pane screens, not dialogs (g366)
   =====================================================================================
   Owner: the sub-pages still feel like overlays. They do, and it is not ten problems — all ten
   emit the same `.modal-backdrop > .modal` shell, so the dialog-ness is a few shared traits.
   Measured across all ten docked screens, two of them do most of the work.

   1 · EVERY TITLE RENDERED AT 28px, the Display rung, inside a 503px pane whose own header
   already says "Settings". A dialog earns a Display title because it arrives over the page with
   no other context; a pane screen sits under a header and beside a rail that both say where you
   are, so it takes the Section rung.
   The g359 rule for this EXISTED AND LOST: `.modal h2` carries `font-size:28px !important` from
   the reskin layer, and !important beats specificity however deep the selector goes. Measured
   28px on all ten screens with the docked rule provably matching. It needs the same weapon.

   2 · EVERY FORM WAS TWO COLUMNS. `.form-grid` is `1fr 1fr`, drawn for an 820px dialog; in the
   pane that is two 224px columns, narrower than the controls want. One column at pane width,
   which also turns the `.wide` spans into no-ops rather than exceptions.

   Neither is per-screen, which is why they were worth finding before touching any individual
   screen. What remains after these is genuinely bespoke and gets its own pass. */
/* 12, not 14 (g399). This is every docked sub-page's title, and 14 is not a spacing token — §5
   gives stack-heading 12 for a heading and the content it introduces, and that value is one of
   the micro tokens the book says never changes with screen size. Found on the Context Library,
   fixed for all ten panes at once because they all emit this same h2. */
body.settings-docked .set-detail .modal>h2{font-size:16px!important;font-weight:500;margin:0 0 12px}
body.settings-docked .set-detail .form-grid{grid-template-columns:1fr}
/* Long screens scroll the PANE, never themselves — one scrollbar, at the drawer's edge. */
body.settings-docked .set-detail .modal{overflow:visible!important;max-height:none!important}

/* =====================================================================================
   Appearance: the three findings that survived the audit (g367)
   =====================================================================================
   1 · THE MODE SELECTOR WORE A FILL AND A LINE AT ONCE. `.theme-mode-btn.sel` measured
   `surface2` fill PLUS a `linestrong` border — the exact pattern g348 retired on the subpath
   filter, and the tier ladder's plainest rule: a control sits on one rung or the other, never
   half of each. v1.8 also retired lines on engaged states outright. The fill IS the engaged
   signal; the border was saying it twice.

   2 · THE BRAND SWATCH MARKED SELECTION WITH A LINE. `.brand-none` selected carried a 1px INK
   outline. Selection is "a fill plus ink text, never a colour" and never a line; on the panel's
   #121212 ground the fill has somewhere to go, so it takes one.

   3 · THE LOGO DROP-ZONES WERE DASHED. Measured: the only dashed borders in the app. A dashed
   edge is a convention borrowed from elsewhere, and this system separates by tone and states by
   a solid hairline. It becomes a solid `line` hairline over a `surface2` well, which says
   "something goes here" in the language the rest of the app already speaks. */
.theme-mode-btn.sel{border-color:transparent!important}
.brand-none.sel,.brand-none[aria-pressed="true"]{
  border-color:transparent!important;background:var(--color-overlay-hover)!important;color:var(--color-ink)!important}
.logo-drop{border-style:solid;border-color:var(--color-line);background:var(--color-surface2)}
.logo-drop:hover{border-color:var(--color-linestrong)}
/* Its action row sits at the foot of the pane, not floated over a page. */
/* =====================================================================================
   Phones: title and actions on top, the view tabs at the foot (g397)
   =====================================================================================
   THE TWO BARS ARE SWAPPED, on the owner's call 2026-08-16. g362 had it the other way and this
   supersedes it; the g362 reasoning is kept below rather than deleted, because it was sound and
   whoever reads this next deserves to know what was traded away.

   Now:
     WHERE YOU ARE   · row 1, the workflow title with its version chip and AI indicator
     WHAT YOU DO     · row 2, the six actions
     ...content...
     WHERE YOU GO    · the five view tabs, at the foot, in the thumb zone

   WHAT g362 ARGUED, and what changed. g362 picked title -> tabs -> content -> actions from a
   three-way render, on two grounds: the screen should read "where you are, where you go, what
   you do", and the actions belong under the thumb. The first still holds and is why the TITLE
   did not move — only the two bars traded places, which is exactly what was asked for. The
   second is what the owner has revised: on a workflow screen you switch VIEW far more often than
   you undo or save, so the thumb zone is better spent on the thing you actually reach for. A
   bottom tab bar is also the platform convention for exactly this control, which g362's own note
   half-conceded by calling these Tabs.

   HOW IT IS ASSEMBLED. All three bars are fixed and `.app-shell` is padded clear of them, which
   is what lets the toolbar's own children be re-used in three different places without touching
   the markup:
     · `.toolbar` BECOMES the top action row — six equal columns, `display:contents` on its two
       wrappers so their buttons are direct grid items.
     · `.toolbar-title` is lifted out to the very top as its own fixed row.
     · `.viewseg` is lifted out and pinned to the FOOT.
   An earlier attempt pinned only the tabs and padded `.app-shell` — but the toolbar lives INSIDE
   `.app-shell`, so the padding pushed it down beneath the fixed tabs and the title row vanished
   entirely. Either all of them are fixed or none are.

   THE SAFE-AREA INSET MOVES WITH THE TABS. It was on the action bar because the action bar was
   the thing sitting on the home indicator; now the tabs are, so the padding follows them. Left
   behind, it would have padded the top bar for a notch it is nowhere near and let the tabs sit
   under the indicator.

   HOW IT IS ASSEMBLED. All three bars are fixed and `.app-shell` is padded clear of them, which
   is what lets the toolbar's own children be re-used in three different places without touching
   the markup:
     · `.toolbar` BECOMES the bottom action bar — six equal columns, `display:contents` on its
       two wrappers so their buttons are direct grid items.
     · `.toolbar-title` is lifted out to the top as its own fixed row.
     · `.viewseg` is lifted out to sit under it.
   An earlier attempt pinned only the tabs and padded `.app-shell` — but the toolbar lives INSIDE
   `.app-shell`, so the padding pushed it down beneath the fixed tabs and the title row vanished
   entirely. Either all of them are fixed or none are.

   COLOUR: all three bars are `canvas` (#000000). The action bar's old `surface` #121212 went at
   g391 — it was the only lit surface on a black screen, and the note that defended it cited a
   settings panel that had since moved to black itself.

   Both bars speak the Tab language (g391): engaged is ink plus a 2px ink underline, never a
   fill, and the underline seats on the BOTTOM edge of the control in both bars — the owner's
   rule that a tab bar's engaged edge is its bottom edge whichever side the content sits on. That
   rule is why the swap needed no change to either engaged state. */
@media (max-width:900px){
  /* --- the toolbar becomes the TOP action row, under the title --- */
  .toolbar{
    position:fixed!important;left:0;right:0;top:48px;bottom:auto;z-index:300;
    display:grid!important;grid-template-columns:repeat(6,1fr);align-items:center;
    /* min-height too: the toolbar carries one from the desktop layer, which held the bar at 60
       while the content padding below was set for 56 — a 4px strip where the last row of content
       slid under the bar. Both are pinned so the two numbers cannot drift apart. */
    height:56px!important;min-height:56px!important;padding:0!important;margin:0!important;
    /* g391: was `surface` (#121212). The comment above still says that matches "the same ground
       the settings panel now sits on" — it did at g362, and then the settings panel moved to
       `--bg` black and this was never brought along. Measured on staging: bar rgb(18,18,18)
       against a sidebar, Axi panel and settings panel all at rgb(0,0,0), so the one piece of
       chrome the thumb rests on was the only lit surface on the screen. It is app chrome, not a
       container, and chrome sits on the canvas with a single hairline doing the separating —
       which the border-top below already provides. */
    background:var(--color-canvas)!important;
    border-bottom:1px solid var(--color-line)!important;border-top:0!important}
  .toolbar-toggles,.toolbar-actions{display:contents}
  .toolbar .tb-more{display:none!important}
  /* FULL-HEIGHT COLUMNS, not 44px chips floating in a 56px bar (g391). Two things follow.
     The engaged underline needs an edge to sit on, and a 44px button left it hovering 6px above
     the bar's own bottom edge while the view tabs 52px up sit flush on theirs — the two bars
     would have disagreed by 6px. And the tap target grows 44 -> 56, which is the rung §5 asks
     for on touch rather than the bare minimum. */
  .toolbar-toggles>.btn,.toolbar-actions>.btn{
    display:inline-flex!important;align-items:center;justify-content:center;
    width:100%!important;min-width:0!important;height:100%!important;min-height:44px!important;
    padding:0!important;margin:0!important;aspect-ratio:auto!important;border-radius:0!important;
    background:transparent!important;border-color:transparent!important;box-shadow:none!important}
  .toolbar-toggles>.btn>span:not(.tog-ico),.toolbar-actions>.btn>span:not(.tog-ico){display:none!important}
  /* Save names itself: `.toolbar #saveBtn` carries its Secondary skin at ID specificity, which a
     class selector cannot reach. In a uniform icon bar it is a Ghost like the rest; above 900,
     where the row is mixed again, it keeps the hairline. */
  .toolbar #saveBtn{
    background:transparent!important;border-color:transparent!important;box-shadow:none!important}
  .toolbar #saveBtn:hover{background:var(--color-hover)!important;border-color:transparent!important}
  /* 22px to match the tabs. At 16px the undo glyph's arrowhead and curved tail read as two
     separate marks rather than one arrow. */
  .toolbar .tog-ico{width:22px;height:22px}
  .toolbar .tog-ico svg{width:22px!important;height:22px!important;display:block}

  /* --- row 1: the title, at the top, on canvas --- */
  .toolbar-title{
    position:fixed;left:0;right:0;top:0;z-index:300;
    display:flex;align-items:center;gap:8px;min-width:0;
    height:48px;padding:0 12px;margin:0;
    background:var(--color-canvas);border:0}
  .toolbar-sop-btn{border:0!important;padding:0!important;min-height:0!important;flex:0 1 auto;min-width:0}
  .toolbar-sop-btn .sop-btn-cv{display:none}
  .toolbar-sop-title{font-size:14px}
  .toolbar-title .content-ver,.toolbar-title .ai-posture{display:inline-flex!important;flex:0 0 auto}

  /* --- the five view tabs, pinned to the foot, also on canvas --- */
  .toolbar .viewseg{
    position:fixed;left:0;right:0;bottom:0;top:auto;z-index:300;
    display:grid;grid-template-columns:repeat(5,1fr);align-items:center;
    width:auto;height:52px;margin:0;padding:0;gap:0;
    background:var(--color-canvas);
    border:0;border-top:1px solid var(--color-line);
    padding-bottom:env(safe-area-inset-bottom,0px)}
  .toolbar .viewseg button{
    position:relative;display:inline-flex!important;align-items:center;justify-content:center;
    width:100%!important;height:100%!important;min-width:0!important;
    padding:0!important;border:0!important;border-radius:0!important;
    background:transparent!important;color:var(--color-muted)!important}
  .toolbar .viewseg .vs-lbl{display:none}
  .toolbar .viewseg .vs-ico{width:22px;height:22px}
  .toolbar .viewseg .vs-ico svg{width:22px;height:22px;display:block}
  .toolbar .viewseg button.on{color:var(--color-ink)!important}
  .toolbar .viewseg button::after{
    content:"";position:absolute;left:50%;transform:translateX(-50%);
    top:auto;bottom:0;width:22px;height:2px;background:transparent;border-radius:0}
  .toolbar .viewseg button.on::after{background:var(--color-ink)}
  .toolbar .viewseg button:hover::after{background:transparent}
  .toolbar .viewseg button.on:hover::after{background:var(--color-ink)}
  /* the count badge would collide with a 22px icon in a fifth-of-the-width column; the number
     returns at desktop width, where the tab carries its label. */
  .toolbar .viewseg .nav-badge{display:none}

  /* content clears all three bars: 48 title + 56 actions above, 52 tabs below */
  .app-shell{
    padding-top:104px;
    padding-bottom:calc(52px + env(safe-area-inset-bottom,0px))}

  /* =====================================================================================
     THE BOTTOM BAR IS TABS, NOT TOGGLES (g391) — supersedes the v1.8 Toggle call below
     =====================================================================================
     The block further up this file (search "v1.8 names this control a TOGGLE") gives the panel
     buttons the Toggle recipe: engaged takes a `control` fill and ink text, with measured ΔL*
     reasoning behind the exact rung. That reasoning is sound and it is retired here for the
     BOTTOM BAR ONLY, on the owner's call.

     Why it was wrong in this bar specifically. A bar of six equal columns pinned to the bottom
     edge is a tab bar; it reads as one, and every other tab bar in this app already speaks the
     text tier — the five view tabs 52px above it take an ink underline on their bottom edge
     (see the .viewseg rules above) and nothing else. Two tab bars stacked on one screen were
     answering "which one is on?" in two different languages, one with a filled 10px-radius
     chip and one with a 2px line. Measured before this change: `toggle-toc` engaged at
     rgb(28,28,28) with a 10px radius and `::after` content:none, i.e. no line at all.

     The underline seats on the BOTTOM edge, matching the row above it. That is a deliberate
     departure from the "underline meets the content" reasoning the view tabs were built on —
     for a bar at the foot of the screen the content is above, so that logic would put the line
     on top. The owner's call is that a tab bar's engaged edge is its bottom edge whichever way
     the content sits, because that is what a tab bar looks like. Consistency between the two
     bars beats the derivation.

     Rulebook §6 supports the tier swap: the text tier "carries no box at any point" and its
     engaged state "is an ink underline, not an outline… the right choice wherever a selection
     should read as WHERE YOU ARE rather than WHAT YOU PRESSED". A panel that is open is where
     you are. */
  .toolbar .btn{position:relative}
  .app-shell:not(.toc-collapsed) .toolbar [data-action="toggle-toc"],
  .app-shell:not(.toc-collapsed) .toolbar [data-action="toggle-toc"]:hover,
  .app-shell.chat-open .toolbar [data-action="chat-toggle"],
  .app-shell.chat-open .toolbar [data-action="chat-toggle"]:hover,
  body.settings-open .toolbar [data-action="settings-open"],
  body.settings-open .toolbar [data-action="settings-open"]:hover{
    background:transparent!important;color:var(--color-ink)!important;box-shadow:none!important}
  /* 22px to match the view tabs' underline width exactly, so the two bars read as one system. */
  .toolbar-toggles>.btn::after,.toolbar-actions>.btn::after{
    content:"";position:absolute;left:50%;transform:translateX(-50%);
    top:auto;bottom:0;width:22px;height:2px;background:transparent;border-radius:0}
  .app-shell:not(.toc-collapsed) .toolbar [data-action="toggle-toc"]::after,
  .app-shell.chat-open .toolbar [data-action="chat-toggle"]::after,
  body.settings-open .toolbar [data-action="settings-open"]::after{
    background:var(--color-ink)}
  /* Hover still gives the tile — these are buttons you press, and only the ENGAGED one gives up
     its box (§6: hover says you could act; the tone says this is the state). */
}
/* Steps is the phone bar's own affordance — above 900 the sidebar is how you reach a workflow,
   and a fifth tab in the desktop rail would be a control with nothing to do. */
@media (min-width:901px){
  .viewseg .vs-steps{display:none!important}
}

/* =====================================================================================
   AI & API key: the essay folds away so the form can lead (g363)
   =====================================================================================
   Measured in the docked settings pane (503 x 781): 529px of prose stood between the heading
   and the first control — 68% of the pane — and the form needed 595px of scrolling to reach
   Save. The data-handling note alone was 327px.

   It is now a disclosure, closed by default. Nothing is cut: every word is one tap away, and
   the sentence that matters most on arrival — paste your key, the provider is detected — stays
   visible below it because that one is instruction rather than reference. A privacy essay is
   read once and consulted rarely; it should not be the first thing between you and the field
   you came to fill.

   Applied at every width, not just docked. The form is only reachable from Settings, and on a
   phone it is a full-screen sheet where the same wall was just as costly. */
.ai-data-details{margin:0 0 12px}
.ai-data-details>summary{
  display:flex;align-items:center;gap:8px;
  min-height:36px;padding:0 2px;
  color:var(--color-muted);font-size:12px;cursor:pointer;list-style:none}
.ai-data-details>summary::-webkit-details-marker{display:none}
/* g429 — THE CARET IS AN ICON NOW, NOT A BORDER TRICK. It used to be an 8x8 box carrying only
   a right and bottom border, rotated 45°. Three things were wrong with that and all three were
   visible: the ink sits on two EDGES of the box, so centring the box does not centre the mark —
   measured off the rendered pixels, the caret's ink centroid sat 2.67px ABOVE the pill's centre;
   the mitred border ends gave it square-cut tips while every other chevron in the app is the
   rulebook's 1.5 stroke with round caps; and §9 says not to hand-roll a look inline when the
   icon set has the mark. It is now the same SVG chevron the settings rows use, in a real span,
   so `align-items:center` centres the GLYPH and the caps match the set. */
.ai-data-details>summary:hover{color:var(--color-ink)}
.ai-data-details .ai-data-note{margin-top:8px}
/* It is a control, so it takes the touch rung on phones — 36 is the pointer height. */
@media (max-width:720px){
  .ai-data-details>summary{min-height:44px}
}
/* The note is three short paragraphs now rather than one block, so they need to breathe. */
.ai-data-note>p{margin:0 0 12px}   /* g429 — 10 was off the ramp; 12 is the paragraph rung */
.ai-data-note>p:last-child{margin-bottom:0}

/* =====================================================================================
   AI & API key: the colour comes off, and the disclosure becomes a control (g365)
   =====================================================================================
   Audited against rulebook v1.8, which is unambiguous about the one thing this screen was
   doing most: "Status colors never decorate, never brand." Measured, the screen carried
   EIGHTEEN chromatic elements.

   1 · THE GREEN BAR GOES. The data note wore `border-left:2px solid` in `success` at 35% —
   the badge recipe's border strength, used as an accent stripe on a paragraph that reports no
   success at all. It is an explanation. The note already steps from the panel's #121212 to
   surface2 #171717, and a tone step IS the separation the book asks for; the stripe was doing
   a second job nobody ordered. (The box has no other border: measured 0/0/0/2px, so removing
   the left edge leaves a clean tonal card, not a borderless one.)

   2 · THE TICKS GO NEUTRAL. Fourteen ticks, every one `success` green, on a list where the
   green never varies — colour that cannot differ carries no information, which is the
   definition of decoration. The tick GLYPH already says "yes" and the greyed-out rows already
   say "no". Ink for the affirmative, faint for the negative, and the meaning survives intact.

   3 · THE YELLOW COMES ON-TOKEN. `.warn` was #E8C68A, a bright buttery yellow that is not on
   the ladder; the book's dark `warning` is #AE9674, a muted ochre solved for 6.5:1. The `.ok`
   cases had already been remapped by the reskin layer and these were simply missed.

   4 · THE DISCLOSURE READS AS A CONTROL. Owner: make it a toggle rather than a bare caret. It
   takes the Secondary look the book gives a pressable non-primary — a hairline, control radius,
   control height — with the chevron moved to the RIGHT where a disclosure conventionally puts
   it, and the row filling the column so the whole thing is the hit target. */
.ai-data-note{border-left:0}

.ai-cap-summary .caps li.yes .ck{color:var(--color-ink)}

.ai-key-detect.warn,.ai-models-note.warn,.kb-status.idx{color:var(--color-warning)}

.ai-data-details>summary{
  justify-content:space-between;
  min-height:var(--ctl-h,36px);padding:0 12px;
  border:1px solid var(--color-linestrong);border-radius:var(--radius-control);
  color:var(--color-ink);font-size:14px;
  transition:background .14s,border-color .14s}
.ai-data-details>summary:hover{background:var(--color-hover)}
/* SYMMETRY (g429). The caret used to carry `margin-right:2px` on top of the pill's own 12px
   padding, so the text sat 12px off the left edge and the caret 14px off the right — measured,
   and the owner's "isn't spaced properly". The padding alone sets both insets now.
   `margin-left:auto` also went: `justify-content:space-between` already pushes the caret to the
   trailing edge, and having BOTH meant the auto margin absorbed all free space (it resolved to
   566px) while space-between had none left to distribute. One mechanism, not two. */
.ai-data-details>summary .ai-data-sum-t{min-width:0}
.ai-data-details>summary .ai-data-chev{
  flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:16px;height:16px;color:var(--color-muted);
  transition:transform .18s cubic-bezier(.2,.85,.22,1)}
.ai-data-details>summary .ai-data-chev svg{width:16px;height:16px;display:block}
.ai-data-details[open]>summary .ai-data-chev{transform:rotate(180deg)}
/* ONE SHAPE, ONE RADIUS (g429). Open, the pill and the note weld into a single block, and they
   were welding at two different radii: the pill's top corners were `--radius-control` (10) and
   the note's bottom corners `--radius-card` (16), so the assembly was tight at the top and round
   at the bottom. Worse, the note carried NO border while the pill carried 1px all round, so the
   outline simply stopped at the seam and the expanded body hung off an outlined pill with no
   enclosure of its own. That pair is the whole of "when it expands, it looks off".
   Resolved toward the CONTROL, because that is what the thing is when closed and expanding
   should not change what it is: control radius across the assembly, and the note continues the
   pill's own hairline on its three open sides (no top border — the seam is the pill's bottom
   edge, and doubling it would draw a 2px line where the book allows 1). */
.ai-data-details[open]>summary{border-bottom-left-radius:0;border-bottom-right-radius:0}
.ai-data-details[open] .ai-data-note{
  margin-top:0;border-top-left-radius:0;border-top-right-radius:0;
  border-bottom-left-radius:var(--radius-control);border-bottom-right-radius:var(--radius-control);
  border:1px solid var(--color-linestrong);border-top:0}

/* =====================================================================================
   EVERY SUB-PAGE'S ACTION ROW SITS AT THE FOOT OF THE PANE (g395)
   =====================================================================================
   Owner, having hit it on Project, then Appearance, then AI: "I'm seeing a pattern here — all
   of their cancel/save buttons should be pinned to the bottom of the page, just above the
   AXD SOP 3.0 tag." It is one pattern, so it gets one fix rather than three.

   WHY STICKY WAS NOT ENOUGH. `position:sticky; bottom:0` only does something once the content
   is TALLER than its scroll container. Project, Appearance and AI are all short pages, so there
   was no overflow, nothing to stick to, and the row simply sat wherever the content stopped —
   mid-pane, with dead space beneath it. Sticky was solving the long-page case and silently
   doing nothing in the common one.

   So the pane becomes a full-height column and the row is pushed down by `margin-top:auto`.
   Short page: the row lands at the foot, directly above `.set-foot` and its version tag, which
   is what was asked for. Long page: there is no free space for the auto margin to absorb, it
   computes to 0, and the sticky rule takes over exactly as before. The two mechanisms cover
   opposite cases and do not fight.

   SCOPED WITH :has() so panes with no action row keep plain block layout — turning every docked
   sub-page into a flex column would change the box model under ten screens to fix three, and the
   Analysis and Template Library panes have their own grids inside. Chrome's :has() is already
   load-bearing elsewhere in this sheet (the Selector thumb, the lone-dismiss row below). */
body.settings-docked .set-detail .modal-backdrop:has(.modal-actions){
  display:flex;flex-direction:column;min-height:100%}
body.settings-docked .set-detail .modal:has(.modal-actions){
  display:flex;flex-direction:column;flex:1 0 auto}
/* g410 — HEADROOM AT THE FOOT. Owner: the action row is "almost touching" the version bar and
   the page feels cut off at the bottom. Measured: the buttons cleared the bar by the row's 14px
   padding and nothing else, because g395 pinned the row flush to the pane floor with a -24px
   bottom margin. 14 is not a spacing token and, more to the point, it is the wrong KIND of
   value — it was the row's internal inset doing duty as the separation between two different
   things, the controls and the panel's own footer.
   The row now carries inset-card (20) above its buttons and gap-group's narrow rung (24) below
   them, so the gap to the version bar is a deliberate 24 rather than a leftover. Content also
   gets 24 before the row instead of 18, for the same reason: these are content groups.
   Reported on Project, Appearance and AI, and fixed here rather than three times because all
   three emit this same footer — Memory & Rulebook and Context Library, which the owner said feel
   fine, have no action row at all in a docked pane (g368 hides a lone Close). */
body.settings-docked .set-detail .modal-actions{
  /* the -24 cancels the modal's own bottom padding so the row's floor IS the pane's floor at
     every content length: without it a SHORT page (Project) stacked the two and measured 48
     while the sticky pages measured 24. The headroom lives in the row's padding, which is the
     one place both cases share. */
  /* g412 — and the content must RESERVE the row's space, not just be margined off it. A sticky
     row overlays what scrolls under it; `margin-top` only separates on a SHORT page where the row
     is in flow. On a long one (AI, Appearance) the last control ended ~8px under the hairline
     however large the margin was, because the margin was never the thing in the way. The modal
     below reserves 112 = the row's own 88 (20 + 44 + 24) plus 24 of clearance, so the last
     control stops clear of the row at every content length. */
  position:sticky;bottom:0;margin:24px -20px -24px;padding:20px 20px 24px;
  background:var(--color-surface);border-top:1px solid var(--color-line)}
body.settings-docked .set-detail .modal>.modal-actions{margin-top:auto}
/* g412 — THE SEPARATION GOES ON THE ELEMENT BEFORE THE ROW, because `margin-top:auto` above eats
   any margin the row declares. On a SHORT page auto expands and the gap looks generous; on a LONG
   one it computes to 0, so the last control sat 0-2px under the hairline no matter what g410 set.
   Measured: AI clearance 2, Appearance 0, Project 0 — with a 24px margin declared on the row the
   whole time. The margin was never the thing in the way.
   `nth-last-child(2)` is the last content block, the one immediately above the row, and it is the
   only place a value survives the auto margin. A flex `row-gap` would also work and was rejected:
   it would apply between EVERY child and change spacing the length of a form that is already on
   the contract. */
body.settings-docked .set-detail .modal>*:nth-last-child(2){margin-bottom:24px}

/* =====================================================================================
   Settings and its sub-pages drop to the darkest legal ground (g356)
   =====================================================================================
   Owner's call, made against a four-way render: the panel goes to `surface` #121212, the
   darkest rung the ladder offers below the floating tones, and every settings sub-page follows
   so drilling in never steps back up in tone.

   RAISED AND OVERRULED, recorded because it will look like an oversight later: #121212 is the
   exact tone of the step-table card behind it (measured rgb(18,18,18)), so the drawer no longer
   separates from the content by tone at all. Its `linestrong` left edge is now doing that job
   alone — which is why the hairline below is promoted from decoration to structure, and why the
   scrim matters. The owner chose this knowing it; the alternative on the table was #171717,
   which kept a 2.28 L* step.

   HOW, and this is the part worth keeping. Both surfaces are built on tokens, so the whole inner
   ladder is re-derived by re-pointing two custom properties on the two components rather than by
   restating a colour on every descendant. `.navrow.on`, `.sr:hover::before`, `.set-x:hover` and
   everything else that reads overlay/overlay-hover follows automatically and cannot drift.
     · --color-overlay      -> surface  (#121212 dark / #FFFFFF light)
     · --color-overlay-hover -> control (#1C1C1C dark / #ECECEC light)
   `control` is chosen for the engaged/hover rung because at +4.81 L* it is the closest match to
   the +4.32 step the panel had at its old ground — the ENGAGED STATE KEEPS ITS PERCEPTUAL SIZE
   rather than inheriting whatever the next rung happens to be. `overlay-hover` #2A2A2A would
   have been +11.60 here: a state four times louder than the one it replaces.

   Written as token references, never hexes, so light mode re-derives itself. And `--tertiary`
   is overridden alongside `--color-overlay` because `.modal` reads THAT: it is declared
   `--tertiary: var(--color-overlay)` at :root, where custom properties resolve, so the modal
   would otherwise have kept the root's #212121 no matter what this block said about overlay.

   SCOPE, stated plainly: `.modal` is one shared component, so this reaches every modal in the
   app and not only the ones reachable from Settings. Two modal grounds depending on which
   button opened them would be worse and unmaintainable. Say so if the non-settings modals
   should stay light. */
.setpanel,.modal{
  --color-overlay:var(--color-surface);
  --color-overlay-hover:var(--color-control);
  --tertiary:var(--color-surface);
}
/* The edge is now the only thing separating the drawer from identical content behind it. */
.setpanel{border-left-color:var(--color-linestrong)}

/* NESTED FLOATING SURFACES CLIMB BACK UP. Re-pointing the tokens on the container is what makes
   the inner ladder re-derive for free, but it also reaches things that float ON TOP of these
   surfaces and are rendered INSIDE them — measured: the provider dropdown in the AI modal is a
   child of `.modal`, so it inherited the override and painted #121212 on a #121212 modal. A
   popover the same tone as the surface it floats over is the container-inside-a-container
   problem with the tone step removed, and a hairline alone should not have to carry it.
   They take the rung above their host instead, so the ladder ascends the way depth should:
   modal #121212 -> dropdown #1C1C1C -> its hovered row #212121. */
.setpanel .cdd-panel,.modal .cdd-panel,
.setpanel .color-pop,.modal .color-pop{
  --color-overlay:var(--color-control);
  --color-overlay-hover:var(--color-hover);
  background:var(--color-control);
}

/* =====================================================================================
   The pane has no Close button, and the Template Library stops being a squeezed gallery (g368)
   =====================================================================================
   1 · CLOSE IS DIALOG LEGACY. Six sub-pages — Axi Memory, Context Library, Knowledge Base,
   Handover, Versions and Template Library — end in a Close button, which is the right and only
   exit for a dialog. In a pane it is not an exit at all: you leave by switching rail category or
   closing the drawer, both of which are on screen the whole time. It stayed until now because
   until g367 the rail exit was genuinely broken and Close was the only way out.

   HIDDEN, NOT DELETED, and that is the whole point — this is the SAME markup a phone renders,
   where there is no rail beside it and Close IS the exit. So the rule is scoped to
   `body.settings-docked`, which by construction never applies below 901.

   Targeted by a `set-dismiss` marker class rather than by `#cancelModal`: that id is also the
   CANCEL button on the Project, Appearance and AI forms, where it discards edits and must
   survive. Same id, opposite meaning — a marker class is the only honest way to tell them apart.

   The footer goes with it when Close was its only occupant, because `.modal-actions` is sticky
   with a top hairline: hiding just the button would leave an empty bar pinned to the pane floor.
   Where a real action shares the footer (Context Library's Clear All, the KB's Open Context
   Library) the bar stays and only Close leaves. */
body.settings-docked .set-detail .set-dismiss{display:none}
body.settings-docked .set-detail .modal-actions:has(>.set-dismiss:only-child){display:none}

/* 2 · THE TEMPLATE LIBRARY WAS A 1440 GALLERY RENDERING INSIDE A 503px PANE.
   Measured docked at 1440: `.tmpl-group` held THREE columns of 143.8px, cards 143.8 x 340.5,
   42 cards over 10 category groups, pane scrollHeight 7198 against a 781 viewport.

   The three-track grid is correct for the locked screen and its breakpoints are correct too —
   they are just keyed to the VIEWPORT (960/640), and the viewport is still 1440 when the thing
   being squeezed is a 503px pane. No media query can see a container, so docked gets its own
   layout rather than a new breakpoint.

   A pane this width wants a LIST, not a grid of cards. The card keeps every element it already
   emits and simply re-flows: content stacks in a fluid first column, the Insert/Delete actions
   move out to a shrink-to-fit second column and centre against the stack, and meta shares its
   row with the role pips instead of taking one of its own. Placement is by grid-template-areas
   so no markup changes — the children are siblings in a single flex column today, which is
   exactly what cannot become a row without one.

   The per-child margins that space the 1440 card (12/8/14/10/16 down the stack) are re-tuned for
   row density, and the summary clamps to two lines: it is the tallest child and the one item
   whose full text is never the reason you pick a template from a list.

   Scoped under `.set-detail` and `body.settings-docked` so the 1440 gallery, the phone sheet and
   cloud.css's dead template-chooser markup are all untouched. */
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-group{
  grid-template-columns:minmax(0,1fr);gap:10px}
/* THREE LINES, NOT FIVE, and the count is the whole design. One column of five-line cards was
   measured at 7837 — TALLER than the 7198 it replaced, because 42 legible rows beat 14 cramped
   grid-rows on total height even though every row got better. Height had to come out of the ROW,
   not the column count: the kind chip rides beside the name instead of above it, the summary
   clamps to one line, and meta shares its row with the role pips. Five stacked children become
   three rows, and the per-child margins that space the 1440 card give way to one row-gap. */
/* g399 — THE TITLES SHARE ONE LEFT EDGE, and the card gets its air back.
   Owner: "some cards have some weird indent before the title — Procure to Pay is indented,
   Project Delivery is indented." Measured, and it is not an indent on those cards; it is that
   EVERY card had its own. The `kind` track above was `auto`, so each card sized that column to
   its OWN badge text — "Workflow HR" against "Section Operations" — and the name started
   wherever that card's chips happened to end. Measured 79px on the HR cards; longer categories
   push it further. A list is read by running the eye down one column of names, and this one
   started in as many places as there were category words.
   So the name takes its own row and begins at the card's inset, which is the one x that cannot
   vary. This costs the row g368 deliberately saved — its note is right above and worth reading —
   but the owner has since asked for slightly taller cards, so the trade it was avoiding is now
   the trade being requested. Spacing goes with it: row-gap 5 -> 8 and padding 12 -> 14, because
   5px between three stacked text rows was the "tight" that was reported. */
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:
    "kind    actions"
    "name    actions"
    "summary actions"
    "meta    roles";
  align-content:start;align-items:center;
  column-gap:10px;row-gap:8px;padding:14px 16px}
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-top{grid-area:kind;justify-self:start}
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-name{
  grid-area:name;margin-top:0;line-height:1.35}
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-summary{
  grid-area:summary;margin-top:0;flex:none;
  display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-meta{
  grid-area:meta;margin-top:0}
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-roles{
  grid-area:roles;margin-top:0;justify-self:start;flex-wrap:nowrap;overflow:hidden}
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-actions{
  grid-area:actions;margin-top:0;align-self:center}
/* The category is the group heading directly above the card; repeating it on every card in the
   group is noise a 503px row cannot afford. The kind and Org badges stay — those vary per card. */
body.settings-docked .set-detail .tmpl-lib-modal .tmpl-cat{display:none}

/* =====================================================================================
   The rail becomes a list of destinations, and both dividers become handles (g369)
   =====================================================================================
   1 · SELECTION IS ONE TILE, ONE HEIGHT, AT EVERY WIDTH. Owner: "selection looks like it's not
   equal to all sizes and doesn't really follow the AXD UI design rulebooks." It didn't — the row
   was padding-sized, so its height tracked the label's line box and the selected fill grew and
   shrank with the text inside it. A control's hit area is a decision, not a consequence of its
   content: 40px fixed, the full width of the rail, `--radius-control`, and the engaged state is
   the `control` rung fill the rulebook gives a selected row on `surface`.
   The chevron only appears on the selected row, because it points at the pane that row opened —
   on an unselected row it would be pointing at nothing.

   2 · THE FOOT RULE separates the two rows that FIRE from the nine that NAVIGATE. Getting Started
   and Guided Tour do not open a pane; grouping them under a hairline says so before you click,
   which is cheaper than learning it by clicking.

   3 · TWO HANDLES, 7px of grab either side of a 1px line. The line is the affordance and stays
   exactly as wide as the hairline it replaces; the grab area is invisible and generous, because
   a 1px drag target is a 1px drag target. They brighten to ink on hover and on keyboard focus.
   `touch-action:none` stops the browser claiming the gesture as a scroll mid-drag. */
.setpanel .navrow{
  height:40px;min-height:40px;padding:0 12px;gap:12px;
  border-radius:var(--radius-control)}
.setpanel .navrow .nav-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.setpanel .navrow+.navrow{margin-top:2px}
.setpanel .navrow.on{background:var(--color-control);color:var(--color-ink)}
.setpanel .navrow .nav-ic{flex:0 0 auto;display:inline-flex;width:17px;height:17px}
.setpanel .navrow .nav-ic svg{width:17px;height:17px}
/* THE RAIL ANSWERS THE SAME WAY THE LISTS DO (g434)
   Owner: make the rail match the category lists. The rail's hover was already type rather than a
   tile — it just did half the sentence, lifting the row under the cursor without doing anything
   to the others. Now pointing at the rail dims every label one rung and lifts the one you are on,
   so the same gesture reads the same way everywhere in the panel.
   SELECTION IS NOT HOVER, and it keeps its fill. `.navrow.on` carries `--color-control` from
   g421, which answers a different question — where you ARE, not where you are pointing — and it
   has to survive you pointing somewhere else, or the panel forgets which screen is open the
   moment your cursor moves. So `.on` is exempt from the fade and holds ink throughout. That is
   also why the two can coexist without competing: one is a fill, the other is tone.
   `(hover:hover)` for the same reason as g433 — a sticky :hover on a touchscreen would strand the
   rail faded around whichever row was last tapped. */
@media (hover:hover){
  .setpanel .set-rail:hover .navrow{color:var(--color-faint)}
  .setpanel .set-rail:hover .navrow .nav-ic{color:var(--color-faint)}
  .setpanel .set-rail .navrow:hover{color:var(--color-ink)}
  .setpanel .set-rail .navrow:hover .nav-ic{color:var(--color-muted)}
  .setpanel .set-rail:hover .navrow.on,
  .setpanel .set-rail:hover .navrow.on .nav-ic{color:var(--color-ink)}
}
/* The rail's chevron is gone at g379 — owner: "it serves no purpose since we already have the
   hover to indicate what section of the settings we've selected." Right: the row already carries
   its state as the `control` fill, and a chevron that only appears on the selected row was a
   second mark for the same fact. The `.sr` rows keep theirs, because those open something and
   have no selected state of their own to read. */
.nav-rule{height:1px;background:var(--color-line);margin:10px 12px}

.set-grip,.set-split{
  border:0;padding:0;background:transparent;touch-action:none;position:relative}
/* the drawer's own edge, sitting over its left border */
/* FLUSH INSIDE, not straddling the edge. Sitting at left:-3px looked right — the grab area
   centred on the border it drags — but `.setpanel` is `overflow:hidden`, so those 3px were
   clipped away and the handle was a 4px target pretending to be 7. Measured by the clip scan,
   not by eye: nothing about it looked wrong. Flush at left:0 gets the whole 7px back, and the
   affordance line lands on the panel's own border rather than 3px inside it. */
.set-grip{
  position:absolute;left:0;top:0;bottom:0;width:7px;z-index:2;cursor:col-resize}
.set-grip::after{
  content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:1px;height:40px;border-radius:1px;background:transparent;transition:background .14s,height .14s}
.set-grip:hover::after,.set-grip:focus-visible::after,
.setpanel.is-resizing .set-grip::after{background:var(--color-ink);top:0;bottom:0;height:auto;transform:none}
/* the rail/page divider, which replaces the rail's own border-right while it is draggable */
.set-split{width:1px;flex:0 0 auto;cursor:col-resize;align-self:stretch}
.set-split::before{content:"";position:absolute;left:-3px;top:0;bottom:0;width:7px}
.set-split::after{
  content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:1px;height:40px;background:transparent;transition:background .14s,height .14s}
.set-split:hover::after,.set-split:focus-visible::after,
.setpanel.is-resizing .set-split::after{background:var(--color-ink);top:0;bottom:0;height:auto;transform:none}
/* =====================================================================================
   THE SETTINGS GRIPS LIGHT THEIR WHOLE EDGE (g419)
   =====================================================================================
   Owner: dragging Contents or Axi lights the full bar in white; the settings handles do not.
   Right, and they differed in two ways at once. Each drew a 40px stub centred on the edge that
   grew to 64px on hover — a fragment of a line, not the line — and the panel grip lit in
   `linestrong` (14% white) where the TOC resizer lights in `ink`. So one read as a faint tick and
   the other as a white rule, for the same gesture.
   Both now do what `.toc-resizer` does: the marker spans `top:0;bottom:0` and paints `ink`. The
   centring transform is cleared with it, since a full-height bar has nothing to centre.
   AND THEY LIGHT WHILE DRAGGING, which is the half that was actually missing. `:hover` ends the
   moment the pointer leaves the 7px hit strip — which is exactly what happens as soon as you
   start dragging — so the highlight vanished during the drag itself. `.setpanel.is-resizing` is
   the state the panel already sets for `user-select`, and it is the honest hook: the TOC solves
   the same problem with `body.toc-resizing`. */
.setpanel.is-resizing{user-select:none}

/* Phones have no rail, so they have nothing to resize and no second column to divide. */
@media (max-width:900px){ .set-grip,.set-split{display:none} }

/* Search is gone (g369). Its rules are retired rather than left to rot: `.set-search` no longer
   exists in the markup, and `has-query` was the class it set. Kept as one deletion note instead
   of eight orphaned selectors, so nobody spends a morning wondering which state shows them. */

/* =====================================================================================
   Context Library: two sections, one page (g372)
   =====================================================================================
   The local source items and the Knowledge Base now stack in one scroll. They need to read as
   two distinct answers to the same question, not as one long form — so the second section is
   separated by the same hairline the rest of the app uses for structure, and each carries a
   Section-rung heading. No card, no tint: the rulebook gives a container no border and no fill
   just for being a container, and a tinted panel here would be depth that means nothing. */
/* g399 — ONTO THE SPACING CONTRACT. Measured in the pane, the sibling gaps down this screen ran
   14, 14, 28 and the section rule added 26 of padding on top of its 28 of margin. None of 14, 26
   or 28 is a token: §5 gives stack-heading 12 (a heading to the content it introduces), gap-item
   16/12, and gap-group 32/24 (between content groups). The screen was not badly spaced so much as
   spaced in numbers that belong to nothing, which is what makes a layout feel arbitrary rather
   than tight or loose.
   The two sections are content groups, so 24 in the pane. The rule sits between them and takes
   the same 24 on each side rather than 28 above and 26 below, which was an asymmetry nobody
   chose. And 6px under a section heading becomes stack-heading's 12. */
/* WIDER, BECAUSE THAT IS HOW IT GETS SHORTER (g441). Owner: "make it wider, make it whatever you
   need so it doesn't lead to a lot of scroll." Measured at 1440x900 it was 760 wide and 852 tall
   — 94.7% of the viewport with 350px still to scroll, the longest screen in the app after
   Templates. The spacing here is already on the ramp (see the note above), so the height is not
   loose spacing; it is prose and item rows wrapping inside a narrow column. Widening reflows both
   and takes the height down without removing anything, which is the same trade the AI overlay
   took at g429. 1040 keeps the modal's 24px gutter clear at 1100 and still collapses below that. */
.modal.ctx-lib-modal{width:min(1040px,100%)}
.ctx-lib-modal .ctx-sec+.ctx-sec{
  margin-top:24px;padding-top:24px;border-top:1px solid var(--color-line)}
.ctx-lib-modal .ctx-sec-h{
  font-size:16px;font-weight:500;line-height:22px;color:var(--color-ink);margin:0 0 12px}
/* the page intro and each section intro bind to the heading above them: stack-heading, not 14 */
.ctx-lib-modal>h2{margin-bottom:12px}
.ctx-lib-modal>.rename-hint{margin-bottom:24px}
.ctx-lib-modal.ctx-lib-modal>h2{margin-bottom:12px}
/* The two intros sit directly under their headings and are the same rung as the page intro. */
.ctx-lib-modal .ctx-sec .rename-hint{margin-top:0}
/* The privacy note ends the local section rather than the page, so it stops being a footer. */
.ctx-lib-modal .ctx-sec-local .ai-privacy{margin-bottom:0}

/* =====================================================================================
   SOP Analysis, rendered as a pane screen instead of a page (g373)
   =====================================================================================
   The report was built as a canvas view — centred at max-width 1180, wearing the page card, with
   a 28px Display title and grids that assume a page's worth of room. In a 638px pane none of
   that holds, and none of its breakpoints fire either: they are keyed to the VIEWPORT (720/640),
   which is still 1440 when the thing being squeezed is a drawer. Same trap the Template Library
   hit at g368, same fix — the docked state gets its own layout rather than a new breakpoint.

   The outer card goes because the pane IS the surface: a card inside a drawer is a container
   inside a container with no tone step between them, which the rulebook spends a paragraph
   forbidding. Its children keep their `surface2` panels, so the ladder still reads.

   The title takes the Section rung for the same reason every other docked sub-page does (g366):
   a pane screen sits under a header that already says where you are. It needs `!important` to
   land — the reskin layer sets 28px with one of its own. */
body.settings-docked .set-detail .analysis-modal .analysis-report{
  max-width:none;margin:0;background:none;border:0;padding:0;box-shadow:none;overflow:visible}
body.settings-docked .set-detail .analysis-modal .analysis-head{
  display:block;margin-bottom:16px}
body.settings-docked .set-detail .analysis-modal .analysis-head h2{
  font-size:16px!important;font-weight:500!important;line-height:22px!important;margin:6px 0 0}
body.settings-docked .set-detail .analysis-modal .analysis-head .manager-note{margin-top:8px}
body.settings-docked .set-detail .analysis-modal .analysis-actions{margin-top:14px}
/* Two across, not four: at pane width a four-track row gives each metric ~145px, and the
   numbers are the whole point of the row. */
body.settings-docked .set-detail .analysis-modal .analysis-metrics{
  grid-template-columns:repeat(2,minmax(0,1fr))}
/* The donut and its ranked list stack — side by side they were 170px of chart against 400px of
   list, and the chart is the thing you look at first. */
body.settings-docked .set-detail .analysis-modal .analysis-donut-grid{
  grid-template-columns:minmax(0,1fr);gap:16px;justify-items:center}
body.settings-docked .set-detail .analysis-modal .analysis-ranked-list{width:100%}

/* THE PANELS LOST THEIR TONE STEP, and only a measurement showed it. The report's tiles and
   panels paint `--paper2`, a legacy alias that resolves through `--tertiary` — which g356
   deliberately re-points to `surface` on `.setpanel`/`.modal` so the drawer's inner ladder
   re-derives. Correct for the drawer, wrong here: measured rgb(18,18,18) on the tiles against
   rgb(18,18,18) on the pane behind them, so every panel in the report was flat text on the same
   ground with nothing to say it was a panel. Nothing looked broken; the boxes simply were not
   there. They take `surface2` explicitly — one rung up, the same step the report gets on canvas. */
body.settings-docked .set-detail .analysis-modal .analysis-metrics>div,
body.settings-docked .set-detail .analysis-modal .analysis-panel,
body.settings-docked .set-detail .analysis-modal .analysis-insight{
  background:var(--color-surface2)}

/* =====================================================================================
   Settings stops being an overlay and becomes a panel of the shell (g374)
   =====================================================================================
   Owner: "the Settings menu isn't an overlay slide-out panel. It expands from the panel, similar
   to how Axi Assistant expands, while keeping everything clickable and not being an overlay…
   make it black, like darker colors, similar to the contents and Axi Assistant."

   Contents and the Axi panel were already this: `--bg` (pure #000), a hairline against the
   canvas, no scrim, and `.main` giving up the width rather than being covered. Settings was the
   one modal thing in the app, which is what made it feel like "too much every time".

   WHAT CHANGES HERE is only the panel's relationship to the page. It keeps its two-stage width,
   its resizable dividers and every sub-page inside it.
     · ground: `--color-overlay` (#212121) -> `--bg` (#000), matching its two neighbours.
     · it starts BELOW the app bar (`--bar-h`) exactly as the chat does, so the toolbar — and the
       button that opened it — stay reachable. "Everything clickable" starts with that button.
     · z-index drops from 341 (above the scrim, above everything) to 160, the chat's layer: below
       the Contents rail at 170 and far below real modals, which must still be able to cover it.
     · the ground is now the SAME as the canvas either side of it, so the hairline is the only
       thing dividing them and is promoted from decoration to structure — the same call g356 made
       when the drawer matched the step table.

   THE CANVAS MAKES ROOM at >=1101 only. Below that the Contents rail is itself an overlay drawer
   and there is not enough width to seat a third column, so Settings keeps the sheet it has. */
/* The ground is a TONE decision, not a layout one, so it applies at every width — a phone sheet
   should be the same black as the Contents drawer it sits beside. Only the docking below is
   desktop-scoped. */
.setpanel{background:var(--bg);border-left:1px solid var(--line)}

@media (min-width:1101px){
  .setpanel{top:var(--bar-h);z-index:160}
  /* THE CANVAS YIELDS THE SAME WAY IT DOES FOR THE CHAT — by `width`, with `!important`, in the
     same four-state pattern at :2565. Written any other way it silently loses: `.main` is sized by
     `width:…!important` there, so the `margin-right` + `max-width` this rule first used computed
     correctly and changed nothing at all. Measured `margin-right:560px` applied and `.main` still
     1440 wide. `!important` beats specificity however careful the selector.
     `--toc-rail` is 0 — collapsing Contents removes it entirely rather than leaving a rail (see
     the note at :2548) — so a collapsed Contents hands the canvas its full width back, and
     Settings at 600 still leaves 840 at 1440. Both Contents states are covered because you can
     re-open Contents while Settings is up; chat states are not, because opening either of those
     two panels closes the other. */
  /* THE CANVAS MOVES IN THE SAME TWO STAGES THE PANEL DOES (g375). Owner: "since the settings
     expand to two stages, then the rest of the screen should expand to two stages."
     g374 subtracted the OPEN width at both stages, so with the panel showing only its 304px rail
     the canvas had already given up 600 — measured a 296px band of dead ground between the
     content's right edge and the panel's left. The panel had two stages and the canvas had one.
     `--set-stage` is whichever width is actually on screen, and it is the same expression the
     panel sizes itself with, so the two cannot drift apart. Both interpolate on the same curve,
     so the canvas and the panel move as one gesture rather than one chasing the other. */
  /* g436 — AND THERE IS ONLY ONE STAGE NOW, so there is only one width to give up. The second
     line here was `body.settings-open.settings-expanded{--set-stage:var(--set-w,600px)}`, and
     g432 left it behind: `settings-expanded` is toggled from `state.settingsCat`, which is still
     set (deliberately — the rail uses it to light the active row) even though the panel stopped
     widening. So the canvas kept yielding 600 to a panel occupying 240 and the workflow sat
     pushed right against 360px of empty ground, which is precisely the fault g375 was written to
     cure, arriving from the other direction.
     One stage, one reservation: the rail width, always. */
  body.settings-open{--set-stage:var(--set-rail,240px)}
  body.settings-open .main{
    width:calc(100vw - var(--toc-open) - var(--set-stage))!important;
    max-width:calc(100vw - var(--toc-open) - var(--set-stage))!important}
  body.settings-open .app-shell.toc-collapsed .main{
    width:calc(100vw - var(--toc-rail) - var(--set-stage))!important;
    max-width:calc(100vw - var(--toc-rail) - var(--set-stage))!important}
  /* width interpolates, so the canvas slides open and shut with the panel rather than jumping. */
  .main{transition:margin-left .26s cubic-bezier(.4,0,.2,1),width .26s cubic-bezier(.4,0,.2,1),max-width .26s cubic-bezier(.4,0,.2,1)}
}

/* =====================================================================================
   The pane becomes a query container (g374)
   =====================================================================================
   Three times now a docked sub-page has been laid out against a width nobody could ask about:
   the Template Library at g368, the SOP Analysis at g373, and the role pips below. Each time the
   fix was a hand-picked number standing in for "the pane is narrow", because a media query can
   only see the VIEWPORT and the viewport is 1440 whatever the drawer is doing.
   Now the drawer is resizable AND its default width just dropped from 920 to 600, those guesses
   are worth even less — the pane can be any width the person dragging it wants.
   `container-type:inline-size` on `.set-detail` lets the sub-pages ask the only question that
   matters: how much room do I actually have? Safe here because the pane's own width comes from
   the panel, never from its contents, which is exactly the condition inline-size containment
   requires. */
body.settings-docked .set-detail{container-type:inline-size;container-name:setpane}

/* Role pips were pinned to one line for a 463px card and clipped the moment the pane got
   narrower — measured 10 clipped nodes at a 359px pane. Below 420 they wrap instead: a second
   line of pips costs less than silently hiding half the roles a template brings with it. */
/* SELECTORS MATCH THE RULES THEY OVERRIDE. A container query adds NO specificity — it only gates
   when the rule applies — so `.tmpl-lib-modal .tmpl-card-roles` (0,2,0) lost outright to the
   g368 rule at `body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-roles` (0,4,0) and
   the pips stayed clipped with the container reporting a matching 359px. Same full prefix here. */
@container setpane (max-width:420px){
  body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-roles{flex-wrap:wrap;overflow:visible}
  body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card{
    /* g399 — same alignment fix as the wider block above: `auto` in the first track sized the
       kind column to each card's OWN badge text, so the name began at a different x on every
       card. Name takes its own full-width row and starts at the inset. */
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "kind    kind"
      "name    name"
      "summary summary"
      "meta    roles"
      "actions actions";
    row-gap:8px}
  body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-top,
  body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-roles{justify-self:start}
  body.settings-docked .set-detail .tmpl-lib-modal .tmpl-card-actions{justify-self:start}
  /* The analysis metrics drop to one column before they truncate their own numbers. */
  body.settings-docked .set-detail .analysis-modal .analysis-metrics{grid-template-columns:minmax(0,1fr)}

  /* WORKLOAD BALANCE STOPS CUTTING IN (g399). `.analysis-bar-row` is four tracks whose minimums
     add up to 26+130+120 plus three 12px gaps = 312px, inside a panel measuring ~271 in this
     pane. It overflowed the pane's right edge by 5px on every row — 30 elements, measured — which
     is the "cutting in" that was reported.
     The collapse rules for this row already exist at max-width 900 and 380, and they never fired
     here: those are WINDOW queries and the window is 1440. That is the whole reason this pane has
     a container. Mirrored, not reinvented — same tracks the 380 breakpoint uses, so the narrow
     layout stays one design rather than two that drift. */
  body.settings-docked .set-detail .analysis-modal .analysis-bar-row{
    grid-template-columns:22px minmax(0,1fr);gap:8px}
  body.settings-docked .set-detail .analysis-modal .analysis-bar-val{grid-column:2/-1;text-align:left}
  body.settings-docked .set-detail .analysis-modal .analysis-mini-bar{grid-column:2/-1}
}

/* =====================================================================================
   The panel is mirrored: rail on the right, sub-pages unfurl left into the page (g377)
   =====================================================================================
   Owner: "instead of the sub settings expanding from the right of the list, they should expand
   from the left of the list. The list… should be right-aligned… everything expands out into the
   page, rather than off to the side."

   THE BUG THIS FIXES IS MOVEMENT, not taste. The panel is anchored to the right edge and grows
   leftward, and the rail sat at the panel's LEFT edge — so expanding dragged the rail along with
   it. Measured: the rail's left edge went 1137 -> 646 between stage one and stage two. The row
   you had just clicked slid 491px away from the cursor that clicked it, and the thing you were
   reading moved while you read it.

   Mirrored, the rail is pinned to the screen edge where the Settings button already lives, and it
   does not move at all between stages. The width the panel gains goes entirely to the sub-page,
   which unfurls leftward into the canvas — content arriving in the space, instead of the menu
   retreating from it.

   Scoped to >=901, the only widths where a rail and a detail column both exist. Below that the
   sheet has no rail to mirror. */
@media (min-width:901px){
  .set-cols{flex-direction:row-reverse}
  /* the divider changes sides with the column it divides */
  .set-rail{border-right:0;border-left:1px solid transparent}
  /* g432 — the divider itself goes. It marked the join between the rail and the detail column,
     and with stage two removed there is no second column on the other side of it: a hairline
     against the canvas is not a divider, it is an edge the panel does not need (the panel already
     separates from the canvas by tone). Left as a transparent 1px so the rail's box width is
     unchanged and nothing shifts. */

  /* A true mirror of the row: icon against the outer edge, label right-aligned to it, and the
     chevron on the inner side pointing at the pane it opens — which is now on the left. */
  /* THE ROWS STAY LEFT-ALIGNED (g378). g377 mirrored them along with the columns and the owner
     read it back plainly: "the right-aligned thing doesn't work." It doesn't — a menu is scanned
     by running an eye down one column of labels, and right-aligning against a ragged left edge
     asks the eye to find a new starting point on every row. Mirroring the PANEL solved a real
     problem (the rail moving between stages); mirroring the TEXT solved nothing and cost
     legibility.
     The chevron briefly led the row here and was removed entirely at g379 — the fill already
     says which section is open, and a mark that appears only on the selected row was saying it
     twice. The row is back to [icon][label], which is what it was before the mirror. */
  .setpanel .navrow{flex-direction:row;text-align:left}
  .setpanel .navrow .nav-t{text-align:left}
}
/* ONLY THE MENU MIRRORS. Owner, clarifying: "I want the main settings menu to be right-aligned,
   but the sub settings can still be left-aligned as they expand into the page."
   So `.set-cols` swaps the two COLUMNS and the rail's rows align to the outer edge, and nothing
   inside `.set-detail` is touched. A sub-page is a form, a document list, a report; those read
   left-to-right whichever side of the panel they arrive on, and mirroring them would be a mirror
   for its own sake.
   The header stays put too, and not only because it is not the menu: an X on the panel's LEFT
   edge would sit on the one boundary that MOVES between stages, which is the exact problem this
   pass exists to remove. At the top right it is pinned, conventional, and sits directly under the
   toolbar button that opened it. */

/* =====================================================================================
   The drawer travels through zero on the way in and out (g380)
   =====================================================================================
   The two stages animated; arriving and leaving did not. Opening runs render(), which builds the
   panel at its final width with no previous value to interpolate from; closing emptied the panel
   root outright. Both jumped, and the canvas jumped with them.

   Width 0 is the extra state both ends need. The panel's children are fixed-width and anchored to
   the right edge (row-reverse since g377), so narrowing clips from the LEFT — the drawer wipes
   off the screen edge instead of squashing its contents on the way out. `--set-stage` rides to 0
   with it so the canvas closes the gap on the same curve rather than snapping afterwards.

   `!important` on the stage, because these two states have to outrank both
   `body.settings-open` and `body.settings-open.settings-expanded`, which are more specific than a
   single class and would otherwise hold the canvas open while the panel left without it.
   The border goes to 0 too: a 1px line on a 0px panel still paints, leaving a hairline stranded
   at the screen edge after the drawer has gone. */
@media (min-width:1101px){
  body.settings-entering,body.settings-leaving{--set-stage:0px!important}
  .setpanel.set-enter,.setpanel.set-leave{width:0!important;border-left-width:0}
  /* The old arrival nudge (translateX + fade) is redundant now the panel wipes from the edge, and
     the two together read as two separate movements. The wipe is the arrival. */
  .setpanel{animation:none}
}
@media (min-width:1101px) and (prefers-reduced-motion:reduce){
  .setpanel.set-enter,.setpanel.set-leave{width:var(--set-rail,240px)!important}
  body.settings-entering,body.settings-leaving{--set-stage:var(--set-rail,240px)!important}
}

/* =====================================================================================
   Contents, Axi and Settings may all be open at once (g383)
   =====================================================================================
   g374 forbade it and g383 allows it, so the canvas now has to subtract BOTH side panels when
   both are up. Without these two rules the settings rules alone win — they are later in the
   file and equally !important — and the canvas keeps the chat's width, sliding its left edge
   underneath the Axi panel.

   Four states exist ({Contents folded?} x {Axi open?}) and all four need spelling out, because
   `.main` is positioned by margin-left as well as sized by width: the chat sits between Contents
   and the canvas, so opening it pushes the canvas right by exactly its width while Settings
   takes from the other end. Mirrors the existing four-state block at :2565 rather than inventing
   a second way of saying the same thing.

   No floor is imposed on what is left. Three panels at 1440 leaves a cramped canvas, and that is
   the honest result of opening three panels — visible, and undone by closing one. */
@media (min-width:1101px){
  body.settings-open .app-shell.chat-open .main{
    margin-left:var(--chat-open,320px)!important;
    width:calc(100vw - var(--toc-open) - var(--chat-open,320px) - var(--set-stage))!important;
    max-width:calc(100vw - var(--toc-open) - var(--chat-open,320px) - var(--set-stage))!important}
  body.settings-open .app-shell.chat-open.toc-collapsed .main{
    margin-left:calc(var(--toc-rail) - var(--toc-open) + var(--chat-open,320px))!important;
    width:calc(100vw - var(--toc-rail) - var(--chat-open,320px) - var(--set-stage))!important;
    max-width:calc(100vw - var(--toc-rail) - var(--chat-open,320px) - var(--set-stage))!important}
}

/* =====================================================================================
   The toolbar Save takes the primary fill (g385)
   =====================================================================================
   Owner's call, and it reverses half of g349 ("Save is Secondary on desktop; Draft Steps keeps
   the one primary"). Recorded rather than silently applied, because g349 was a locked decision
   and a future audit must be able to tell a knowing reversal from a drift.

   `.toolbar-tog` sets its own transparent ground at a specificity `.btn.primary` cannot reach,
   so the class alone changed nothing — measured `rgba(0,0,0,0)` on a button carrying `primary`,
   while Draft Steps beside it rendered the real #2A2A2A. The fill has to be stated here.

   ⚠️ THIS PUTS TWO PRIMARIES ON THE DESKTOP CANVAS — Save in the toolbar and Draft Steps above
   the table. The rulebook's one-primary-per-view discipline says that is one too many; the owner
   was offered the demote-Draft-Steps variant and chose Save alone, so Draft Steps stands until
   he says otherwise. Logged as a known departure, not an oversight. */
/* IT HAS TO BE AN ID RULE, and the file says so at :7411 — "Save names itself: `.toolbar
   #saveBtn` carries its Secondary skin at ID specificity, which a [class rule] cannot reach."
   Five `.toolbar #saveBtn` rules (:6006, :6140, :6223, :7028, :7414) pin it, all with
   !important. `.toolbar .toolbar-tog.primary` is three classes; one ID outranks any number of
   them, so the class version measured transparent twice — the second time AFTER "fixing" it,
   which is the tell that specificity, not the selector, was the problem.
   Desktop only: g349's "Ghost in the phone icon bar" is untouched, and the narrow-width rule at
   :7414 still governs there. */
@media (min-width:1101px){
  .toolbar #saveBtn.primary{
    background:var(--color-overlay-hover)!important;
    border-color:transparent!important;
    color:var(--color-ink)!important}
  .toolbar #saveBtn.primary:hover,.toolbar #saveBtn.primary:focus-visible{
    background:var(--color-hover)!important;border-color:transparent!important}
}

/* =====================================================================================
   The step row's three chips take their rulebook rungs (g388) — PROPOSAL, easy to revert
   =====================================================================================
   Owner asked to SEE this before it is settled. The column currently sits at 12px for all
   three, which is why the step link "looks 11px": it is the same nominal size as the badges
   beside it but carries no tracking, so it reads optically smaller. The rulebook (v1.8 §3 and
   §Containers) does not have them at one size at all:

     · Badge rung  — 11px / 500 / 0.08em / UPPERCASE   -> .role-chip, .step-type
     · Button rung — 14px / 500 / 0.01em / Title Case  -> .step-link (it IS a control)

   So the fix is a divergence, not a nudge: the badges go DOWN to 11 and the control goes UP to
   14, which is what gives the row a hierarchy instead of three things at one size.

   Placed last and !important because the values it must beat are themselves !important at
   :5289-:5294 and :5181 — same specificity, so order decides.

   ⚠️ TWO THINGS TO LOOK AT PARTICULARLY:
   1. `text-transform:uppercase` turns "Start / Process / Decision" into "START / PROCESS /
      DECISION". The badge rung says uppercase and role codes already are; step types were not.
      This is the most visible part of the change and the easiest to drop on its own.
   2. The step link grows 12 -> 14, so the Following Step column gets wider. If that crowds the
      row at narrow widths it is the padding that should give, not the size. */
/* g389: the badge RUNG holds for both, but the CASE splits. Role codes (CLIENT, CEO, LCE) are
   uppercase because that is what they are — initialisms, uppercase in the data. Step types are
   words (Start, Process, Decision), and the owner's call is that words stay words: uppercasing
   them made the column shout a taxonomy nobody needs to read twice.
   So `.step-type` takes `none` and renders as authored. The rulebook's "UPPERCASE" on the badge
   rung is written for codes and short status words; this is a knowing departure for step types,
   and it is the kind of thing the rulebook prompt should settle in the book rather than leave as
   a per-app exception. */
.role-chip,.step-type{
  font-size:11px!important;
  font-weight:500!important;
  letter-spacing:.08em!important}
.role-chip{text-transform:uppercase}
.step-type{text-transform:none!important}
.step-link{
  font-size:14px!important;
  font-weight:500!important;
  letter-spacing:.01em!important}

/* =====================================================================================
   TOUCH TARGETS — the 44px minimum, honoured without redrawing the UI (g391)
   =====================================================================================
   Audit, measured at 320/375/390/430/768: 226 interactive elements below the §5 minimum of
   44px on touch. Most came from `--ctl-h`, which is fixed at the token above. These are the
   ones that carry their own height and so never saw it:

     .workflow-link  36h  x33     .cmt-btn      32h  x17     .edit-pen   32h  x17
     .step-link      28h  x16     .do-seg-btn   32h  x12     .sp-toggle  25h  x6
     .toolbar-sop-btn 22h         .ai-posture   22h

   §5 gives the method as well as the number: "Tap targets never below 44px on touch, EXPANDED
   INVISIBLY if the visual is smaller." So the pencil stays a 32px pencil — growing it to 44
   would wreck the row density the desktop design is built on — and gains a hit area instead.

   THE EXPANSION IS VERTICAL ONLY, deliberately. Height is the failing dimension in every case
   above; the widths were already fine. A horizontal expansion on `.cmt-btn` and `.edit-pen`,
   which sit 10px apart in the same cell, would have overlapped their hit areas by 2px and made
   the wrong icon fire — a worse bug than the one being fixed. Vertically the card now has a
   12px gap (see the stacked-card block), so a 6px reach either side stays inside it.

   Full-width rows are the exception and just take the height for real: they are list rows, and
   §5 puts those at 56 anyway, so there is nothing to preserve by faking it. */
@media (max-width:1024px){
  /* Full-width rows and real text buttons take the height for real — nothing is preserved by
     faking it, and §5 puts list rows at 56 anyway. `.btn.small` pins BOTH height and min-height
     at 32/34 further up, so both have to be released or the min-height alone is ignored. */
  .workflow-link,.sp-toggle{min-height:44px}
  .btn:not(.menu-item),label.btn:not(.menu-item){min-height:44px!important}
  .btn.small{height:auto!important;min-height:44px!important}
  /* MENU ITEMS COUNT. The base rule at ~L3988 excludes `.menu-item` so dropdown rows can keep
     their own compact sizing, and that exclusion quietly carried the height down to 38px on
     touch — the rows inside "All Actions" and the ⋯ menu were the last things under the minimum
     here ("Fill Gap", "Add Blank Step", "Edit Workflow Title", "Save as Template"). A row in an
     open menu is exactly as tappable as a button on the page; §5 draws no distinction. */
  .btn.menu-item,label.btn.menu-item{height:auto!important;min-height:44px!important}

  /* The Selector's options are 28px, a pointer size, in BOTH its uses — the view switch and the
     subpath filter. The track grows with them so the travelling thumb still lands on an option. */
  .modeseg{padding:4px}
  .modeseg button{height:44px}
  .modeseg::before{top:4px;left:4px;height:44px;width:calc((100% - 11px) / 2)}

  /* Icon and inline controls keep their drawn size and gain an invisible hit box. */
  .cmt-btn,.edit-pen,.step-link,.toolbar-sop-btn,.do-seg-btn{position:relative}
  .cmt-btn::before,.edit-pen::before,.step-link::before,
  .toolbar-sop-btn::before,.do-seg-btn::before{
    content:"";position:absolute;left:0;right:0;top:50%;
    height:44px;transform:translateY(-50%);
    /* z-index stays at auto so the expander never paints over its own icon, and -1 is avoided
       because it would drop the box behind the button's background and stop taking taps. */
    pointer-events:auto}

  /* `.ai-posture` IS THE ONE EXCEPTION, and it is deliberate. It carries `overflow:hidden`,
     which it needs for the `text-overflow:ellipsis` holding a long provider name inside its
     `max-width:40vw` — and that same clip eats any pseudo-element expander, so the trick above
     cannot reach it. Both alternatives are worse: dropping the clip lets a long label spill out
     of the fixed 48px title row, and a real 44px min-height turns a quiet status readout into
     the tallest object in that row. It stays 22px as a status chip that happens to be clickable,
     and the screen it opens is also one tap away at Settings, then AI. */
}

/* =====================================================================================
   SOP ANALYSIS — the three things the owner caught (g399)
   =====================================================================================
   1 · THE METRIC ROW WAS TOUCHING THE PANEL BELOW IT. Measured in the pane: `.analysis-metrics`
   carries `margin:28px 0 0` — top only — so the gap between the four metric tiles and the Role
   usage panel underneath was exactly 0px. They are two content GROUPS, and §5 puts gap-group
   between groups (32 desktop, 24 below 768). In the pane the narrow rung is the honest one.
   Invariant 5 is the reason it read wrong even before you measured it: the gap between groups
   must be at least twice the gap inside them, and the tiles sit 16px apart internally. At 0 the
   grouping inverted — the tiles looked like they belonged to the panel below.

   2 · THE FAIRNESS CARDS WERE INVISIBLE. Measured: `.analysis-flag` computed rgb(23,23,23) inside
   an `.analysis-insight` that is also rgb(23,23,23). Identical. g368 gave the report's PANELS
   their `surface2` step on a `surface` pane and the flags were never part of that fix, so they
   sat at the same tone as the box containing them.
   The fix is not a fourth tone. §2 is explicit: "If a layout seems to need a fourth structural
   tone, the layout is too deep: flatten it or separate with hairlines." A list of findings is a
   specification list, and row dividers are one of the four line exceptions precisely because they
   carry the structure of data. So the flags stop being cards-inside-a-card and become a divided
   list. The invisibility goes away because there is no longer a box that needs to be seen.

   3 · AND THE 3px LEFT BAR GOES. `border-left:3px solid` in a status colour is the same thing
   g365 already removed from the AI data note: an accent stripe standing in for the badge recipe.
   Containers carry no border (§2), and the severity is not lost — `.analysis-flag-sev` was
   already in the markup carrying the word. It just needed to look like a badge: 11px/500/0.08em
   uppercase on the §2 status recipe (14% fill, 35% border, full-strength text), which is where
   that colour belonged all along. It was 700/.05em, which is neither the badge rung nor the
   eyebrow's. */
body.settings-docked .set-detail .analysis-modal .analysis-metrics,
.analysis-metrics{margin-bottom:24px}
@media (min-width:1101px){ .analysis-metrics{margin-bottom:32px} }

.analysis-flag{
  border-left:0!important;border-radius:0;
  background:transparent;
  padding:14px 0;margin-top:0;
  border-top:1px solid var(--color-line)}
.analysis-flag:first-of-type{border-top:0;padding-top:6px}
/* the severity keeps the colour; it just carries it as a badge instead of as a stripe */
.analysis-flag-sev{
  height:auto;padding:3px 8px;
  font-size:11px;font-weight:500;letter-spacing:.08em;
  border-width:1px;border-style:solid}
.analysis-flag.high .analysis-flag-sev{
  color:#BC8F8B;background:rgba(188,143,139,.14);border-color:rgba(188,143,139,.35)}
.analysis-flag.med .analysis-flag-sev{
  color:#AE9674;background:rgba(174,150,116,.14);border-color:rgba(174,150,116,.35)}
.analysis-flag.low .analysis-flag-sev{
  color:#839CBC;background:rgba(131,156,188,.14);border-color:rgba(131,156,188,.35)}

/* =====================================================================================
   PHONE STEP CARD — two things the desktop column layout was hiding for it (g401)
   =====================================================================================
   Found by putting the phone and desktop renders side by side. Most of what a vision pass
   flagged was the responsive design doing its job — the subpath block is a transparent indent
   at EVERY width (measured `10px 0 2px 24px` and transparent at 390, 768 and 1440 alike), the
   tablet does have its column headers, and desktop wraps its row actions to two lines too. Two
   findings survived checking.

   1 · THE STRAY DASHES. `nextCellHTML` and the icons cell render `<span class="muted">—</span>`
   when there is nothing to show. On desktop that dash sits under a column headed FOLLOWING STEP
   or ICONS, where it reads as "none" — the header supplies the noun and the dash supplies the
   value. The phone card has no headers (correctly: `thead` is display:none below 720), so the
   dash loses the word that gave it meaning and becomes a bare punctuation mark floating in the
   card. Measured six of them per screen. An empty cell should simply not be there.

   2 · THE SUBPATH FOOTER WRAPPED TO THREE ROWS, against two on desktop and tablet. Its
   `.se-spacer` is an auto-margin strut that pushes the last control to the far edge — right when
   the row has width to spare, and a guaranteed extra line when it does not. Removing the strut
   on phones lets the three buttons flow, which is what the same footer does at every other size.
   The 44px touch height from g391 is what tipped it from two rows to three; the strut was always
   the thing making the decision. */
@media (max-width:720px){
  .page-card.wf-view tr.step-row > td:has(> .muted:only-child){display:none!important}
  .subpath-foot .se-spacer{display:none}
  .subpath-foot{gap:8px}
}

/* =====================================================================================
   A FLAG WITH A BUTTON STACKS IN THE PANE (g402)
   =====================================================================================
   Caught in the render, not the measurements — which is the honest reason it survived the g399
   audit. `.analysis-flag` is `display:flex; justify-content:space-between`, so "Open Workflow"
   takes its width off the text beside it. On the 1064px desktop report that is free; in a 359px
   settings pane it left "75 decision steps have no distinct downstream approver" wrapping inside
   roughly half the row while the button sat in the other half. The flags WITHOUT a button, right
   above it, read fine — which is what made it invisible to a scan that measured flags as a class.
   Nothing here overflowed and no rule was violated, so every number came back clean.
   Stacked, the finding gets the full width and the action sits under it at its natural size.
   Scoped to the pane's own container query: the wide report has room for the side-by-side and
   should keep it. */
@container setpane (max-width:520px){
  body.settings-docked .set-detail .analysis-modal .analysis-flag{
    flex-direction:column;align-items:stretch;gap:10px}
  body.settings-docked .set-detail .analysis-modal .analysis-flag>.btn{align-self:flex-start}
}

/* =====================================================================================
   THE PHONE CARD LABELS THE ONE FIELD THAT NEEDS IT (g403)
   =====================================================================================
   Desktop names every value with a column header. The phone card hides `thead` — correctly, a
   stacked card has no columns — so each value has to say what it is on its own. Most of them
   already do: the step number is a number, the role and step-type chips are visually distinct
   and carry their own words, the description is prose, and the icons are icons. Exactly one
   field is left holding a value whose meaning lived entirely in its column header: the following
   step, which renders as a bare "Step 4" pill sitting beside the edit tools with nothing saying
   what the 4 refers to.

   So one label, not six. Labelling all of them would restate what the chips already say and add
   six lines to a card that is 223px tall before anyone reads a word — and §5's whole argument
   about proximity is that a label earns its place by removing an ambiguity, not by being
   thorough. This one removes a real ambiguity; the others would be decoration.

   Eyebrow rung (§3): 11px / 500 / 0.12em uppercase, on `faint` so it reads under the value
   rather than competing with it. `:has()` keeps it off rows with no following step, where the
   cell holds only the edit tools and the label would name something absent. */
@media (max-width:720px){
  .page-card.wf-view tr.step-row > td:nth-child(6):has(.step-link, .next-end){
    display:block!important}
  /* One eyebrow recipe for every labelled cell, so the four cannot drift apart. */
  .page-card.wf-view tr.step-row > td:nth-child(1)::before,
  .page-card.wf-view tr.step-row > td:nth-child(2)::before,
  .page-card.wf-view tr.step-row > td:nth-child(3)::before,
  .page-card.wf-view tr.step-row > td:nth-child(4)::before,
  .page-card.wf-view tr.step-row > td:nth-child(5)::before,
  .page-card.wf-view tr.step-row > td:nth-child(6):has(.step-link, .next-end)::before{
    display:block;
    font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;
    color:var(--color-faint);margin-bottom:6px}
  .page-card.wf-view tr.step-row > td:nth-child(1)::before{content:"Step"}
  .page-card.wf-view tr.step-row > td:nth-child(2)::before{content:"Role"}
  .page-card.wf-view tr.step-row > td:nth-child(3)::before{content:"Step type"}
  /* The last two, on the owner's call for symmetry across the card. The icons cell needs its
     font-size restored on the pseudo-element alone: the cell itself sits at font-size:0 so the
     whitespace between inline icon glyphs collapses, and a label inheriting that would have
     rendered at zero and measured as present-but-invisible. */
  .page-card.wf-view tr.step-row > td:nth-child(4)::before{content:"Description"}
  .page-card.wf-view tr.step-row > td:nth-child(5)::before{content:"Icons";font-size:11px}
  .page-card.wf-view tr.step-row > td:nth-child(6):has(.step-link, .next-end)::before{
    content:"Following step"}
  /* The identity trio now carries label-over-value, so they hang from a shared top edge instead
     of being centred against each other — which is what makes the three read as columns of one
     header row rather than three unrelated chips. */
  .page-card.wf-view tr.step-row > td:nth-child(1),
  .page-card.wf-view tr.step-row > td:nth-child(2),
  .page-card.wf-view tr.step-row > td:nth-child(3){align-self:flex-start}
}

/* =====================================================================================
   THE ACTION BAR STOPS TOUCHING THE STEP CARD (g406)
   =====================================================================================
   Owner: "the draft steps button is touching the card where all the steps are." Measured across
   a continuous 320-1440 sweep: the gap is 0px at all 40 widths from 320 to 1100, and 16px only
   from 1120 up. It was never a narrow-screen compression — the separation simply did not exist
   below the desktop breakpoint.
   The action bar and the step table are two content GROUPS, so §5 gives gap-group: 32 desktop,
   24 below 768. The desktop 16 is itself under the token, but it is the shipped, looked-at value
   and raising it is a design change rather than a defect fix, so it stays and the narrow widths
   come up to 24 — which also satisfies invariant 5, since the actions sit 8px apart internally
   and 24 clears the required 2x. */
@media (max-width:1100px){
  .wf-view>.table-wrap{margin-top:24px}
}

/* =====================================================================================
   SPACING CONFORMANCE — the last three off-token values (g407)
   =====================================================================================
   Scanned every visible structural block at 1440 and 390 against §5's ramp (0/4/8/12/16/20/24/
   32/40/48/64/80, plus the badge's 3). Three values were off it, and only three — the contract
   is otherwise held across the app, which is worth recording because "everything feels tight"
   usually means many small violations and here it did not.

   · `.role-panel` margin-bottom 18. The Workflow Roles panel and the controls beneath it are
     separate content groups, so §5 gives gap-group — 32 desktop, 24 below 768. 18 was not on the
     ramp at any width, and it sat closer to gap-item than to gap-group, which is invariant 5
     backwards: groups must separate MORE than their members.
   · `.subpath-controls` row and column gap 10. Adjacent inline controls are gap-inline, 12, and
     that is a micro token — §5 invariant 1 says values of 12 and below are identical at every
     screen size, so there is no width at which 10 was the right answer.

   These are small numbers and the change is small. That is the point: the owner's report was that
   things feel close together, and the fix for that is the contract being kept, not a sweeping
   re-space that trades one set of invented values for another. */
.role-panel{margin-bottom:24px}
@media (min-width:1101px){ .role-panel{margin-bottom:32px} }
.subpath-controls{gap:12px}

/* =====================================================================================
   MODALS SIT ABOVE THE PHONE CHROME (g408)
   =====================================================================================
   Owner: "Getting Started doesn't really work — it's being covered by the header/footer."
   Measured, and it is every modal, not that one screen. `.modal-backdrop` is z-index 200. The
   three phone bars — title, action row and view tabs — are all z-index 300, so on a phone the
   app chrome has been painting OVER every dialog the app can open.

   The tell was that the guided tour works: `.coach-overlay` is 301, the only overlay in the app
   that happens to sit above 300. One number, and it is why one screen behaved and the rest did
   not.

   Raised above the bars on phones only. Desktop is deliberately untouched: there the toolbar is
   200 and the modal is 200, and the modal wins on DOM order because `#modalRoot` comes last —
   correct today, and not worth disturbing to fix a phone bug. */
@media (max-width:900px){
  .modal-backdrop{z-index:310}
  /* AND IT HAS TO FIT THE PHONE IT COVERS. Raising the modal above the bars stops the chrome
     painting over it; it does not stop the modal being taller than the screen. Measured at 390:
     the backdrop pads 14px top and bottom, the modal took the full 844 inside it, and the sum
     overflowed the viewport by 48px — so the last thing in every dialog, which is where Done and
     Save live, sat below the fold with nothing to scroll it into view.
     `svh` rather than `vh` deliberately: on iOS `vh` is the LARGEST viewport, measured with the
     browser chrome retracted, so a `vh`-sized sheet is exactly one toolbar too tall the moment
     the toolbar is showing — which is most of the time. `svh` is the small viewport and is the
     only one that is always true. */
  /* g409 — IT SITS BETWEEN THE BARS, not over them. g408 raised the modal above the chrome so it
     stopped being painted on; the owner's call is that it should not block the chrome either.
     Both are right, and the honest resolution is that a phone dialog is not full-screen here —
     it occupies the canvas, which is the band between the fixed header block and the tab bar.
     The bars stay visible and usable, and the z-index from g408 still matters: it keeps the
     dialog above anything else that shares the canvas.
     104 is the header block (48 title + 56 actions) and 52 the tab bar, both taken from the same
     constants `.app-shell` is padded with a few rules up — if those bars are ever resized these
     move with them, so they are written the same way rather than as new magic numbers. */
  .modal-backdrop{
    top:104px;bottom:calc(52px + env(safe-area-inset-bottom,0px));
    padding:12px 8px}
  .modal-backdrop>.modal{max-height:100%}
}

/* =====================================================================================
   THE COLOURED LEFT EDGE GOES FROM THE REVIEW SCREENS TOO (g413)
   =====================================================================================
   Owner, on the AI Review screen: the coloured border on the left of the warnings and checks has
   to go, the label is enough, and the colour and the warning symbol stay. Same call he made on
   the Analysis flags, which g399 removed for the same reason — and the same one g365 made on the
   AI data note before that. This is the third time the pattern has been found, so it is worth
   naming as a pattern rather than a third one-off: an accent stripe is a badge recipe wearing a
   container's clothes. §2 gives a container no border for being a container, and the four line
   exceptions are focus, engaged state, row dividers and the top-bar hairline. A severity stripe
   is none of them.

   Nothing is lost. Every one of these rows already carries its severity as a WORD — `.ai-issue`
   has its sev label, `.validate-row` its badge — so the stripe was a second encoding of a fact
   already stated, and the weaker of the two: a colour with no text is unreadable to anyone who
   cannot separate those hues, which is exactly what §2 means by status colour never being the
   only carrier.

   The rows keep their tone step (`surface2` on the pane) so they still read as items, and they
   keep their severity colour where it belongs — in the badge, on the §2 status recipe. The data
   note's 2px success edge goes with them; g365 removed its 35%-strength stripe and left a
   thinner one behind, which was half the argument. */
.ai-issue,
.validate-row.error,
.validate-row.warn{border-left:0!important}
.ai-data-note{border-left:0}
/* the tone step does the work the stripe was doing — these are items in a list, not bare text */
.ai-issue{background:var(--color-surface2)}

/* =====================================================================================
   THE STRUCTURAL-CHECK ROWS GET THEIR BOX BACK (g414)
   =====================================================================================
   Owner: after the stripe came off, the rows have nothing around them and read as too minimal —
   at least a soft line or a light box. Right, and the measurement says why: `.validate-row` is
   `rgb(18,18,18)` sitting on a page card that is also #121212. Identical. The stripe had been the
   only thing making a row visible as a row, so removing it left the tone problem it had been
   covering — exactly what happened to the Analysis flags at g399, and the same fix g368 applied
   to the Analysis panels before that.

   A tone step, not a border. §2's ladder is what separates nested surfaces, and `surface2` on a
   `surface` card is a real, measured rung rather than a hairline standing in for one. That also
   keeps the change consistent with every other list of items in the app, so the Review screen
   stops being the one place that draws its rows differently.

   The list gap goes 10 -> 12, gap-inline. 10 is on no ramp, and 12 is a micro token, so there is
   no width at which it should have been anything else. */
/* A HAIRLINE, NOT A TONE STEP — and the second choice on purpose. A `surface2` fill was tried
   twice, at two specificities, and measured `rgb(18,18,18)` both times: on this screen that token
   resolves to the same value as the card behind it, so the step does not exist to be taken. §2
   offers the other answer for exactly this case — row dividers are one of its four line
   exceptions, "in tables and in specification lists alike", because they carry the structure of
   the data rather than decorating a container. A list of structural findings is a specification
   list.
   On the row rather than between siblings, so the last row closes the list too.
   AND THE RADIUS GOES WITH IT. The row kept a card radius from when it had a fill, and a
   border-bottom on a rounded box curls up at both ends — the owner saw the line bending at its
   corners. A rule is a straight line; the curvature was the box's shape showing through an edge
   that was never meant to trace it. Nothing is lost by zeroing it, because the row has no visible
   fill on this screen for the radius to shape. */
.validate-list .validate-row{border-bottom:1px solid var(--color-line)}
.validate-list .validate-row:last-child{border-bottom:0}
.validate-list{gap:12px}

/* =====================================================================================
   FIX WITH AXI HAS TO LOOK PRESSABLE (g416)
   =====================================================================================
   Owner: it is not clear that it is a button. Measured, and it is worse than "unclear" — the
   control renders `background: transparent, border-color: transparent, color: #a3a3a3`. That is
   a Ghost at rest with muted text, sitting in a row that is `surface` on a card that is also
   `surface`. There is no fill, no edge and no tone step: nothing on screen says it can be
   pressed except the words themselves.

   The markup asks for `btn small primary` and an earlier rule un-primaries it to Secondary, and
   then something later flattens the border it was given. Three rules arguing produced a fourth
   thing none of them intended. Rather than add a fifth opinion at a higher specificity — which
   is the mistake g415 recorded — this states the whole resting appearance in one place.

   It takes the `control` fill: §2's own rung for interactive controls, and what §6 Inputs
   already specifies for every field on this screen. Measured that is #1C1C1C against the row's
   #121212, a real step, so the button reads as an object rather than as text with an invisible
   edge. It costs the view nothing — `control` is not `primary`, so §6's "a control that appears
   twenty times cannot be the one strong action" is untouched, which matters because this button
   is repeated on every finding. Hover takes the standard `hover` fill and an ink edge. */
.validate-fix,
.v-fixall,
[data-action="ai-fix-with-axi"]{
  background:var(--color-control)!important;
  border:1px solid var(--color-linestrong)!important;
  color:var(--color-ink)!important}
.validate-fix:hover,
.v-fixall:hover,
[data-action="ai-fix-with-axi"]:hover{
  background:var(--color-hover)!important;border-color:var(--color-ink)!important}

/* =====================================================================================
   ONE FOOTER GRAMMAR FOR EVERY SUB-PAGE (g420)
   =====================================================================================
   Owner, on the Project page and then generally: Cancel does nothing useful — it clears the
   screen and leaves the sub-menu open — so it goes; the danger action belongs hard left; the
   one button you are meant to press belongs hard right.

   CANCEL IS DIALOG LEGACY, exactly as Close was at g368. It is the right control in a dialog,
   where dismissing IS the escape. In a pane it is not an escape at all: you leave by pressing
   the back arrow, switching rail category, or closing the panel, and all three are on screen the
   whole time. What it actually did was discard the form and leave you standing in the same place
   with nothing changed, which reads as a no-op.
   Hidden rather than deleted from the markup, and scoped to `.set-detail`, because `#cancelModal`
   is the genuine dismiss on modals that open outside Settings. Same reasoning and same shape as
   the `.set-dismiss` rule above; the difference is that one keyed on a marker class and this one
   can key on the container, since a sub-page is only ever a sub-page inside this pane.

   LEFT AND RIGHT NOW MEAN SOMETHING. `.modal-actions` is `justify-content:flex-end`, so every
   control queued at the right and the destructive one sat immediately beside the committing one
   — the two that most need distance. An auto right-margin on the danger button pushes it to the
   far edge, so the row reads destructive-left, commit-right at a glance, and the gap between them
   is the whole width of the footer rather than a 10px gap.
   Appearance already did this by hand with an inline margin on Reset to Default; this makes the
   one-off the rule. */
.set-detail .modal-actions #cancelModal{display:none}
.set-detail .modal-actions .btn.danger{margin-right:auto}


/* =====================================================================================
   SELECTION DRINKS FROM THE BRAND (g421)
   =====================================================================================
   `--sel-fill` is defined by the theme layer ONLY when a brand colour is set (see the guard in
   applyTheme). Unbranded, this fallback resolves to `--color-control` and nothing changes — the
   monochrome default is untouched, which matters because most installs have no brand.
   Every engaged state points here instead of at the raw neutral, so "selection" is one decision
   in one place rather than four components each hardcoding a grey. */
:root{--sel-fill:var(--color-control)}
/* the Selector's travelling thumb — the view toggle AND the subpath filter share this control */
.modeseg::before{background:var(--sel-fill)}
/* the shell's panel toggles: Contents, Axi, Settings */
.app-shell:not(.toc-collapsed) .toolbar [data-action="toggle-toc"],
.app-shell:not(.toc-collapsed) .toolbar [data-action="toggle-toc"]:hover,
.app-shell.chat-open .toolbar [data-action="chat-toggle"],
.app-shell.chat-open .toolbar [data-action="chat-toggle"]:hover,
body.settings-open .toolbar [data-action="settings-open"],
body.settings-open .toolbar [data-action="settings-open"]:hover{
  background:var(--sel-fill)!important}
/* the settings rail's selected row carries its state as a fill (g379), so it is a selection too */
.setpanel .sr.is-open,.setpanel .sr[aria-expanded="true"]{background:var(--sel-fill)}
