/* ══ SIGNATURE ══════════════════════════════════════════════════════════════
   The plat hatch. Surveyors hatch a parcel to say which estate is drawn; the
   direction of the ruling is the convention that carries the meaning. Here it
   does the same job it does on a plat — it is never decoration.
     surface  ▨ ruled up-right     mineral  ▨ ruled down-right
     both     ▨ crosshatched
   ═════════════════════════════════════════════════════════════════════════ */
.hatch { --h:var(--estate-surface); --a:45deg;
  background-image:repeating-linear-gradient(var(--a),transparent 0 2px,var(--h) 2px 3px); }
.hatch-surface { --h:var(--estate-surface); --a:45deg; }
.hatch-mineral { --h:var(--estate-mineral); --a:-45deg; }
.hatch-both { --h:var(--estate-both);
  background-image:repeating-linear-gradient(45deg,transparent 0 2px,var(--h) 2px 3px),
                   repeating-linear-gradient(-45deg,transparent 0 2px,var(--h) 2px 3px); }

*,*::before,*::after { box-sizing:border-box; }
html,body { height:100%; }
body {
  margin:0; font-family:var(--font-sans); font-size:var(--text-base);
  line-height:var(--leading-normal); color:var(--text-primary);
  background:var(--bg-page); -webkit-font-smoothing:antialiased;
}
:focus-visible { outline:2px solid var(--border-focus); outline-offset:2px; border-radius:var(--radius-xs); }
:focus:not(:focus-visible) { outline:none; }
button,input,select { font:inherit; color:inherit; }
/* REDUCED MOTION REMOVES TRANSFORM AND POSITION ANIMATION. OPACITY AND COLOUR SURVIVE.
   The blanket `animation:none` below was producing the exact defect it sits next to: a delayed
   reveal (`.sh-wait`) is an OPACITY animation, so killing it froze the element at opacity:0
   permanently — a screen that never tells you it is working, which is what the wait indicator
   exists to fix. Reduced motion is a request not to be moved, not a request to be left staring at
   nothing.
   The blanket stays, because it protects against motion nobody has written yet. The exemption is
   explicit and opacity-only. */
@media (prefers-reduced-motion:reduce) {
  *,*::before,*::after { animation:none!important; transition:none!important; }
  .sh-wait { animation:sh-wait-in 1ms 400ms forwards!important; }
}

/* THE WAIT INDICATOR'S DELAY IS CSS, NOT A TIMER — so there is nothing to clear on a fast unmount.
   If the read resolves first the element is removed before it was ever visible, and no callback
   fires into a screen that has moved on.
   400ms even though both lists currently measure over 2s: a wait shown at 0ms flashes on every fast
   read, which is its own defect, and hard-coding "immediate" makes that flash the day the read gets
   faster. */
.sh-wait { opacity:0; animation:sh-wait-in 1ms 400ms forwards; }
@keyframes sh-wait-in { to { opacity:1; } }

/* ══ RECORD TYPE ════════════════════════════════════════════════════════════
   THE RULE: anything that appears on a recorded instrument is set in mono with
   tabular figures — tract IDs, volume/page, API numbers, bearings, distances,
   money, dates. Anything a person typed is sans. A landman scanning a runsheet
   is matching fixed-format strings, and mono is what makes the columns line up
   so a transposed digit is visible.
   ═════════════════════════════════════════════════════════════════════════ */
.rec { font-family:var(--font-mono); font-variant-numeric:tabular-nums;
       letter-spacing:-.01em; font-size:.95em; }
.num { font-variant-numeric:tabular-nums; }

/* ══ APP SHELL ═════════════════════════════════════════════════════════════ */
/* A rail item whose organization does not have that feature. Its own attribute with `!important`
   rather than `[hidden]` or inline display: `.rail-item` sets `display:flex`, which beats the
   user-agent rule for `[hidden]`, and inline display would fight the permission gate that owns its
   own hiding. See applyFeatureGate() in app.js — and note UNKNOWN is never hidden, only KNOWN OFF. */
.rail-item[data-feature-off] { display:none !important; }

/* HEIGHT MINUS THE BANNER, not a bare 100vh.
   sandbox-banner.js pads the BODY by the banner's height to push the shell clear of it. With
   `height:100vh` the shell then measured a full viewport BELOW that padding, so the document became
   viewport + banner tall and scrolled by exactly the banner's height — sliding the whole shell up
   behind it. Measured at 1500x900: banner 27px, body padding-top 27px, #app 900px, document
   scrollHeight 927 against a 900 viewport.

   Mike, debug session mtvxxawy-lhkswe: "when in the sandbox and the banner is on top the shell
   below will scroll up behind the banner... can we lock these so there is no scrolling of the shell
   behind the sandbox banner."

   `--sandbox-banner-h` is published by the sweep BEFORE it offsets anything, and republished when
   the banner wraps to two lines, so this tracks the wrap with no JS. It DEFAULTS TO 0px, so a page
   with no banner — production — resolves to a plain 100vh and is untouched. */
/* ── THE PANEL: PEEK AND PIN (Mike 2026-10-01; SH-UIUX BUILD-PACKAGE) ─────────────────────────────
   Mike: "when the mouse hovers over it pops out… once the mouse is no longer over, the side panel
   auto collapses… still have the ability to click the expand button and then it remains expanded and
   pushes the rest of the window over (the side panel does not overlap the working space)."

   THE ONE INVARIANT EVERYTHING HERE SERVES:
     --shell-inset  the space the SHELL reserves. Changes ONLY on pin.
     --rail-live    the panel's own width. Changes on peek, and NOTHING outside the rail may read it.
   The moment a topbar, a sticky header or the document sheet is positioned from the panel's live
   width, hovering the nav slides it. The sheet in live-shim.js was the one offender and now reads
   --shell-inset.

   THE GRID IS WHY A PEEK COSTS THE WORK SURFACE NOTHING. The rail is a grid COLUMN, so the surface is
   never offset by anything. A peek widens the rail ELEMENT past its column and paints over the
   surface; the column does not move. A pin widens the column.

     collapsed   column collapsed   rail collapsed   no shadow
     peeking     column unchanged   rail full        SHADOW — it is floating
     pinned      column full        rail full        no shadow — it is furniture

   THE SHADOW IS NOT DECORATION. It is the only thing that says "this is above your page and will go
   away" rather than "this is furniture now".

   THE DEFAULT IS UNCHANGED, DELIBERATELY. index.html's markup says data-rail="expanded", so out of
   the box a person is PINNED and sees exactly what they see today; the inline script collapses it
   only if they had collapsed it before. Nobody's shell changes without having asked. The cost is that
   the peek is discoverable only after collapsing once — flagged to SH-UIUX and Mike rather than
   decided here, because defaulting everyone to collapsed changes every existing person's screen.
   ─────────────────────────────────────────────────────────────────────────────────────────────── */
.app { display:grid; grid-template-columns:var(--shell-inset) 1fr;
       grid-template-rows:var(--topbar-h) 1fr;
       height:calc(100vh - var(--sandbox-banner-h, 0px)); overflow:hidden;
       /* On .app rather than :root so the attribute rules below override without !important. */
       --shell-inset:var(--rail-w-collapsed);
       --rail-live:var(--rail-w-collapsed);
       --panel-dur:200ms;
       --panel-ease:cubic-bezier(.22,.61,.36,1);
       transition:grid-template-columns var(--panel-dur) var(--panel-ease); }

/* PINNED. `data-rail="expanded"` is the EXISTING attribute and the existing control already writes it,
   so somebody with the rail expanded today is simply pinned after this change. Nothing to migrate. */
.app[data-rail="expanded"] { --shell-inset:var(--rail-w); --rail-live:var(--rail-w); }

/* PEEKING — its own attribute, NEVER data-rail. A MutationObserver in live-shim re-fits every anchored
   field whenever data-rail changes; a 60ms hover must not trigger that. */
.app[data-panel="peeking"] { --rail-live:var(--rail-w); }

.rail { width:var(--rail-live); position:relative; z-index:var(--z-chrome);
        transition:width var(--panel-dur) var(--panel-ease),
                   box-shadow var(--panel-dur) var(--panel-ease); }
/* --shadow-popover, NOT --shadow-float. The package named `--shadow-float` and there is no such token:
   tokens.css has --shadow-card, --shadow-popover and --shadow-modal, each defined twice so it is right
   on the light ground and on the dark one. An undefined custom property makes the whole declaration
   invalid at computed-value time, so box-shadow fell back to `none` and the peek had NO shadow —
   caught only because the acceptance test asserts the shadow in BOTH states, which is exactly why
   SH-UIUX required the pair: "either alone passes a build that never changes it".
   POPOVER is the right one of the three on its meaning, not just because it is in the middle. A peek
   is a transient floating above content and going away again; a card is furniture and a modal owns the
   screen. */
.app[data-panel="peeking"] .rail { box-shadow:var(--shadow-popover); }

/* Labels fade rather than being clipped mid-glyph while the width animates.
   .rail-scope IS NOT IN THE FADED SET ANY MORE, AND THIS WAS MIKE'S BUG (2026-10-02, "I still dont
   see ... what scope they are currently in").
   It belonged here when collapsing made the scope block meaningless. It now carries the ONLY thing
   that tells a person which scope they are in, and this rule was fading that to nothing.
   WHY IT SURVIVED THREE ENGINES OF GREEN TESTS: opacity:0 on a PARENT leaves the child perfect.
   The label kept display:flex, visibility:visible, opacity:1, a 49x32 box, white on a .12 fill at
   14.67:1, nothing clipping it, and elementFromPoint still returned it as topmost — an opacity:0
   ancestor does not stop hit-testing. Every property I could read about the element was true and the
   element was invisible. A 4x crop of its own rectangle came back flat navy, which is what finally
   settled it. The buttons do not need the fade: collapsed takes them out with display:none. */
.rail .lbl, .rail-scope, .rail-sec { transition:opacity 130ms var(--panel-ease); }
.app:not([data-rail="expanded"]):not([data-panel="peeking"]) .rail .lbl,
.app:not([data-rail="expanded"]):not([data-panel="peeking"]) .rail-sec { opacity:0; }

@media (prefers-reduced-motion: reduce) {
  .app, .rail, .rail .lbl, .rail-scope, .rail-sec { transition-duration:1ms !important; }
}

/* ── top bar ─────────────────────────────────────────────────────────────── */
.topbar {
  grid-column:1/-1; display:flex; align-items:center; gap:var(--space-5);
  padding:0 var(--space-6); background:var(--bg-banner);
  border-bottom:1px solid rgba(255,255,255,.08);
  /* CHROME, the same rung as the rail (UIUX z-ladder §1.1). It read --z-banner (250), which put it
     ABOVE transients at 150 — so a popover opening near the top bar was drawn underneath it. No tie
     with shared.css's .banner: that rule is in the auth-page family and the app does not load it. */
  z-index:var(--z-chrome);
}
.brand { display:flex; align-items:center; gap:var(--space-4); min-width:0;
         flex:0 0 auto; text-decoration:none; }
.brand-mark { width:26px; height:26px; border-radius:var(--radius-sm);
  background:linear-gradient(160deg,var(--blue-500),var(--blue-800)); flex:0 0 auto;
  display:grid; place-items:center; }
.brand-mark svg { width:15px; height:15px; stroke:#fff; fill:none; stroke-width:1.9;
                  stroke-linecap:round; }
.brand-name { font-size:var(--text-md); font-weight:var(--weight-bold); color:#fff;
  letter-spacing:.055em; text-transform:uppercase; white-space:nowrap; }

.org-switch { display:flex; align-items:center; gap:var(--space-3);
  padding:var(--space-3) var(--space-5); border-radius:var(--radius-btn);
  background:rgba(255,255,255,.07); border:1px solid rgba(255,255,255,.10);
  color:#dbe4ed; font-size:var(--text-sm); font-weight:var(--weight-medium);
  cursor:pointer; white-space:nowrap; min-height:var(--target-min-dense);
  transition:background var(--dur-fast); }
.org-switch:hover { background:rgba(255,255,255,.12); }
/* THE ONE-ORGANIZATION CASE IS A LABEL, NOT A CONTROL. It keeps `.org-switch` so it sits in the top
   bar identically — same size, same alignment, no layout shift for a person who later joins a second
   organization — and gives up everything that says "press me". A disabled button was the defect this
   replaces: inert, but still shaped like a control. */
.org-switch-label { cursor:default; background:transparent; border-color:transparent; }
.org-switch-label:hover { background:transparent; }
.org-switch svg { width:12px; height:12px; stroke:currentColor; fill:none; stroke-width:2.2; opacity:.7; }

.topsearch { flex:1 1 auto; max-width:440px; position:relative; min-width:0; }
.topsearch input {
  width:100%; padding:var(--space-4) var(--space-5) var(--space-4) 34px;
  background:rgba(255,255,255,.07); border:1px solid rgba(255,255,255,.10);
  border-radius:var(--radius-input); color:#fff; font-size:var(--text-sm);
  min-height:var(--target-min-dense); }
.topsearch input::placeholder { color:#8ba0b4; }
.topsearch input:focus { background:rgba(255,255,255,.13); border-color:var(--blue-500); outline:none; }
.topsearch svg { position:absolute; left:11px; top:50%; transform:translateY(-50%);
  width:14px; height:14px; stroke:#8ba0b4; fill:none; stroke-width:2; pointer-events:none; }
.topsearch kbd { position:absolute; right:8px; top:50%; transform:translateY(-50%);
  font-family:var(--font-mono); font-size:var(--text-2xs); color:#8ba0b4;
  border:1px solid rgba(255,255,255,.16); border-radius:var(--radius-xs); padding:1px 5px; }

.topbar-right { margin-left:auto; display:flex; align-items:center; gap:var(--space-4); flex:0 0 auto; }

/* ── THE SEARCH BOX: TRULY CENTRED, SHRINKS TO FIT, AND STEPS INTO THE FREE SPACE WHEN THERE IS NO ROOM LEFT IN THE MIDDLE
   (Mike, 2026-09-16: "i want the search bar pinned in the center of the top"; then option B with a narrower ticker).
   TRUE CENTRE is the centre of the top bar, not the middle of the space between the two groups — those differ whenever the groups differ,
   and the left group carries the organization's NAME, so a box centred in the leftover space slides when you switch organizations.
   MEASURED BEFORE BUILDING (platform admin, the widest right-hand group): at 1440 the right group started at 794px, so a 440px box centred
   at 720 sat on top of it; at 1280 the right group reached PAST the centre. Three modes, chosen by js/platform-topbar.js (fitTopSearch)
   from the room it measures, because CSS cannot see how wide the groups are:
     data-fit="full" | "compact"   centred, width = the room to the NEARER group, doubled, capped at 440 (compact drops the key hint)
     data-fit="flow"               not enough room to be centred and useful (<160px): sits in the free space between the groups instead
     data-fit="measure"            momentary, inside one frame: centred at zero width so the groups can be measured without the box
   <= 900px is untouched: the phone rules own that width. */
@media (min-width:901px) {
  .topbar { position:relative; }
  .topsearch { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
    width:var(--topsearch-w, 440px); max-width:none; flex:none; }
  .topsearch[data-fit="compact"] kbd { display:none; }
  .topsearch[data-fit="flow"] { position:relative; left:auto; top:auto; transform:none; width:auto; flex:1 1 auto; max-width:440px;
    min-width:120px; margin:0 auto; }
  .topsearch[data-fit="flow"] kbd { display:none; }
}
/* THE TICKER GIVES WAY BELOW 1600px (Mike, 2026-09-16). It is 340px and was the largest thing in the right-hand group; at 220 — the width it
   already takes below 1100 — the centred box gains about 240px on a 1440 screen. Written HERE, not in live-shim.js or ticker.js (not this
   session's files): `.topbar #ogTicker` outranks live-shim's injected `#ogTicker{max-width:340px}` by specificity, so the order the two
   stylesheets load in does not matter. */
@media (min-width:901px) and (max-width:1599px) {
  .topbar #ogTicker { max-width:220px; flex-basis:220px; }
}
.icon-btn { width:32px; height:32px; display:grid; place-items:center; cursor:pointer;
  border-radius:var(--radius-btn); border:1px solid rgba(255,255,255,.10);
  background:rgba(255,255,255,.06); color:#c5d3e0; transition:background var(--dur-fast); }
.icon-btn:hover { background:rgba(255,255,255,.13); }
.icon-btn svg { width:15px; height:15px; stroke:currentColor; fill:none; stroke-width:2; }
/* Role is STATED, not chosen. There is no user↔admin switch — the panel merge
   removed it. This reads the same value applyPermGate() stamps on <html>. */
.role-pill { font-size:var(--text-2xs); font-weight:var(--weight-semibold);
  letter-spacing:var(--tracking-wide); text-transform:uppercase;
  padding:2px var(--space-4); border-radius:var(--radius-pill);
  border:1px solid rgba(255,255,255,.18); color:#c5d3e0; white-space:nowrap; }
.role-pill[data-perm="developer"] { background:var(--ai-rail); border-color:transparent; color:#fff; }
/* Super admin is the top of an ORGANIZATION; developer is outside every organization.
   Two ranks, two looks — one badge for both would hide the boundary that matters. */
.role-pill[data-perm="super_admin"] { background:var(--accent-700); border-color:transparent; color:var(--text-on-accent); }
.role-pill[data-perm="user"] { display:none; }   /* the common case needs no label */
/* Ghosting is a different org's data under your account. It must never be quiet. */
/* THE GHOST PILL GETS THE RING, FOR THE REASON THE RAIL BADGE DID (UIUX z-ladder §0 + §1.3).
   UIUX asked for the alert fill measured against the grounds it sits on, and ringed if any is under
   3:1. Measured: this pill sits on the TOP BAR (--bg-banner), and there the fill is 2.70:1 in LIGHT
   theme — the worst case of any ground, and under the bar. Dark is 3.63:1.

   It is not a hypothetical ground. --bg-subtle is the worst number in the dark theme (2.73:1) but no
   alert fill is ever drawn on it; the top bar is where this one actually lives. And this is the pill
   that says a platform admin is acting as someone else, which is the same fact §1.3 rules must never
   be covered — too faint to notice is the quieter version of covered.

   A RING, NOT A SECOND RED, as ruled: one alert red, made legible where it lands. Same 1.5px white
   ring SH - Debug used on the rail badge, so the two read as one device. */
.role-pill[data-ghost="1"] { background:var(--alert-bg); color:var(--alert-fg); border-color:transparent;
  box-shadow:0 0 0 1.5px var(--rail-text-active); }

.avatar { width:28px; height:28px; border-radius:var(--radius-pill);
  background:var(--blue-600); color:#fff; display:grid; place-items:center;
  font-size:var(--text-2xs); font-weight:var(--weight-bold); letter-spacing:.03em; }

/* ── left rail ───────────────────────────────────────────────────────────── */
.rail { background:var(--bg-rail); border-right:1px solid rgba(255,255,255,.07);
  display:flex; flex-direction:column; overflow-y:auto; overflow-x:hidden;
  padding:var(--space-5) 0 var(--space-6); }
.rail::-webkit-scrollbar { width:6px; }
.rail::-webkit-scrollbar-thumb { background:rgba(255,255,255,.13); border-radius:3px; }

.rail-scope { display:flex; margin:0 var(--space-5) var(--space-5); padding:2px;
  background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.10);
  border-radius:var(--radius-btn); }
.rail-scope-btn { flex:1; min-width:0; padding:var(--space-3) var(--space-4); border:0;
  background:none; border-radius:var(--radius-sm); font-family:inherit; font-size:var(--text-sm);
  font-weight:var(--weight-medium); color:var(--rail-text); cursor:pointer;
  min-height:var(--target-min-dense); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  transition:background var(--dur-fast),color var(--dur-fast); }
.rail-scope-btn:hover { color:var(--rail-text-active); }
/* the collapsed-rail scope label: present in the markup always, shown only when collapsed */
.rail-scope-tag { display:none; }
/* the short word is for the eye; the full one is the only text an assistive reader is given, so
   it says WHICH AXIS the word belongs to — "Me" alone on a nav rail does not. */
.rail-sr { position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
.rail-scope-btn[aria-pressed="true"] { background:var(--bg-rail-active);
  color:var(--rail-text-active); font-weight:var(--weight-semibold); }
/* ── the phone's in-page view of the scope toggle (js/scope-flow.js) ─────────────────────────
   Page tokens, not rail tokens: it sits on the page surface, in both themes. The row is zero-height and lives INSIDE the scroller, so the
   view keeps the floating toggle's resting place and leaves with the content. NO ELLIPSIS: a control's label is never chopped — `…`
   means a dialog follows in this product. */
.scope-flow-row { position:relative; height:0; z-index:1; }
.scope-flow { display:inline-flex; padding:2px; background:var(--bg-input); border:1px solid var(--border); border-radius:var(--radius-btn); }
.scope-flow[data-where="page"] { position:absolute; top:var(--space-4); right:var(--gutter-phone); }
.scope-flow[data-where="tab"] { margin-left:auto; }
.scope-slot { display:flex; justify-content:flex-end; margin:var(--space-3) 0; }
.scope-slot:empty { margin:0; }   /* still laid out (height 0): scope-flow.js finds a slot by its being on screen */
.scope-flow-btn { flex:0 0 auto; padding:var(--space-3) var(--space-4); border:0; background:none; border-radius:var(--radius-sm);
  font-family:inherit; font-size:var(--text-sm); font-weight:var(--weight-medium); color:var(--text-secondary); cursor:pointer;
  min-height:var(--target-min-dense); white-space:nowrap; }
.scope-flow-btn[aria-pressed="true"] { background:var(--bg-card); color:var(--text-primary); font-weight:var(--weight-semibold);
  box-shadow:0 0 0 1px var(--border); }
/* Collapsed: one letter each, still two targets. */
/* COLLAPSED: ONE INDICATOR, AT THE EXPANDED ROW'S HEIGHT (Mike, 2026-10-01: "when the shell panel
   is collapsed i want the top to show which scope is selected mine or org and then the icons
   immediately below that so that they are in the exact same vertical location as if the side panel
   were expanded").

   It was `flex-direction:column` with BOTH buttons stacked — measured 70px against the expanded row's
   38px, which pushed every nav icon 32px lower. Showing only the SELECTED scope keeps the row, keeps
   the height, and says which one is on, which is what he asked to see.

   THE SWITCH IS NOT LOST: hovering peeks, and a peek shows both buttons with their full labels. The
   remaining button stays a real control rather than becoming a label, so a click on it is a no-op
   re-selection rather than a dead element. */
/* ONLY THE SELECTED SCOPE, AND IT IS AN INDICATOR, NOT A SWITCH (Mike, 2026-10-01: "yes you can
   remove the ability to change scope but i want the current selected scope to at least show").

   I had built this and reverted it once, because hiding the unselected button removes the ability to
   change scope while collapsed — which it does, and Mike has now spent that deliberately. What he
   wanted was to SEE which scope is on; switching happens on hover, where both buttons return with
   their full labels, or when pinned.

   The remaining button keeps its pressed styling (white on a lighter ground, aria-pressed="true"), so
   it still reads as the selected one of a pair rather than as a stray label. It is left clickable: a
   click re-selects the scope already selected, which is a no-op, and that is less surprising than a
   focusable button with pointer-events removed.

   The row stays 38px — matching the expanded block — which is what keeps every icon below it aligned.
   THE TEST HELPER KNOWS: lib-mutate-read.js `useOrgScope` clicks this control to set up almost every
   suite, and it now expands the rail first. Hiding a control that a hundred suites drive is the kind
   of change that breaks them all at once. */
/* ── THE COLLAPSED SCOPE IS A LABEL, NOT A BUTTON (Mike + UIUX collapsed-rail §2) ───────────────
   Mike ruled that you change scope only from the open panel and that the collapsed rail must still
   SHOW which scope you are in. The first build did that by hiding the unselected button, which left
   a lone button in its pressed styling that does nothing when pressed — a control that lies, and one
   people will click. So collapsed draws the selected scope as static text: the same short word in
   the same place, the selected look kept so it reads as the current state, no hover, no pointer
   cursor, and out of the tab order — because the buttons are `display:none` and not `disabled`,
   which would leave them announced.

   WHICH WORD SHOWS IS DERIVED, NEVER PAINTED. Four places already set aria-pressed on these buttons
   (index.html's pre-paint script, and app.js's load, clamp and choose). A fifth painter for the
   label would be a fifth chance to drift out of step, so `:has()` reads the attribute those four
   maintain and no JS is added at all.

   `display:none` DOES NOT BREAK THE LENT TOGGLE. opinion-record's _revokeOrgToggle() hands scope
   back by calling .click() on the Mine button, and a programmatic click still dispatches on a hidden
   element — only a pointer cannot reach it. Asserted in live-shell-panel.js, because if it ever
   stopped a non-admin would keep organization scope with no control to leave it. */
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-scope { margin:0 2px var(--space-5); }
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-scope-btn { display:none; }
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-scope:has(.rail-scope-btn[data-scope="mine"][aria-pressed="true"]) .rail-scope-tag[data-scope-tag="mine"],
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-scope:has(.rail-scope-btn[data-scope="org"][aria-pressed="true"]) .rail-scope-tag[data-scope-tag="org"] {
  display:flex; align-items:center; justify-content:center;
  flex:1; min-width:0; text-align:center; cursor:default;
  /* THE SAME BOX THE BUTTON HAD, and `min-height` is the half that matters. The first version set
     only the padding and came out 3px shorter (35 vs 38), which lifted every nav icon below it by
     1-4px — the compounding drift Mike had already had fixed once. The button's floor was
     --target-min-dense, so the label carries it too. */
  min-height:var(--target-min-dense); box-sizing:border-box;
  padding:var(--space-3) 0; font-size:var(--text-2xs);
  font-weight:var(--weight-semibold); border-radius:var(--radius-sm);
  background:var(--bg-rail-active); color:var(--rail-text-active); }

.rail-group { padding:0 var(--space-5); margin-bottom:var(--space-3); }
.rail-label { font-size:var(--text-2xs); font-weight:var(--weight-semibold);
  letter-spacing:var(--tracking-wide); text-transform:uppercase; color:var(--rail-label);
  padding:var(--space-5) var(--space-4) var(--space-3); }
/* THE SECTION HEADING KEEPS ITS BOX AND LOSES ITS WORDS. It was crushed to `height:1px`, which is
   34px of lost height per heading — and the drift compounds down the rail. `color:transparent` with the
   EXPANDED padding holds the space, so every icon below it sits where it sits when expanded. The rule
   that reserves a height by reusing the real box rather than a number, again. */
/* The heading keeps its BOX when collapsed — that is what holds the icons below it at the same
   pixel — and loses only its text, becoming the divider between groups.
   AN INSET SHADOW, NOT A BORDER, AND THAT IS THE WHOLE POINT. The first version drew the divider
   with `border-top:1px`, which made each heading 1px TALLER collapsed than expanded (35.05 vs
   36.05, measured). With four headings down the rail that is the cumulative drift Mike reported,
   reintroduced by the fix for it: Chromium and WebKit rounded most of it away and came in inside
   tolerance, Firefox — the browser Mike uses — stacked it to 3px at the bottom icon.
   An inset shadow paints the identical 1px line and takes part in no layout at all. */
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-label { color:transparent;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08); }

.rail-item { display:flex; align-items:center; gap:var(--space-5); width:100%;
  padding:var(--space-4) var(--space-5); border:0; background:none; cursor:pointer;
  border-radius:var(--radius-btn); color:var(--rail-text); font-size:var(--text-base);
  font-weight:var(--weight-medium); text-align:left; min-height:var(--target-min-dense);
  position:relative; transition:background var(--dur-fast),color var(--dur-fast); }
.rail-item:hover { background:var(--bg-rail-hover); color:var(--rail-text-active); }
.rail-item.active { background:var(--bg-rail-active); color:var(--rail-text-active);
  font-weight:var(--weight-semibold); }
/* the hatch marks the active view — same ruling a plat uses for the parcel in hand */
.rail-item.active::before { content:""; position:absolute; left:0; top:6px; bottom:6px; width:3px;
  border-radius:0 2px 2px 0; --h:var(--blue-400); --a:45deg;
  background-image:repeating-linear-gradient(45deg,transparent 0 2px,var(--blue-400) 2px 3px); }
.rail-item svg { width:16px; height:16px; stroke:currentColor; fill:none; stroke-width:1.9;
  stroke-linecap:round; stroke-linejoin:round; flex:0 0 auto; }
.rail-item .lbl { flex:1 1 auto; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.rail-count { font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:var(--text-2xs); color:var(--rail-label); flex:0 0 auto; }
.rail-item.active .rail-count { color:var(--blue-400); }
.rail-count.alert { color:var(--alert-fg); background:var(--alert-bg); border-radius:var(--radius-pill);
  padding:1px 6px; font-weight:var(--weight-bold); }

/* A BADGE'S FILL AND ITS TEXT NEVER CHANGE WITH THE STATE OF THE ITEM IT SITS ON
   (SH-UIUX, 2026-10-01, answering Mike's session muprguqo-c6ovs6: "the '1' message bubble for the
   dashboard tab... when not clicked on it the number shows white... when i click the dashboard tab it
   turns grey... i want it to be/stay white dont turn grey there").

   IT WAS A SPECIFICITY LOSS, not a colour choice. `.rail-item.active .rail-count` is (0,3,0) and
   `.rail-count.alert` is (0,2,0), so selecting the tab replaced the bubble's white `--alert-fg` with
   `--blue-400`. Nothing intended the bubble to change; the active rule was written for a plain count
   and caught the pill as well.

   RULED AS A BADGE RULE, NOT A FIX FOR THE DASHBOARD TAB. SH-UIUX: "A one-off for the dashboard tab
   would leave the next badge broken the same way." So every state the rail can put an item in is named
   here, and `opacity` is pinned too — a parent that dims itself would otherwise dim the badge with it,
   which looks like a different colour and is not one. */
/* AND THE EDGE IS A RING, NOT THE FILL (SH-UIUX, 2026-10-01, after the ratios came back).
   A red pill on the navy rail is 2.70:1 resting, 2.20 on hover and 1.87 on active, and NO lighter red
   fixes it: white text at 4.5:1 caps the fill's luminance, the active ground's 3:1 sets a floor above
   that cap, and there is no red in between. So the thing that has to be seen is a white edge, measured
   at 14.67:1 resting, 11.94 hover, 10.13 active — arithmetic I checked against theirs rather than
   taking on trust.

   `box-shadow` TAKES PART IN NO LAYOUT, so the pill does not change size and the rail's geometry is
   untouched — the same invariant as the heading fix.

   THE RING IS IN EVERY STATE, not just the active one: "a ring that appears when the item is selected
   is a badge that changes with its item's state", which is the thing this rule exists to stop.

   ONE PAIR IN BOTH THEMES, because the rail is navy in both. `--rail-badge-*` are SH - Build's to
   declare once in `:root` as part of their rail cleanup. The fallback here is the ALERT PAIR — not a
   copy of the hex — so that until those tokens land the badge keeps exactly today's colours instead of
   losing its background to an unresolved var. Confirmed from the comment-stripped tokens.css: the
   alert pair is declared only in `:root`, with no live `[data-theme]` override, so today's render is
   #ffffff on #c0392f in both themes and the fallback is the same pair the ruling chose. */
.rail-item .rail-count.alert,
.rail-item:hover .rail-count.alert,
.rail-item:focus-visible .rail-count.alert,
.rail-item.active .rail-count.alert,
.rail-item.active:hover .rail-count.alert {
  color:var(--rail-badge-fg);
  background:var(--rail-badge-bg);
  box-shadow:0 0 0 1.5px var(--rail-text-active);
  opacity:1;
}
/* THE FALLBACK IS GONE ON PURPOSE. It read `var(--rail-badge-fg, var(--alert-fg))` while SH - Build's
   tokens were pending, so the pill could not lose its background between two deploys. Both are now
   declared once in `:root` — verified from the comment-stripped tokens.css — and a second source for
   the colour would mean deleting the token silently reverts the rail badge to the page's alert pair
   instead of breaking visibly. One declaration, one reader. Same reasoning SH - Build used to drop the
   `, 30` fallback on `--z-chrome` once that token existed. */
/* THE LABEL KEEPS ITS HEIGHT AND LOSES ITS WIDTH, which is what makes the icons line up.

   `--target-min-dense` is 32px, and an expanded item is 38px because the LABEL's line box is taller
   than the 16px icon. `display:none` on the label therefore dropped every collapsed item to the 32px
   floor — 6px per item, compounding down the rail: measured drift 0, -6, -12, -16, -22, -27.

   So the label stays in the layout as a zero-width flex item with its text hidden. It still generates
   one line box (it is already `white-space:nowrap; overflow:hidden`), so the item is exactly as tall
   as it is when expanded — BY CONSTRUCTION, not by hard-coding 38px, which would go stale the first
   time the type scale moves. The same rule as the reserved filing cards and the section heading above.

   `gap:0` because a zero-width label is still a flex item: the 12px gap between it and the icon would
   push the icon 6px left of centre, which is precisely the off-centre Mike has reported twice. */
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-item { justify-content:center; padding-left:0; padding-right:0; gap:0; }
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-item .lbl,
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-item .rail-count { flex:0 0 0; width:0; min-width:0; visibility:hidden; }

.rail-rule { height:1px; background:rgba(255,255,255,.09);
  margin:var(--space-4) var(--space-5); }
/* Narrower, not shorter — the vertical margin matches the expanded rule. */
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-rule { margin-left:var(--space-3); margin-right:var(--space-3); }

.rail-foot { margin-top:auto; padding:var(--space-5) var(--space-5) 0; }
.rail-toggle { display:flex; align-items:center; gap:var(--space-5); width:100%;
  padding:var(--space-4) var(--space-5); border:0; background:none; cursor:pointer;
  color:var(--rail-label); font-size:var(--text-sm); border-radius:var(--radius-btn); }
.rail-toggle:hover { background:var(--bg-rail-hover); color:var(--rail-text); }
.rail-toggle svg { width:15px; height:15px; stroke:currentColor; fill:none; stroke-width:2; }

/* THE CARET SITS ON THE SIDE THE WORD POINTS (Mike, 2026-10-02): right of "Pin expanded", left of
   "Collapse" — and Collapse is unchanged, which is why this keys on the state rather than moving the
   element. shell-panel.js writes "Pin expanded" whenever data-rail is not expanded and "Collapse"
   when it is, so the same attribute that chooses the word chooses the side. `order` rather than a DOM
   move: the button's markup stays one shape, and the icon never leaves the accessible name's way.

   COLLAPSED-AND-NOT-PEEKING HAS NO WORD AT ALL — the label is display:none there and the icon is
   centred — so this only shows itself during a peek, which is the one place "Pin expanded" is read. */
.app:not([data-rail="expanded"]) .rail-toggle svg { order: 2; }
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-toggle .lbl { display:none; }
[data-rail="collapsed"]:not([data-panel="peeking"]) .rail-toggle { justify-content:center; }
/* THE CARET POINTS WHERE THE PANEL IS ABOUT TO GO (Mike, 2026-10-02: "needs to be facing the other
   way"). It was rotated only in the collapsed-and-not-peeking state — where there is no word beside
   it at all — so during a PEEK it sat to the right of "Pin expanded" still pointing back inward, at
   the word rather than at the movement. The condition now matches the one that puts it on the right,
   so the two are one decision: not expanded → on the right, pointing out; expanded → on the left,
   pointing in, which is Collapse and is unchanged, as Mike asked. */
.app:not([data-rail="expanded"]) .rail-toggle svg { transform:rotate(180deg); }

/* ── main ────────────────────────────────────────────────────────────────── */
.main { overflow-y:auto; background:var(--bg-page); }
/* When the plotter is mounted the pane must not scroll — the CAD surface fills it
   exactly and owns its own panning. */
.main:has(> .is-plotter) { overflow:hidden; }
.page { padding:var(--space-7) var(--gutter) var(--space-11); max-width:1500px; }

.crumbs { display:flex; align-items:center; gap:var(--space-3); font-size:var(--text-sm);
  color:var(--text-muted); margin-bottom:var(--space-5); flex-wrap:wrap; }
.crumbs a { color:var(--text-muted); text-decoration:none; border-radius:var(--radius-xs); }
.crumbs a:hover { color:var(--accent-500); text-decoration:underline; }
.crumbs svg { width:12px; height:12px; stroke:currentColor; fill:none; stroke-width:2; opacity:.5; }
.crumb-eg { margin-left:var(--space-5); padding-left:var(--space-5);
  border-left:1px solid var(--border); font-size:var(--text-xs);
  color:var(--text-muted); font-style:italic; }

.page-head { display:flex; align-items:flex-start; gap:var(--space-6);
  margin-bottom:var(--space-7); flex-wrap:wrap; }
.page-title { font-size:var(--text-2xl); font-weight:var(--weight-bold);
  letter-spacing:var(--tracking-tight); margin:0; line-height:var(--leading-tight); }
.page-sub { font-size:var(--text-sm); color:var(--text-muted); margin:var(--space-2) 0 0; }
.page-actions { margin-left:auto; display:flex; gap:var(--space-4); flex-wrap:wrap; }

.btn { display:inline-flex; align-items:center; gap:var(--space-3);
  padding:var(--space-4) var(--space-6); border-radius:var(--radius-btn);
  border:1px solid var(--border); background:var(--bg-card); color:var(--text-primary);
  font-size:var(--text-sm); font-weight:var(--weight-semibold); cursor:pointer;
  min-height:var(--target-min-dense); white-space:nowrap;
  transition:background var(--dur-fast),border-color var(--dur-fast); }
.btn:hover { background:var(--bg-subtle); border-color:var(--border-strong); }
.btn svg { width:14px; height:14px; stroke:currentColor; fill:none; stroke-width:2.2; }
.btn-primary { background:var(--accent-700); border-color:var(--accent-700); color:#fff; }
.btn-primary:hover { background:var(--blue-600); border-color:var(--blue-600); }

/* ── scope switch (My / Org) — kept, moved out of the tab strip ──────────── */
.scope { display:inline-flex; background:var(--bg-subtle); border:1px solid var(--border);
  border-radius:var(--radius-btn); padding:2px; }
.scope button { padding:var(--space-3) var(--space-5); border:0; background:none;
  border-radius:var(--radius-sm); font-size:var(--text-sm); font-weight:var(--weight-medium);
  color:var(--text-secondary); cursor:pointer; min-height:var(--target-min-dense); }
.scope button[aria-selected="true"] { background:var(--bg-card); color:var(--text-primary);
  font-weight:var(--weight-semibold); box-shadow:var(--shadow-card); }

/* ── saved views + filters ───────────────────────────────────────────────── */
.viewbar { display:flex; align-items:center; gap:var(--space-4); margin-bottom:var(--space-5);
  flex-wrap:wrap; }
.chip { padding:var(--space-3) var(--space-5); border-radius:var(--radius-pill);
  border:1px solid var(--border); background:var(--bg-card); color:var(--text-secondary);
  font-size:var(--text-sm); font-weight:var(--weight-medium); cursor:pointer;
  min-height:var(--target-min-dense); display:inline-flex; align-items:center; gap:var(--space-3);
  transition:background var(--dur-fast),border-color var(--dur-fast); }
.chip:hover { border-color:var(--border-strong); }
.chip[aria-pressed="true"] { background:var(--accent-700); border-color:var(--accent-700);
  color:#fff; font-weight:var(--weight-semibold); }
.chip .n { font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:var(--text-2xs); opacity:.75; }

.filters { display:flex; gap:var(--space-4); align-items:center; margin-bottom:var(--space-6);
  flex-wrap:wrap; }
.field { position:relative; }
.field input,.field select {
  padding:var(--space-4) var(--space-5); padding-left:32px; min-width:200px;
  border:1px solid var(--border); border-radius:var(--radius-input);
  background:var(--bg-card); font-size:var(--text-sm); min-height:var(--target-min-dense); }
.field select { padding-left:var(--space-5); min-width:150px; cursor:pointer; }
.field svg { position:absolute; left:11px; top:50%; transform:translateY(-50%);
  width:14px; height:14px; stroke:var(--text-muted); fill:none; stroke-width:2; pointer-events:none; }
.result-count { margin-left:auto; font-size:var(--text-sm); color:var(--text-muted); }
.result-count b { color:var(--text-primary); font-family:var(--font-mono);
  font-variant-numeric:tabular-nums; font-weight:var(--weight-semibold); }

/* ── table ───────────────────────────────────────────────────────────────── */
.tablecard { background:var(--bg-card); border:1px solid var(--border);
  border-radius:var(--radius-card); box-shadow:var(--shadow-card); overflow:hidden; }
.tablescroll { overflow-x:auto; -webkit-overflow-scrolling:touch; }
table { width:100%; border-collapse:separate; border-spacing:0; }
thead th { position:sticky; top:0; z-index:2; background:var(--bg-sunken);
  line-height:var(--leading-tight);
  font-size:var(--text-xs); font-weight:var(--weight-semibold); color:var(--text-secondary);
  letter-spacing:var(--tracking-wide); text-transform:uppercase; text-align:left;
  padding:var(--space-4) var(--space-6); white-space:nowrap;
  border-bottom:1px solid var(--border); }
thead th[data-sort] { cursor:pointer; user-select:none; }
thead th[data-sort]:hover { color:var(--text-primary); }
thead th .car { opacity:0; margin-left:var(--space-2); transition:opacity var(--dur-fast); }
thead th[data-sort]:hover .car { opacity:.45; }
thead th[aria-sort] .car { opacity:1; color:var(--accent-500); }
tbody td { padding:var(--row-py-dense) var(--space-6); font-size:var(--text-base);
  line-height:var(--leading-snug); border-bottom:1px solid var(--border-subtle);
  vertical-align:middle; }
tbody tr:last-child td { border-bottom:0; }
tbody tr { cursor:pointer; transition:background var(--dur-fast); }
tbody tr:hover { background:var(--bg-hover); }
tbody tr:focus-visible { background:var(--bg-hover); outline:2px solid var(--border-focus);
  outline-offset:-2px; }
.cell-id { font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-weight:var(--weight-semibold); color:var(--text-primary); white-space:nowrap; }
.cell-muted { color:var(--text-muted); }
/* WHERE A CELL'S VALUE CAME FROM, after the value itself — "Magnolia Oil & Gas Operating LLC through Golden 1HA"
   on the wells list, where the operator is inherited from the well's unit rather than recorded on the well.
   SECONDARY, NOT MUTED: it is a real fact the reader needs, not an absence, and it must be legible enough to be
   read as a separate clause. Without a colour of its own the suffix reads as part of the company's name, which
   is the whole reason UIUX ruled the source has to be shown at all (operator-column ruling §1, state 2). */
.cell-through { color:var(--text-secondary); font-size:var(--text-xs); }
.cell-num { font-family:var(--font-mono); font-variant-numeric:tabular-nums; text-align:right; }
th.right { text-align:right; }
.cell-title { font-weight:var(--weight-semibold); }
.cell-sub { font-size:var(--text-xs); color:var(--text-muted); margin-top:0;
  font-weight:var(--weight-regular); }

/* estate marker — the hatch, doing the same job it does on the plat */
.estate { display:inline-flex; align-items:center; gap:var(--space-3);
  font-size:var(--text-xs); color:var(--text-secondary); white-space:nowrap; }
.estate i { width:14px; height:14px; border-radius:var(--radius-xs);
  border:1px solid currentColor; flex:0 0 auto; }
.estate.s { color:var(--estate-surface); }
.estate.m { color:var(--estate-mineral); }
.estate.b { color:var(--estate-both); }

.badge { display:inline-flex; align-items:center; padding:2px var(--space-4);
  border-radius:var(--radius-pill); font-size:var(--text-2xs);
  font-weight:var(--weight-semibold); background:var(--neutral-bg); color:var(--neutral-fg);
  border:1px solid var(--neutral-border); white-space:nowrap; }
.badge.ok { background:var(--success-bg); color:var(--success-fg); border-color:var(--success-border); }
.badge.warn { background:var(--warning-bg); color:var(--warning-fg); border-color:var(--warning-border); }
.badge.bad { background:var(--danger-bg); color:var(--danger-fg); border-color:var(--danger-border); }
.badge.info { background:var(--info-bg); color:var(--info-fg); border-color:var(--info-border); }
.badge.ai { background:var(--ai-bg); color:var(--ai-fg); border-color:var(--ai-border); }

.tablefoot { display:flex; align-items:center; gap:var(--space-5);
  padding:var(--space-5) var(--space-6); border-top:1px solid var(--border);
  background:var(--bg-card); font-size:var(--text-sm); color:var(--text-muted); flex-wrap:wrap; }
.pager { margin-left:auto; display:flex; gap:var(--space-2); }
.pager button { min-width:32px; height:32px; padding:0 var(--space-4);
  border:1px solid var(--border); background:var(--bg-card); border-radius:var(--radius-sm);
  font-size:var(--text-sm); cursor:pointer; font-variant-numeric:tabular-nums; }
.pager button[aria-current="page"] { background:var(--accent-700); border-color:var(--accent-700); color:#fff;
  font-weight:var(--weight-semibold); }

/* ── empty state — states what filtered, and offers the way out ──────────── */
.empty { padding:var(--space-11) var(--space-8); text-align:center; }
.empty svg { width:34px; height:34px; stroke:var(--text-muted); fill:none; stroke-width:1.4;
  opacity:.55; margin-bottom:var(--space-5); }
.empty h3 { font-size:var(--text-lg); margin:0 0 var(--space-3); font-weight:var(--weight-semibold); }
.empty p { font-size:var(--text-sm); color:var(--text-muted); margin:0 auto var(--space-6); max-width:44ch; }

/* ── record header (tract detail) ────────────────────────────────────────── */
.record { background:var(--bg-card); border:1px solid var(--border);
  border-radius:var(--radius-card); box-shadow:var(--shadow-card);
  padding:var(--space-7); margin-bottom:var(--space-6); position:relative; overflow:hidden; }
.record::before { content:""; position:absolute; left:0; top:0; bottom:0; width:6px;
  background-image:repeating-linear-gradient(45deg,transparent 0 2px,var(--estate-both) 2px 3px); }
.record-top { display:flex; align-items:flex-start; gap:var(--space-6); flex-wrap:wrap; }
.record-id { font-family:var(--font-mono); font-size:var(--text-2xl);
  font-weight:var(--weight-bold); letter-spacing:-.02em; line-height:1; }
.record-where { font-size:var(--text-sm); color:var(--text-secondary); margin-top:var(--space-3); }
.record-legal { font-family:var(--font-mono); font-size:var(--text-sm);
  color:var(--text-secondary); margin-top:var(--space-4); line-height:var(--leading-snug);
  padding:var(--space-5); background:var(--bg-sunken); border-radius:var(--radius-sm);
  border-left:2px solid var(--border-strong); }
.record-facts { display:flex; gap:var(--space-9); margin-top:var(--space-6); flex-wrap:wrap; }
.fact-k { font-size:var(--text-2xs); text-transform:uppercase;
  letter-spacing:var(--tracking-wide); color:var(--text-muted);
  font-weight:var(--weight-semibold); }
.fact-v { font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:var(--text-lg); font-weight:var(--weight-semibold); margin-top:var(--space-2); }

/* NO SCROLLBAR UNDER A TAB STRIP (Mike 2026-09-15: hovering the sub tabs shows a scrollbar, and he wants it gone everywhere). The strip
   WRAPS instead — the same change the record strips take. This is the platform strip the console's debug screen uses. The rest of this
   strip's styling is NOT touched. Mike ruled on it (2026-09-16): "leave the setting tabs the way they are" — the Settings restyle is
   REFUSED, and only the scrollbar was ever asked for. Don't re-open it from this comment. */
.tabs { display:flex; gap:var(--space-2); border-bottom:1px solid var(--border);
  margin-bottom:var(--space-6); flex-wrap:wrap; }
.tabs button { padding:var(--space-5) var(--space-6); border:0; background:none;
  color:var(--text-secondary); font-size:var(--text-base); font-weight:var(--weight-medium);
  cursor:pointer; border-bottom:2px solid transparent; margin-bottom:-1px;
  white-space:nowrap; min-height:var(--target-min-dense); }
.tabs button:hover { color:var(--text-primary); }
.tabs button[aria-selected="true"] { color:var(--accent-500); border-bottom-color:var(--accent-500);
  font-weight:var(--weight-semibold); }
.tabs .n { font-family:var(--font-mono); font-size:var(--text-2xs); opacity:.7; margin-left:var(--space-3); }

/* ── command palette ─────────────────────────────────────────────────────── */
.palette-bg { position:fixed; inset:0; background:var(--bg-overlay);
  z-index:var(--z-modal); display:none; padding-top:12vh; }
.palette-bg.open { display:block; }
.palette { max-width:560px; margin:0 auto; background:var(--bg-card);
  border:1px solid var(--border); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-modal); overflow:hidden; }
.palette input { width:100%; padding:var(--space-6) var(--space-7); border:0;
  background:none; font-size:var(--text-md); outline:none;
  border-bottom:1px solid var(--border); }
.palette-list { max-height:340px; overflow-y:auto; padding:var(--space-3); }
.palette-item { display:flex; align-items:center; gap:var(--space-5); width:100%;
  padding:var(--space-5) var(--space-6); border:0; background:none; cursor:pointer;
  border-radius:var(--radius-btn); text-align:left; font-size:var(--text-base); }
.palette-item:hover,.palette-item.sel { background:var(--bg-subtle); }
.palette-item svg { width:15px; height:15px; stroke:var(--text-muted); fill:none; stroke-width:2; }
.palette-item .k { flex:1; }
.palette-item .grp { font-size:var(--text-2xs); color:var(--text-muted);
  text-transform:uppercase; letter-spacing:var(--tracking-wide); }

/* ── mobile ──────────────────────────────────────────────────────────────
   One breakpoint, not eight. 900px is where the rail stops earning its width.
   Below it the rail becomes a bottom bar and every table becomes a card list —
   overflow-x on a 9-column table is not a mobile design.
   ──────────────────────────────────────────────────────────────────────── */
/* THE SAME NUMBER, WHERE JS CAN READ IT. A media query cannot read a custom property, so the 900
   below has to stay a literal — but the dock needs the same threshold, and UIUX ruled (2026-10-12
   §2) that ONE value governs both the bar and the dock. dock.js reads this token; nothing in JS
   retypes 900. The two copies are kept in step by tests/unit-shell-breakpoint-agrees.js, because two
   copies nobody compares is how you end up with two thresholds. */
:root { --shell-bar-bp:900px; }
.rowcards { display:none; }

@media (max-width:900px) {
  /* minmax(0,1fr), NOT 1fr. A bare 1fr track has an automatic MINIMUM of its widest child's min-content, so one wide child (the bottom
     rail's row of 74px items is the suspect) stretched the one column to 535px on a 390px phone, and #app's overflow-x:hidden cut the
     right ~145px off every screen with no way to scroll to it (SH - Debug, 2026-09-15 21:40Z). The rail already scrolls sideways. */
  .app { grid-template-columns:minmax(0,1fr); grid-template-rows:var(--topbar-h) 1fr auto; }
  .app[data-rail="collapsed"] { grid-template-columns:minmax(0,1fr); }

  /* BELOW THE SHELL'S ONE BREAKPOINT NOTHING FROM THE PANEL APPLIES. The rail is a bottom bar here, so
     its peek width and its floating shadow are both meaningless. A pin from a wide window is RELEASED,
     not forgotten: the stored preference is untouched, so widening restores it. */
  .rail { width:auto !important; box-shadow:none !important; }
  .rail { grid-row:3; flex-direction:row; overflow-x:auto; overflow-y:hidden;
    padding:0; border-right:0; border-top:1px solid rgba(255,255,255,.09);
    padding-bottom:env(safe-area-inset-bottom); }
  .rail-group { padding:0; margin:0; display:flex; flex:0 0 auto; }
  .rail-label,.rail-foot { display:none; }
  .rail-item { flex-direction:column; gap:var(--space-2); padding:var(--space-4) var(--space-6);
    min-width:74px; min-height:var(--target-min); border-radius:0; font-size:var(--text-2xs);
    justify-content:center; }
  .rail-item .lbl { font-size:var(--text-2xs); text-align:center; }
  .rail-item .rail-count { display:none; }
  .rail-item.active::before { top:0; bottom:auto; left:0; right:0; width:auto; height:3px;
    border-radius:0; }
  .rail-item[data-secondary] { display:none; }
  .rail-rule { display:none; }
  /* The bottom bar is for navigation. Scope is not navigation, and it would eat a
     nav slot — so on a phone it moves up beside the search. */
  /* NOT ON A PHONE — THE PAGE DRAWS IT INSTEAD (SH-UIUX phone ruling REV 3, 2026-09-18). This used to pin the toggle to the top right of
     the SCREEN, floating over a page that scrolls: measured in WebKit, 37 controls on 8 records lost taps to it, and a covered tap landed
     on `Organization`. The control itself stays in the rail, in the DOM, because routes and the opinion record press it from code
     (`#rail [data-scope]`) — so it is not displayed here, never removed. js/scope-flow.js mirrors it into the scrolling page. */
  #rail .rail-scope { display:none; }

  .brand-name,.org-switch span,.topsearch kbd { display:none; }
  /* THE TOP BAR FITS A PHONE (SH-UIUX topbar-390, 2026-09-15). Measured at 390: the right group ended at 519px; the role badge alone was 126px.
     RANKED, AND AT ANY WIDTH DROP FROM THE BOTTOM: ghost badge (never) · org identity (truncates, never collapses) · account · console ·
     logo (mark only) · search (icon) · theme · record · role badge (first to go; the role moves into the account menu, js/platform-topbar.js).
     The price ticker's own display:none under 900px lives in js/ticker.js and stays its owner's. */
  .role-pill:not([data-ghost="1"]) { display:none; }
  /* ORG IDENTITY TRUNCATES, NEVER COLLAPSES. live-shim paints the name as a TEXT NODE beside the caret (a single-org user gets a span), and
     text-overflow only applies to a block's own text, so the element becomes a block; ~80px keeps "Example Land Serv…" readable. The caret is
     pinned right so the ellipsis never swallows it. */
  .org-switch { display:block; position:relative; flex:0 1 auto; min-width:80px; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;
    padding-top:0; padding-bottom:0; line-height:var(--target-min-dense); }
  #orgSwitch:not(.org-switch-label) { padding-right:calc(var(--space-5) + 16px); }
  .org-switch svg { position:absolute; right:var(--space-4); top:50%; transform:translateY(-50%); }
  .topbar { gap:var(--space-4); padding:0 var(--gutter-phone); }
  .topsearch { max-width:none; }
  .page { padding:var(--space-5) var(--gutter-phone) var(--space-9); }
  .page-title { font-size:var(--text-xl); }
  .page-sub { display:none; }            /* the blurb is orientation, not work */
  .crumbs { margin-bottom:var(--space-4); }
  .page-head { gap:var(--space-4); margin-bottom:var(--space-5); }

  /* one action row: primary keeps its label, the rest reduce to icons */
  .page-actions { margin-left:0; width:100%; gap:var(--space-3); }
  .scope { flex:1 1 auto; }
  .scope button { flex:1; text-align:center; }
  .btn { min-height:var(--target-min); }
  .btn-primary { flex:1 1 auto; justify-content:center; }
  .btn:not(.btn-primary) span { position:absolute; width:1px; height:1px;
    overflow:hidden; clip:rect(0 0 0 0); }
  .btn:not(.btn-primary) { padding:var(--space-4); }

  /* saved views scroll rather than wrap to three lines */
  .viewbar { flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
    margin:0 calc(-1 * var(--gutter-phone)) var(--space-4);
    padding:0 var(--gutter-phone); }
  .viewbar::-webkit-scrollbar { display:none; }
  .chip { flex:0 0 auto; min-height:var(--target-min); }

  .filters { gap:var(--space-3); margin-bottom:var(--space-5); }
  .field { flex:1 1 100%; }
  .field input,.field select { min-width:0; width:100%; min-height:var(--target-min); }
  .result-count { margin-left:0; flex:1 1 100%; order:-1; }
  .record { padding:var(--space-6); }
  .record-facts { gap:var(--space-6) var(--space-7); margin-top:var(--space-5); }
  .fact-v { font-size:var(--text-md); }
  .record-legal { font-size:var(--text-xs); }

  /* tables → cards */
  .tablecard .tablescroll { display:none; }
  .rowcards { display:block; }
  .rowcard { padding:var(--space-6); border-bottom:1px solid var(--border-subtle); cursor:pointer; }
  .rowcard:last-child { border-bottom:0; }
  .rowcard:active { background:var(--bg-hover); }
  .rowcard-top { display:flex; align-items:baseline; justify-content:space-between;
    gap:var(--space-5); margin-bottom:var(--space-4); }
  .rowcard-id { font-family:var(--font-mono); font-weight:var(--weight-bold);
    font-size:var(--text-md); }
  .rowcard-meta { display:flex; flex-wrap:wrap; gap:var(--space-3) var(--space-6);
    font-size:var(--text-sm); color:var(--text-muted); }
  .rowcard-meta span b { color:var(--text-secondary); font-weight:var(--weight-medium); }
}

@media (min-width:901px) { .mobile-only { display:none; } }


/* UNWIRED IS DASHED, NOT WARNING-TONED. Nothing is wrong with the record — a read that would
   answer the question has never been written. A warning treatment here would send somebody to
   look for a fault in data that is fine. */
.dr-unwired{
  border:1px dashed var(--line, #c8cdd4);
  border-radius:6px;
  padding:14px 16px;
  background:transparent;
}
.dr-unwired p{ margin:0 0 6px; }
.dr-unwired .dr-needs{ margin:0; font-size:13px; opacity:.8; }
.dr-unwired code{ font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12px; }

/* THE UNIT FACTS ROW — three facts, and the empty state that carries the distinction.
   `Not recorded` is NEUTRAL, not warning: b1_unit_operators is empty, so on day one it fires on
   every unit, and a treatment firing on 100% of records is chrome rather than signal. */
.un-fact-none{ color:var(--text-secondary); font-style:normal; }
  /* .un-fact-do IS GONE, AND IT WAS ALREADY DEAD (2026-10-01).
     UIUX ruled it should become a field pattern — "Not recorded · Add…" with a real button instead of
     a blue underlined text link. Checked before building to the ruling, and THE BUILD WAS ALREADY
     RIGHT: no markup anywhere renders this class. The live control is
       <button class="un-btn un-sm" data-un-oprec="1">Record the operator…</button>
     with "A BUTTON, NOT LINKED TEXT (Mike)" written above it. The ellipsis is correct too — it routes
     to _openOpRec(), which opens OperatorLink, a form you fill in rather than a value written on click.

     So all that survived was this rule, styling nothing. It was also the LAST READER OF --link, which
     is why that token looked like an open question: the only thing asking for it was dead CSS.
     (--link had no declaration either, so its one reader was running on a #2563eb fallback that
     measured 3.34:1 in dark — a contrast bug on an element nobody could see.)
     .un-fact-none STAYS: user-unit-record.js still sets it for a known absence. */

.un-fact-more{ display:block; margin-top:2px; font-size:12px; color:var(--text-secondary); }
/* Acres is numeric and read down a column, so it is tabular and monospaced like every other
   figure on the record. */
.un-facts .un-fcell dd{ font-variant-numeric:tabular-nums; }

/* THE ABSENT IMPACT STATES. Three tones, and the difference between them is the whole point:
   neutral is a settled fact, danger is something that broke, warning is a question not answered. */
.dr-abs{ border-radius:6px; padding:14px 16px; }
.dr-abs p{ margin:0 0 6px; }
.dr-abs p:last-child{ margin-bottom:0; }
.dr-abs strong:first-child{ text-transform:none; }
/* pre_epoch — SETTLED AND CLOSED. With the epoch this is no longer unresolved: we know why the copy
   is absent and we know that is correct. Neutral is what a settled fact gets. */
.dr-abs-neutral{ border:1px solid var(--line, #d5d9df); background:transparent; }
/* missing — the archive step should have run and did not. Nothing is contested; something broke. */
.dr-abs-danger{ border:1px solid var(--danger-border); background:var(--danger-bg, #fdf3f3); }
/* unknown — never classified on a failed epoch read. Guessing either way hides or invents a fault. */
.dr-abs-warn{ border:1px solid var(--warning-border); background:var(--warning-bg, #fdf9ee); }

/* ── THE REQUIREMENT'S TRACTS TAB ────────────────────────────────────────────────────────────
   A CHOSEN BADGE FILLS AND STAYS PUT. Removing it from a row of thirty reflows every badge after
   it, and a landman choosing three in sequence loses their scan position after the first. The
   chosen set gathers in the row beneath instead. */
.rw-tt{ padding:var(--space-5, 16px) 0; }
.rw-tt-head h3{ margin:0 0 var(--space-3, 8px); font-size:15px; }
.rw-tt-lead{ margin:0 0 var(--space-4, 12px); }
.rw-tt-note{ color:var(--text-muted, #6b7280); font-size:12px; }
/* THE UNREAD STATE IS WARNED ABOUT, NOT SILENTLY EMPTY — "could not be read" is not "none". */
.rw-tt-warn{ margin:0 0 var(--space-3,8px); color:var(--warning-fg, #8a6d1f); font-size:13px; }
.rw-tt-all{ margin-bottom:var(--space-4, 12px); }
/* A SELECTION, NOT AN OUTCOME. Accent, never --success-* (which means something succeeded) and
   never --info-* (which means sourced but unverified). */
.rw-tract.is-chosen{
  background:var(--accent-500, #2563eb); color:var(--text-on-accent, #fff);
  border-color:var(--accent-500, #2563eb);
}
.rw-tt-chosen{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2, 6px);
  padding-top:var(--space-3, 8px); border-top:1px solid var(--line, #e5e7eb);
  margin-bottom:var(--space-5, 16px);
}
.rw-tt-lab{ font-size:12px; text-transform:uppercase; letter-spacing:.04em;
  color:var(--text-muted, #6b7280); margin-right:var(--space-2, 6px); }
.rw-tt-chip{
  display:inline-flex; align-items:center; gap:4px; padding:2px 4px 2px 8px;
  border:1px solid var(--line, #d5d9df); border-radius:4px; font-family:var(--font-mono, monospace);
  font-size:12px;
}
.rw-tt-x{ border:0; background:none; cursor:pointer; font-size:14px; line-height:1;
  padding:0 4px; color:var(--text-muted, #6b7280); min-width:var(--target-min, 24px); }
.rw-tt-x:hover{ color:var(--danger-fg, #b42318); }
.rw-tt-empty{ color:var(--text-muted, #6b7280); font-size:13px; }
.rw-tt-status{ margin-left:auto; font-size:12px; color:var(--text-muted, #6b7280); }
.rw-tt-cards{ display:flex; flex-wrap:wrap; gap:var(--space-2, 6px); margin-bottom:var(--space-4,12px); }
.rw-tt-card{ display:inline-flex; align-items:center; gap:6px; }
/* RESOLVED BY UIUX'S OWN TEST, not by a pick (no-ref-token §3): "if it names something the system
   knows, it's a reference." This badge marks a tract that is ON the requirement — a tract is a thing
   the system knows — so it reads the reference blue, which is --accent-fg (the token .opr-clientref
   reads for the client name). Measured at 11px on --bg-card: 7.31:1 light, 5.01:1 dark. Both clear
   4.5, which --accent-700 did not (2.22:1 dark) and the old #1d4ed8 fallback did not (2.58:1).

   This was the last reader of --accent-600, a token that never existed and that three authors read
   three different ways. It now has none. */
.rw-tt-onreq{ font-size:11px; color:var(--accent-fg); }

/* ── THE SHARED SECONDARY BUTTON — `.pc-btn` ────────────────────────────────────────────────────────
 * SH-UIUX `UIUX-to-ALL-no-linked-text.md` §2: replacing linked text with buttons must not grow a screen of loud
 * filled buttons, so the replacement is the SECONDARY button — transparent fill, an always-visible 1px
 * `--border-strong` border, `--bg-hover` on hover. Not a filled button, and not a border that appears on hover.
 *
 * `.pc-btn` was already used by settings.js, settings-statuses.js, platform-user-create.js, operator-create.js and
 * user-dashboard.js but DEFINED NOWHERE (found by SH-BUILD2, 2026-09-11), so those rendered as bare browser buttons.
 * One definition here, the same treatment as the dashboard's `.dash-btn`, so every screen's secondary action looks
 * alike. `.pc-btn-primary` is defined just below, on the app's one primary (see --btn-primary-* in tokens.css). */
.pc-btn{font:inherit;font-size:var(--text-sm);min-height:var(--target-min-dense);padding:0 var(--space-5);
  display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
  background:transparent;border:1px solid var(--border-strong);border-radius:var(--radius-sm);
  color:var(--text-primary);cursor:pointer;white-space:nowrap}
.pc-btn:hover:not(:disabled){background:var(--bg-hover)}
/* ── THE PRIMARY BUTTON — `.pc-btn-primary` ─────────────────────────────────────────────────────────────────────────
 * UIUX `grid-label-and-button-colors` §2.1: NOT a second primary. It is the app's existing primary (`.rw-btn.primary` in
 * index.html), sharing its DEFINITION through --btn-primary-* in tokens.css rather than copying hex values — two rules holding
 * the same colour is how they drift. White on the fill 7.77:1, on the hover 5.18:1 (measured 2026-09-13).
 *
 * SPECIFICITY IS DELIBERATE: `.pc-btn:hover:not(:disabled)` above is (0,3,0), so a plain `.pc-btn-primary:hover` (0,2,0) would
 * LOSE and the primary would drop to the transparent hover. These rules double the class to win. At most one per surface; Cancel
 * is always the secondary `.pc-btn` (§2.1 rules). The danger variant is held until UIUX rules on its dark-theme numbers. */
.pc-btn.pc-btn-primary{background:var(--btn-primary-bg);border-color:var(--btn-primary-bg);color:var(--btn-primary-fg);
  font-weight:var(--weight-medium)}
/* THE HOVER IS AN INSET LIGHT EDGE, NOT A FILL CHANGE, in both themes (UIUX hover-and-red-ramp §2): in dark the fill has no second step
 * that keeps both contrasts, and two hover behaviours for one button is a thing that drifts. The fill is untouched, so both contrasts
 * hold by construction. Specificity still doubled — this must beat .pc-btn:hover:not(:disabled), which would clear the fill. */
.pc-btn.pc-btn-primary:hover:not(:disabled){background:var(--btn-primary-bg);box-shadow:inset 0 0 0 1px var(--btn-hover-edge)}
/* `--focus-ring` is a BOX-SHADOW value (0 0 0 3px …), not a colour — as an outline colour it is invalid and the
 * outline silently disappears. */
/* Windows High Contrast Mode drops box-shadows, so a transparent outline is what keeps the ring visible there
 * (SH-UIUX, UIUX-to-SH-BUILD2-focus-buttons-tile-opinions-chart.md §2). */
.pc-btn:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:var(--focus-ring)}
.pc-btn:disabled{opacity:.5;cursor:default}

/* ── the billing band ───────────────────────────────────────────────────────
   SH-UIUX 2026-09-17 §7.3: the sandbox banner is on top, the band under it, both push content down, neither overlays. In this shell the
   top bar is a grid row of `.app`, so the band is the row under it: banner → top bar → band → content. The row only exists while there is
   something to say (`data-billing-band` is set by js/billing-band.js), so no empty strip is ever reserved.
   NO NEW HUE. Warning tokens for the two futures (a trial or subscription about to end, a payment being retried), the same tokens with a
   heavier weight for a stopped organization, and the muted surface for "we could not check" — which is not a warning about them at all. */
.app[data-billing-band] { grid-template-rows:var(--topbar-h) auto 1fr; }
.bill-band {
  grid-column:1/-1; display:flex; align-items:center; gap:var(--space-4);
  padding:var(--space-3) var(--space-6); font-size:var(--text-sm); line-height:1.45;
  background:var(--warn-bg); border-bottom:1px solid var(--warn-bd); color:var(--warn-fg);
}
.bill-band[data-tone="stopped"] .bill-band-text { font-weight:var(--weight-bold); }
.bill-band[data-tone="unknown"] { background:var(--surface-2); border-bottom-color:var(--line); color:var(--fg-muted); }
.bill-band-text { min-width:0; }
.bill-band-ask { opacity:.85; }
.bill-band-act { margin-left:auto; flex:0 0 auto; }
/* THE NOTICE LINE (SH-UIUX forgot-password doc §7.5) — js/notice-line.js. An EVENT, not a state: uncoloured, the body's weight, in the content's
   flow (first child of .main, so beneath the sandbox banner and the billing band by construction), a quiet × at the right. */
.sh-notice { display:flex; align-items:center; gap:var(--space-4); padding:var(--space-3) var(--space-4) var(--space-3) var(--space-6);
  border-bottom:1px solid var(--line); background:var(--bg-page); color:var(--text-primary); }
.sh-notice-text { margin:0; flex:1 1 auto; min-width:0; font-size:var(--text-base); line-height:var(--leading-normal); }
.sh-notice-x { flex:0 0 auto; min-width:var(--target-min); min-height:var(--target-min); padding:0; border:0; border-radius:var(--radius-btn);
  background:transparent; color:var(--fg-muted); font-size:1.4em; line-height:1; cursor:pointer; }
.sh-notice-x:hover { background:var(--bg-hover); color:var(--text-primary); }
/* THE OPTIONAL ACTION ON A NOTICE — `Undo` on a deleted sticky note (UIUX sticky notes B.3). It is a
   BUTTON in the house secondary style, never a text link: Mike's standing ruling is that actions are
   buttons, clickable rows or cards and never linked text. It sits between the sentence and the ×, so
   the order reads sentence, the thing you can do, then dismiss — and dismiss is the commit. */
.sh-notice-act { flex:0 0 auto; font:inherit; font-size:var(--text-sm); font-weight:var(--weight-semibold);
                 min-height:var(--target-min-dense); padding:0 var(--space-5);
                 border:1px solid var(--border); border-radius:var(--radius-btn);
                 background:var(--bg-card); color:var(--text-primary); cursor:pointer; white-space:nowrap; }
.sh-notice-act:hover { border-color:var(--border-strong); background:var(--bg-subtle); }
.sh-notice-act:focus-visible { outline:2px solid var(--border-focus); outline-offset:1px; }
@media (max-width:900px) {
  .bill-band { flex-wrap:wrap; padding:var(--space-3) var(--gutter-phone); }
  .bill-band-act { margin-left:0; }
}

/* ── WHY EVERY `[data-rail="collapsed"]` RULE ALSO SAYS `:not([data-panel="peeking"])` ─────────────
 *
 * Mike, recording mupw2hlt-wcttmc, 2026-10-01 18:51Z, THIRTY MINUTES AFTER THIS WENT TO PRODUCTION:
 *   "The side panel when collapsed and hovered over to pop out the icons should display the words as
 *    if it was pinned out not just the icons."
 *
 * He was right and it was mine. The peek deliberately does NOT change `data-rail` — a MutationObserver
 * in live-shim re-fits every anchored field whenever it does, and a 60ms hover must not trigger that.
 * But seventeen rules in this file and platform.css restyle the rail for the COLLAPSED LOOK off that
 * same attribute: `display:none` on labels and counts, centred items, `font-size:0` on the scope
 * buttons, `height:1px` on section labels, a rotated toggle chevron. They all still applied while the
 * panel was open, so it widened to 236px and showed icons in the middle of empty space.
 *
 * THE CONDITION WAS NEVER "data-rail is collapsed". It was always "the rail is VISUALLY narrow", and
 * those were the same thing until the peek existed. `collapsed AND NOT peeking` is the real condition,
 * which is why it is written on the rules rather than fixed by overriding them afterwards — an override
 * would be a second definition of the expanded look, and the next person to add a collapsed rule would
 * not know to add its twin.
 *
 * `.app[data-rail="collapsed"]` is deliberately NOT widened: that is the grid in the 900px block, where
 * the rail is a bottom bar and no peek applies.
 * ─────────────────────────────────────────────────────────────────────────────────────────────── */

/* ── SIDE BY SIDE (A.8, as amended to one pane per kind) ──────────────────────────────────────────
   --dks-divider IS THE ONE DEFINITION OF THE DIVIDER'S WIDTH. dock-split.js reads this token to work
   out how many panes fit, and the divider itself is drawn at the same token, so the count and the
   thing being counted cannot drift apart. Leaving the divider OUT of that sum is what made A.8's
   rule disagree with its own worked examples. */
.dks { --dks-divider: 6px; display:flex; align-items:stretch; height:100%; min-height:0; }
.dks-pane { flex:1 1 0; min-width:0; display:flex; flex-direction:column;
            border-right:1px solid var(--border); overflow:hidden; }
.dks-pane:last-child { border-right:0; }
/* The focused pane carries the accent bar across its top — the SAME mark a current dock tab wears,
   so "which one am I in" and "which tab is current" read as one idea rather than two. */
.dks-pane.is-focus { box-shadow: inset 0 3px 0 var(--accent); }
.dks-pane > [id] { flex:1 1 auto; min-height:0; overflow:auto; }
/* Each pane scrolls on its own (A.8), which is what `overflow:auto` on the view is for. */
.dks-bar { display:flex; align-items:center; gap:var(--space-3); flex:0 0 auto;
           padding:var(--space-2) var(--space-4); background:var(--bg-subtle);
           border-bottom:1px solid var(--border); font-size:var(--text-xs); }
.dks-bar .dks-kind { font-family:var(--font-mono); font-size:var(--text-2xs);
                     font-weight:var(--weight-semibold); letter-spacing:var(--tracking-wide);
                     text-transform:uppercase; color:var(--text-muted); }
.dks-bar .dks-title { font-weight:var(--weight-medium); white-space:nowrap; overflow:hidden;
                      text-overflow:ellipsis; min-width:0; }
.dks-bar .dks-sp { flex:1; }
.dks-b { font:inherit; font-size:var(--text-xs); padding:var(--space-1) var(--space-3);
         border:1px solid transparent; background:none; color:var(--text-secondary);
         border-radius:var(--radius-sm); cursor:pointer; }
.dks-b:hover { background:var(--bg-hover); color:var(--text-primary); }
.dks-divider { flex: 0 0 var(--dks-divider); cursor: col-resize; background: var(--border);
               border: 0; padding: 0; }
.dks-divider:hover, .dks-divider:focus-visible { background: var(--accent); }

/* The refusal sits where the width refusals already appear, above the panes. It is a CHOICE, not a
   notification: it has no timer and it goes when the person does anything else. */
.dks-refuse { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
              padding: var(--space-4) var(--gutter); background: var(--warning-bg);
              color: var(--warning-fg); border-bottom: 1px solid var(--warning-border);
              font-size: var(--text-sm); }
.dks-refuse[hidden] { display: none; }
.dks-rb { font: inherit; padding: var(--space-2) var(--space-4); border: 1px solid var(--border);
          background: var(--bg-card); color: var(--text-primary); border-radius: var(--radius-btn);
          cursor: pointer; }
.dks-rb:hover { border-color: var(--border-strong); }

/* ── THE DOCK (SH-UIUX BUILD-PACKAGE, Part A · slice 1) ───────────────────────────────────────────
 *
 * A.6  Bottom of the WORK SURFACE, right-aligned, its left bound reading --shell-inset — the
 *      shell-panel rule that nothing outside the nav reads the nav's live width. A peek floats over
 *      the dock; a pin moves it. That is one declaration, not a listener.
 *
 * A.6  z: BELOW the top bar and below menus, above content. There is NO --z-chrome token in
 *      tokens.css — the scale is dropdown 20, popover 150, banner 250, drawer 600, modal 3000 — so
 *      the package's "z: chrome" names something that does not exist, the same shape as
 *      --shadow-float. 15 puts the dock under a dropdown (20) and under the top bar (250), which is
 *      what the package describes; it is a number pending a token and is flagged to SH-UIUX.
 *
 * A.7  The record's content gets bottom padding equal to the dock's height WHILE IT HAS CHIPS, so the
 *      last line of a record is never under one. `body.has-dock` is set by dock.js only when a chip
 *      exists — an empty dock must not reserve space, because then every page carries a gap for
 *      something that is not there.
 * ─────────────────────────────────────────────────────────────────────────────────────────────── */
/* ROWS FILL RIGHT TO LEFT AND STACK UPWARD (Mike, 2026-10-02; UIUX dock-tabs §2).
   `row-reverse` puts the FIRST tab at the right; `wrap-reverse` sends each new row ABOVE the last.
   Between them the order on screen is the order in the array, so dock.js never computes a position —
   it only works out how wide a tab must be for a full row to reach the shell panel exactly. */
/* `bottom` IS THE BAR'S TOP EDGE, NOT ZERO (UIUX 2026-10-12, "the dock sits on top of the nav bar,
   never over it"). Below --shell-bar-bp the rail is a full-width bottom bar in the same strip this
   used to occupy, so docking anything covered the nav and a tap meant for a nav button opened the
   docked record instead — hit-tested at each item's centre: 3 of 7 reachable nav items covered at
   390px WebKit, 9 of 11 at 700px Chromium, 0 with the dock empty.
   `--rail-bar-h` is PUBLISHED BY THE BAR (dock.js, from a ResizeObserver on #rail) rather than
   written here as `calc(44px + 1px + env(safe-area-inset-bottom))`, which would be a second copy of
   three values the layout already decides. Absent — every width above the breakpoint — the fallback
   is 0 and this is the rule it always was. */
.dock { position:fixed; bottom:var(--rail-bar-h, 0px); right:0; left:var(--shell-inset);
        --dk-gap:6px;
        z-index:var(--z-chrome); display:flex; flex-direction:row-reverse; flex-wrap:wrap-reverse;
        align-items:flex-end; justify-content:flex-start;
        gap:var(--dk-gap); padding:var(--dk-gap) var(--space-5);
        pointer-events:none; }
.dock > * { pointer-events:auto; }
body.has-dock #main { padding-bottom:var(--dock-h, 56px); }

/* EVERY TAB THE SAME HEIGHT, AND THE HEIGHT IS FIXED IN CSS, NOT LEFT TO THE CONTENT (§1).
   Mike: "I don't like how the Unit is taller than the other tabs… they all need to be the same
   height." Content-sized tabs are exactly how one kind with a longer label makes its tab taller, so
   44px and border-box are what guarantee it rather than hope.
   The WIDTH comes from --dk-chip-w, which dock.js sets from the dock's measured width. */
/* EVERY TAB THE SAME HEIGHT, AND THE HEIGHT IS FIXED IN CSS, NOT LEFT TO THE CONTENT (§1).
   Mike: "I don't like how the Unit is taller than the other tabs… they all need to be the same
   height." Content-sized tabs are exactly how one kind with a longer label makes its tab taller,
   so 44px and border-box are what guarantee it rather than hope.

   THE WIDTH comes from --dk-chip-w, which dock.js sets from the dock's measured width so a full row
   reaches the shell panel exactly. A FULL ROUNDED CARD, because rows above the first have nothing to
   sit on and a flush edge would look like a dropped tab. */
.dk-chip { height:44px; box-sizing:border-box;
           flex:0 0 var(--dk-chip-w, 200px); max-width:var(--dk-chip-w, 260px);
           display:flex; align-items:stretch; min-width:0; position:relative;
           pointer-events:auto;
           background:var(--bg-card); border:1px solid var(--border);
           border-radius:var(--radius-btn); overflow:hidden; }
/* A PHONE IS ONE ROW THAT SCROLLS SIDEWAYS (UIUX dock-tabs §3): stacking would climb over the whole
   screen. dock.js sets no width here, so the tabs keep their natural one and the row scrolls instead
   of wrapping.
   THE THRESHOLD IS NOW --shell-bar-bp, NOT 600px (UIUX 2026-10-12 §2): once the dock rests on top of
   the nav bar, stacked rows above a nav bar eat the screen, and the 700px case is where that showed.
   One threshold for both elements. dock.js applies `.is-phone` from the token. */
.dock.is-phone { flex-wrap:nowrap; overflow-x:auto; overflow-y:hidden; }
.dock.is-phone .dk-chip { flex:0 0 200px; max-width:200px; }

/* The kind line carries a document's reference now, so it truncates on its own rather than pushing
   the tab wider — the full thing stays in the tooltip and the accessible name. */
.dk-kind { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
/* The recording reference shares the kind's line but NOT its small caps: UIUX asked for it "in
   normal case", and DOCUMENT · VOL 255 PG 763 is harder to read than the thing it exists to make
   readable. Same size and colour, so the line still reads as one quiet strip. */
.dk-kref { text-transform:none; letter-spacing:0; }

/* A.3 — the current chip is marked, and the mark is a BAR rather than a fill: a filled chip would
   compete with the record it refers to, and the bar is the same idiom the plat hatch uses. */
.dk-chip.current { border-color:var(--border-strong); }
.dk-chip.current::before { content:""; position:absolute; left:0; right:0; top:0; height:3px;
                           background:var(--accent-500); }

.dk-open { display:flex; flex-direction:column; align-items:flex-start; gap:1px; min-width:0;
           padding:var(--space-3) var(--space-4); border:0; background:none; cursor:pointer;
           text-align:left; color:var(--text-primary); font-family:inherit; }
/* A.3 — TWO LINES: the kind, small and uppercase, then the title. Without the kind, `Req 9` and
   `Tract 9` are chips that differ by one word you have to read. */
.dk-kind { font-size:var(--text-2xs); font-weight:var(--weight-semibold);
           letter-spacing:var(--tracking-wide); text-transform:uppercase; color:var(--text-muted); }
/* MAX-WIDTH 100%, NOT 200px (UIUX dock-tabs-what-production-still-shows §2; Mike: "see operator
   Magnolia extends over the button and should be cut off at the button edge").
   A hard 200px was the whole bug. The chip's width is COMPUTED now — paint() sets --dk-chip-w so a
   full row spans exactly to the shell panel — and the open area is that width minus the 30px × and
   its own padding, around 154px at the default. A name allowed 200px is therefore WIDER THAN THE
   BOX IT SITS IN: `overflow:hidden` on this element clipped it at 200px, not at the container, and
   because .dk-open had no overflow of its own the surplus painted straight across the ×. A fixed
   number beside a computed width is wrong at every width except the one it was written for. */
.dk-name { font-size:var(--text-sm); font-weight:var(--weight-medium);
           white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
.dk-open:hover .dk-name { color:var(--text-primary); }

/* THE × IS PINNED TO THE TAB'S RIGHT EDGE, whatever the title's length (UIUX dock-tab-close §1).
   It is a flex sibling of a .dk-open that grows, so it always lands at the edge rather than wherever
   the text happens to end — and every tab's × sits at the same place in its own card, which is what
   makes a row of them scannable instead of a ragged column of targets. */
.dk-x { flex:0 0 30px; display:grid; place-items:center; width:30px; border:0; background:none;
        cursor:pointer; color:var(--text-muted); border-left:1px solid var(--border); }
/* AND THE OPEN AREA CLIPS ITS OWN CONTENT, so nothing inside it can ever reach the ×. `min-width:0`
   lets it shrink in the flex row; `overflow:hidden` is what stops the shrinking from being cosmetic. */
.dk-open { flex:1 1 auto; overflow:hidden; }
.dk-x:hover { color:var(--text-primary); background:var(--bg-hover); }
.dk-x svg { width:14px; height:14px; stroke:currentColor; fill:none; stroke-width:2;
            stroke-linecap:round; }


/* ── THE GROUP TAB (UIUX drag-to-reorder-and-named-groups §3) ─────────────────────────────────
   It IS a .dk-chip — same height, same width, same two lines — so it restates none of those
   numbers. Mike's "they all need to be the same height" is a rule about every tab, and a second set
   of measurements beside the first is how that stops being true.

   THE FAINT SECOND EDGE BEHIND IT reads as a stack, which is the whole idea: more than one tab in
   one slot.

   DRAWN WITH A BOX-SHADOW, NOT A PSEUDO-ELEMENT. The first version used `::after` offset up and
   right — and `.dk-chip` sets `overflow:hidden`, so it would have been clipped to nothing and the
   stack simply would not have appeared. A shadow is painted outside the border box and an element's
   own overflow does not clip it, so this needs no exception to the tab's own rules and creates no
   stacking context to reason about. Two layers: the card's fill, then a hairline of the border. */
.dk-group { box-shadow: 3px -3px 0 0 var(--bg-card), 3px -3px 0 1px var(--border); }
/* The `⋯` is the same square as the × it replaces, so a row of tabs keeps one target position. */
.dk-more { font-size:1.1em; line-height:1; letter-spacing:.06em; }

/* The live region the dock announces moves into (UIUX §5). Visually hidden but NOT display:none
   — a region that is not rendered is not announced, which is the trap this class exists to avoid. */
.sr-only { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
           clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }

/* ── A GROUP'S LIST AND ITS ⋯ MENU (UIUX drag-to-reorder-and-named-groups §3–§4) ──────────────
   It opens UPWARD from the tab, because the dock sits at the bottom of the window and downward
   would go off-screen. Above the sheet's scrim for the same reason the dock itself is: a list you
   can see but not reach is not a list. */
.dkg { position:fixed; z-index:var(--z-modal); min-width:240px; max-width:320px;
       background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius-md);
       box-shadow:var(--shadow-float, 0 8px 24px rgba(0,0,0,.3)); padding:var(--space-3);
       display:flex; flex-direction:column; gap:var(--space-2); }
.dkg-head { display:flex; align-items:center; justify-content:space-between; gap:var(--space-3);
            padding:0 var(--space-2) var(--space-2); border-bottom:1px solid var(--border); }
.dkg-title { font-size:var(--text-sm); font-weight:var(--weight-semibold); color:var(--text-primary);
             white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.dkg-count { font-size:var(--text-2xs); color:var(--text-muted); white-space:nowrap; }
/* The name field sits exactly where the name was, so renaming does not move the thing being read. */
.dkg-name { flex:1 1 auto; min-width:0; font:inherit; font-size:var(--text-sm);
            font-weight:var(--weight-semibold); padding:var(--space-2) var(--space-3);
            border:1px solid var(--border-focus); border-radius:var(--radius-btn);
            background:var(--bg-input); color:var(--text-primary); }
.dkg-rows { display:flex; flex-direction:column; gap:var(--dk-gap,6px); max-height:50vh; overflow:auto; }
/* A row IS a .dk-chip — same kind line, same title, same × — so a tab looks the same inside a
   group and out (§4). Only its width is free, because a list is not a row of fixed slots. */
.dkg-row { flex:0 0 auto; max-width:none; width:100%; }
/* The bar for reordering INSIDE a group's list. A list is vertical, so its halves are top and
   bottom — the dock's bar is on the left or right because its row runs sideways. Same accent, same
   3px, so the two read as one idea seen from two angles. */
.dkg-row.dkg-drop-above, .dkg-row.dkg-drop-below { position:relative; }
.dkg-row.dkg-drop-above::after,
.dkg-row.dkg-drop-below::after { content:""; position:absolute; left:-2px; right:-2px; height:3px;
                                 background:var(--accent-500); border-radius:2px; }
.dkg-row.dkg-drop-above::after { top:-5px; }
.dkg-row.dkg-drop-below::after { bottom:-5px; }
.dkg-foot { margin:0; padding:var(--space-2) var(--space-2) 0; font-size:var(--text-2xs);
            color:var(--text-muted); border-top:1px solid var(--border); }
.dkg-menu { position:fixed; z-index:var(--z-modal); min-width:220px;
            background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius-md);
            box-shadow:var(--shadow-float, 0 8px 24px rgba(0,0,0,.3)); padding:var(--space-2);
            display:flex; flex-direction:column; }
.dkg-menu button { text-align:left; font:inherit; font-size:var(--text-sm); color:var(--text-primary);
                   background:none; border:0; border-radius:var(--radius-btn); cursor:pointer;
                   padding:var(--space-3) var(--space-4); white-space:nowrap; }
.dkg-menu button:hover { background:var(--bg-hover); }

/* ── DRAGGING A TAB (UIUX drag-to-reorder-and-named-groups §1–§2) ─────────────────────────────
   THE TAB'S PLACE FADES while a copy follows the pointer (§1), so the row still shows where it
   came from — a gap would make the row re-flow under the hand that is moving it. */
.dk-chip.dk-dragging { opacity:.35; }
/* The copy under the pointer. `pointer-events:none` so elementFromPoint reads the row BENEATH it
   rather than the thing being dragged, which is what every drop decision is made from. */
.dk-ghost { position:fixed; z-index:var(--z-modal); pointer-events:none; opacity:.9;
            box-shadow:var(--shadow-float, 0 8px 24px rgba(0,0,0,.3)); }
/* THE LANDING BAR, on the edge the tab will land against. The dock is row-reverse, so landing
   BEFORE something in the array puts it to that thing's RIGHT on screen. */
.dk-chip.dk-drop-before, .dk-chip.dk-drop-after { position:relative; }
.dk-chip.dk-drop-before::after,
.dk-chip.dk-drop-after::after { content:""; position:absolute; top:-2px; bottom:-2px; width:3px;
                                background:var(--accent-500); border-radius:2px; }
.dk-chip.dk-drop-before::after { right:-5px; }
.dk-chip.dk-drop-after::after  { left:-5px; }
/* §2: the target of a hold gets "an accent outline". */
.dk-chip.dk-group-target { outline:2px solid var(--accent-500); outline-offset:1px; }
/* …and a small label above it, saying which of the two things is about to happen. */
.dk-drag-label { position:fixed; z-index:var(--z-modal); transform:translate(-50%,-100%);
                 background:var(--bg-banner); color:#fff; font-size:var(--text-2xs);
                 font-weight:var(--weight-semibold); padding:var(--space-2) var(--space-3);
                 border-radius:var(--radius-btn); white-space:nowrap; pointer-events:none; }

/* A.4 — below the shell's one breakpoint the same rule leaves room for one chip and `+N more`. */
@media (max-width:900px) {
  .dock { left:0; padding:var(--space-2) var(--space-3); }
  .dk-chip { max-width:none; flex:1 1 auto; }
  /* NOT `none` HERE EITHER — it would un-bound the name again on a phone, which is the narrowest
     place and the one where running under the × matters most. */
  .dk-name { max-width:100%; }
}
@media (prefers-reduced-motion: reduce) { .dock, .dk-chip { transition-duration:1ms !important; } }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   STICKY NOTES (SH-UIUX viewer-dock-and-sticky-notes Part B; the button's place from
   sticky-notes-button §1). Private notes somebody sticks on a record.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* THE LAYER IS FIXED TO THE RECORD PANE AND DOES NOT SCROLL WITH IT (B.4: "When the record scrolls,
   the notes don't"). js keeps its box on the pane; this only says how it behaves.
   `pointer-events:none` so the record underneath stays clickable everywhere a note is not — a layer
   over the whole pane that swallowed clicks would break the record it is decorating. */
.sn-layer { position:fixed; pointer-events:none; overflow:hidden;
            /* ABOVE THE RECORD'S CONTENT, BELOW EVERYTHING ELSE (B.5): the shell and its peek, the
               dock, menus, toasts, drawers, side panes and modals all outrank it. --z-chrome is 30,
               so 6 sits above content and under all of them. The layer also BEGINS below the pinned
               back row, so Messages, Sticky notes and Minimize can never be under a note. */
            z-index:6; }
/* THE SURFACE THE NOTES SIT ON, inside the layer's clip box. It is moved by a transform as the
   record scrolls, which is what pins a note to the record instead of to the window (Mike,
   2026-10-02). It must NOT clip or scroll itself — the layer above it owns both. */
.sn-scroll { position:absolute; left:0; top:0; width:100%; height:100%;
             pointer-events:none; will-change:transform; }
/* The jump list in the Sticky notes menu: one row per note, in record order. Rows read as the
   menu's own items, so they take the menu's button styling and only add the separator above. */
.sn-jump { display:flex; flex-direction:column; border-top:1px solid var(--border); margin-top:var(--space-2); padding-top:var(--space-2); }
.sn-jump-row { text-align:left; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
.sn-note { position:absolute; pointer-events:auto; display:flex; flex-direction:column;
           background:var(--note-paper); color:var(--note-text);
           border:1px solid var(--note-edge); border-radius:var(--radius-md);
           box-shadow:var(--shadow-card); overflow:hidden;
           /* The floor, repeated from js so a note cannot be smaller than its own strip. */
           min-width:180px; min-height:120px; }
.sn-strip { display:flex; align-items:center; justify-content:space-between; gap:var(--space-3);
            padding:var(--space-2) var(--space-3); cursor:grab;
            border-bottom:1px solid var(--note-edge); flex:0 0 auto;
            /* The strip is the drag handle, so it must not select as text while being dragged. */
            user-select:none; -webkit-user-select:none; touch-action:none; }
.sn-strip:active { cursor:grabbing; }
.sn-when { font-size:var(--text-2xs); font-weight:var(--weight-semibold);
           letter-spacing:var(--tracking-wide); text-transform:uppercase; opacity:.75; }
/* A BIGGER TARGET WITHOUT MOVING THE GLYPH. The button was 18px (a 14px icon in 2px of padding),
   well under the house minimum of --target-min-dense: 32px — and it sits INSIDE the drag strip, so
   Mike's near-misses dragged the note instead of deleting it: "Delete button for sticky note not
   working." (production, 2026-10-06; his recording shows clicks resolving to the note and two
   sticky-notes/update calls moving it).
   It now eats the strip's own padding on the three edges it touches, so the pressable area reaches
   the note's top-right corner while the icon stays exactly where it was. */
.sn-del { border:0; background:none; cursor:pointer; color:inherit; opacity:.65;
          display:grid; place-items:center;
          padding:var(--space-2) var(--space-3);
          margin:calc(-1 * var(--space-2)) calc(-1 * var(--space-3))
                 calc(-1 * var(--space-2)) 0; }
/* AND THE REST OF THE WAY TO THE HOUSE MINIMUM, INVISIBLY. Eating the strip's padding gets the
   button to 26x22; --target-min-dense is 32px. This pseudo-element belongs to the button, so a press
   anywhere in it targets the button — without changing the strip's height, which is a visual decision
   and UIUX's to make. It has no paint of its own, so nothing moves and nothing shows. */
.sn-del { position:relative; }
.sn-del::after { content:''; position:absolute; top:50%; left:50%;
                 transform:translate(-50%, -50%);
                 width:var(--target-min-dense); height:var(--target-min-dense); }
.sn-del:hover { opacity:1; }
.sn-del svg { width:14px; height:14px; }
/* PLAIN TEXT, AND IT FILLS THE NOTE. No formatting, no links, no mentions (B.3). `resize:none`
   because the note resizes from its own grip — two resize affordances on one box is one too many. */
.sn-body { flex:1 1 auto; border:0; background:none; resize:none; outline:none;
           font:inherit; font-size:var(--text-sm); color:inherit; line-height:1.45;
           padding:var(--space-3); }
.sn-body::placeholder { color:inherit; opacity:.5; }
/* THE SAVE FAILURE SPEAKS IN THE NOTE'S OWN WORDS, BENEATH THE TEXT (B.3). Not a toast: the note is
   where the words are, and a toast about an unsaved note can be dismissed while the note is still
   unsaved. Nothing says "Saved" — a saved note looks like a note. */
.sn-err { margin:0; padding:0 var(--space-3) var(--space-2); font-size:var(--text-2xs);
          color:var(--note-text); opacity:.85; }
.sn-err[hidden] { display:none; }
.sn-grip { position:absolute; right:0; bottom:0; width:16px; height:16px; cursor:nwse-resize;
           touch-action:none;
           /* Two short strokes, the house idiom for a resize corner, drawn from the edge token so it
              cannot drift from the border it belongs to. */
           background:linear-gradient(135deg, transparent 0 48%, var(--note-edge) 48% 56%,
                      transparent 56% 72%, var(--note-edge) 72% 80%, transparent 80%); }

/* THE BUTTON sits in the back row between Messages and Minimize; nav-back.js owns that order. */
.sn-btn { position:relative; }
.nav-row-slot { position:relative; }      /* the menu is positioned against the slot */
/* THE MENU OPENS BELOW THE BUTTON, ALIGNED TO ITS RIGHT EDGE (sticky-notes-button §2). Above the
   notes layer and the back row, because a menu that opened under a note would be unusable. */
.sn-menu { position:absolute; top:calc(100% + 6px); right:0; z-index:var(--z-popover);
           min-width:260px; display:flex; flex-direction:column; align-items:stretch;
           background:var(--bg-card); border:1px solid var(--border);
           border-radius:var(--radius-md); box-shadow:var(--shadow-card); overflow:hidden; }
.sn-menu button { border:0; background:none; text-align:left; font:inherit;
                  font-size:var(--text-sm); color:var(--text-primary); cursor:pointer;
                  padding:var(--space-3) var(--space-4); min-height:var(--target-min-dense); }
.sn-menu button:hover { background:var(--bg-hover); }
.sn-menu-note { margin:0; padding:var(--space-3) var(--space-4);
                border-top:1px solid var(--border);
                font-size:var(--text-2xs); color:var(--text-secondary); }

/* BELOW 900px NOTES STILL SHOW, can be read, edited and moved (B.4). They are small on a phone;
   that is accepted, and one tap hides them all. */
@media (max-width:900px) { .sn-note { min-width:160px; } }
@media (prefers-reduced-motion: reduce) { .sn-note { transition-duration:1ms !important; } }
