/* ============================================================================
   shell.css — MarkIt  (task #817)
   The layoutTest floating-pane shell: unbroken chrome (title bar + status bar),
   an activity rail at 640+, and the view floating in a rounded pane inset by a
   gutter.

   ── CURATED, NOT WHOLESALE. AND NO kit.css. ──────────────────────────────
   Measured against THIS app before deciding (LinkIt's 24 was not assumed to
   carry over — MarkIt is worse). layoutTest's kit.css and MarkIt's app.css
   both write rules for 26 of the same classes:

     .btn (35 kit rules vs 17 MarkIt)  .icon (17 v 3)     .icon-btn (13 v 3)
     .switch (10 v 4)  .switch-track (10 v 3)  .check (11 v 2)
     .modal (9 v 1)    .segmented (8 v 1)      .toast (5 v 2)
     .danger (5 v 1)   .primary (5 v 4)        .ghost (3 v 3)
     .outline (3 v 2)  .danger-ghost (2 v 2)   .sel (2 v 1)
     .chart-bar (2 v 2) .combo-empty (2 v 1)   .field-grid (2 v 2)
     .fab (1 v 2)      .chart (1 v 1)          .field (1 v 1)
     .field-hint (1 v 1) .field-label (1 v 1)  .modal-open (1 v 1)
     .muted (1 v 2)    .stat-tile (1 v 1)

   Two of those are specific to this app and worse than they look. `.icon`
   carries SEVENTEEN kit rules and layoutTest's markup puts class="icon" on
   every SVG — MarkIt's icons.js stamps the same class on every icon it builds,
   so loading kit would resize and recolour every glyph in the app. And `.fab`
   is MarkIt's floating New-Match button, the primary phone action. Loading
   kit.css would put two stylesheets in a specificity fight over all of that.
   So kit.css is not loaded; app.css keeps every component rule, unchanged.

   layoutTest's shell.css collides on only FOUR classes — .brand, .brand-word,
   .danger and .toast-host — so it ports cleanly. This file is that port, and
   it deliberately defines none of those four: MarkIt's own rules stand.

   Deliberately left out (MarkIt is a single-view app with no content for
   them): pane tab strips, seams/resizable columns, the multi-pane ws-*
   layouts, side/inspector panes, pane-head/pane-foot/crumbs — AND the shell's
   own .bottomnav, because MarkIt already HAS a phone bottom nav
   (.mobile-tabbar, #197, built in app.js). Adding the shell's would have been
   a second, competing bottom surface. See the phone section below.

   Load order (index.html):
     reset.css -> theme.css -> shell-tokens.css -> app.css -> shell.css
   shell.css loads LAST so the frame selectors win; app.css still owns every
   component. Same arrangement as the AI Dashboard (#709-#714) and LinkIt (#816).

   ⚠️ NEW FILE — must be added to the SHELL array in service-worker.js or it
   404s offline. `npm run check:shell` (new here) reports it.
   ========================================================================= */

/* ── Frame ───────────────────────────────────────────────────────────────── */
/* reset.css already does box-sizing and html,body{height:100%}. Not repeated. */

body {
  margin: 0;
  background: var(--frame);       /* beats app.css's background: var(--bg) */
  overflow: hidden;               /* the PAGE never scrolls — the pane does */
  overscroll-behavior: none;      /* stops iOS rubber-banding the whole frame */
}

/* Skip link — first focusable element, jumps into the pane. */
.skip {
  position: fixed; top: -100px; left: var(--sp-2); z-index: 999;
  padding: 10px var(--sp-3); border-radius: var(--r-sm);
  background: var(--accent); color: var(--accent-ink);
  font-weight: 700; text-decoration: none;
  transition: top var(--dur-1) var(--ease-out);
}
.skip:focus { top: var(--sp-2); }

/* Title bar and status bar span BOTH columns — the structural reason nothing
   can cut the chrome. */
.app-frame {
  height: 100dvh;                 /* dvh, not vh — iOS/Android URL bar */
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  grid-template-rows: var(--titlebar-h) 1fr var(--statusbar-h);
  grid-template-areas:
    "title  title"
    "rail   work"
    "status status";
  overflow: hidden;
  min-height: 0;
}
.titlebar  { grid-area: title;  z-index: var(--z-chrome); }
.rail      { grid-area: rail;   z-index: var(--z-rail); }
.workspace { grid-area: work;   min-width: 0; min-height: 0; display: flex; }
.statusbar { grid-area: status; z-index: var(--z-chrome); }

/* ── Title bar ───────────────────────────────────────────────────────────── */
/* This element no longer carries .app-header, so app.css's sticky blurred
   header rules do not apply. Its CHILDREN (.brand, .header-tools, .app-nav,
   .btn, .icon-btn, .install-pill) are still styled entirely by app.css —
   which is how the chrome ends up in MarkIt's green, not a second palette. */

.titlebar {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 0 var(--sp-2);
  background: var(--frame);       /* no border — the frame tone separates */
  min-width: 0;
}
.tb-left { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }

/* The current screen name. Phone only — it fills the space the header nav
   already vacated at <=639 (app.css:429 hides .app-nav there), so the phone
   title bar gains a label instead of gaining chrome. */
.tb-screen {
  display: none; flex: 1; min-width: 0;
  font-weight: 700; font-size: .98rem; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The top text nav is GONE (#891, Maya #888). The four names now live in the
   rail itself at 1024+ — one surface instead of two showing the same four
   destinations. The <nav class="app-nav top-nav"> block was removed from
   index.html; app.css's .app-nav rules are left alone (nothing matches them).

   The settings gear leaves the header at 640+ too: the rail carries Settings,
   labelled from 1024. It stays on the phone, where there is no rail. */
@media (min-width: 640px) { .titlebar .gear-btn { display: none; } }

/* app.css gives .header-tools margin-left:auto, which still does the right
   thing here. Just stop it shrinking. */
.titlebar .header-tools { flex: none; }

/* ── Activity rail (tablet + desktop) ────────────────────────────────────── */
/* Built in js/app.js next to buildTabBar(), following MarkIt's own convention
   that "icons live in JS, so we build the bar here rather than in index.html". */

.rail {
  display: none;                  /* phone: off — .mobile-tabbar covers it */
  flex-direction: column; align-items: center;
  gap: 2px; padding: var(--sp-2) 0;
  background: var(--frame-2);     /* recessed tone separates it, no border */
  overflow: hidden;               /* the rail never scrolls — .rail-nav does */
}
@media (min-width: 640px) { .rail { display: flex; } }

/* The nav group scrolls if MarkIt ever grows past the rail's height; the
   pinned group below (Guide, Settings) always stays on screen. Without this
   the rail clips SILENTLY — no scrollbar, no fade, the items are just gone. */
.rail-nav {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.rail-nav::-webkit-scrollbar { display: none; }

.rail-item {
  position: relative;
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border: 0; background: transparent; color: var(--ink-soft);
  border-radius: var(--r-sm); cursor: pointer; text-decoration: none;
  transition: color var(--dur-1), background var(--dur-1);
}
.rail-item .icon { width: 20px; height: 20px; }
.rail-item:hover { background: var(--frame); color: var(--ink); }
.rail-item.active { color: var(--accent); }
/* 2px accent bar on the left edge marks the current section. */
.rail-item.active::before {
  content: ""; position: absolute; inset: 8px auto 8px 0;
  width: 2px; border-radius: 0 2px 2px 0; background: var(--accent);
}
.rail-group {
  display: flex; flex-direction: column; gap: 2px; align-items: center; flex: none;
  padding-top: var(--sp-2); border-top: 1px solid var(--line); width: 44px;
}

/* Tooltip — rail items are icon-only, so the label has to be reachable.
   640-1023 is the only band where a nav label is not permanently visible; at
   1024+ the text nav is back in the title bar, and below 640 MarkIt's own tab
   bar carries full labels. */
/* ⚠️ position: FIXED, not absolute — and that swap IS the fix (#849, Priya MED-1).
   As `absolute` this tooltip never rendered once, in either app. It sits inside TWO
   clip boxes: .rail{overflow:hidden} above, and .rail-nav, whose overflow-x can
   never be `visible` — declaring `overflow-x: visible` beside `overflow-y: auto`
   computes BACK to `auto`, which Priya measured in the engine rather than citing.
   The tooltip's origin is x=50px against a 44px/48px clip, so it was clipped by
   both, at every rail width. Nothing was wrong with the markup; it just never
   painted.
   A fixed box's containing block is the VIEWPORT, so no ancestor's overflow can
   clip it — and .rail-nav goes on scrolling exactly as #709 intended. Nothing is
   given up.
   Two things make this deterministic rather than lucky, and both are measured:
   · every inset stays `auto`, so the box keeps its STATIC position — where it
     would have been anyway — and the offsets below are still measured from the
     rail item, not from the viewport.
   · `place-self: start` is LOAD-BEARING. .rail-item is display:grid with
     place-items:center, which Chrome applies to the static position too, so
     without it the tooltip lands centred INSIDE the 44px icon and its position
     shifts with the length of its own label (measured: dx=10 for "A", dx=-54 for
     a long one). With it: dx=50, dy=0 from the item's centre, identical for a
     one-letter label and a 31-character one.
   ⚠️ Do not add transform / filter / backdrop-filter / contain / will-change to
   .rail, .rail-nav or .app-frame. Any of those makes that ancestor the containing
   block for fixed descendants and silently brings the clip back. */
.rail-item::after {
  content: attr(data-tip);
  position: fixed; inset: auto; place-self: start;
  margin-left: 50px;                /* the 44px rail item + the 6px gap */
  /* 22px = the item's vertical centre. The --rail-scroll correction is not
     decoration: a fixed box keeps the static position it had BEFORE its
     scroller moved, so once .rail-nav actually scrolls every tooltip renders
     scrollTop px too low and labels the wrong icon. Measured: forced a scroll
     of 92px and every tooltip missed by exactly 92px. app.js publishes the
     offset on .rail-nav; the fallback is 0px, so the pinned Guide/Settings
     items (which are outside the scroller) and a boot where that never ran are
     both still correct. */
  margin-top: calc(22px - var(--rail-scroll, 0px));
  transform: translateY(-50%);
  padding: 4px 8px; border-radius: var(--r-sm);
  background: var(--ink); color: var(--bg);
  font-weight: 600; font-size: 12px; line-height: 1; white-space: nowrap;
  opacity: 0; pointer-events: none; transition: opacity var(--dur-2);
  z-index: var(--z-popover);
}
.rail-item:hover::after, .rail-item:focus-visible::after { opacity: 1; }

/* ════════════════════════════════════════════════════════════════════════════
   EXPANDED + OVERLAY rail (Maya #888, build #891)
   Everything above is the condensed 48px strip and is unchanged. Everything
   below applies only once <html data-rail> says the labels are visible.
   ══════════════════════════════════════════════════════════════════════════ */

html[data-rail="expanded"] .rail,
html[data-rail="overlay"]  .rail { align-items: stretch; padding: var(--sp-2); }

html[data-rail="expanded"] .rail-nav,
html[data-rail="overlay"]  .rail-nav,
html[data-rail="expanded"] .rail-group,
html[data-rail="overlay"]  .rail-group { align-items: stretch; width: 100%; }

/* icon · label. MarkIt's rail carries no badges, so two columns, not three.
   The icon is app.css's .icon (display:block), already sized 20px above. */
html[data-rail="expanded"] .rail-item,
html[data-rail="overlay"]  .rail-item {
  width: 100%; height: 40px;
  display: grid;
  grid-template-columns: 20px 1fr;
  align-items: center;
  justify-items: start;
  gap: 10px;
  padding: 0 12px;
  border-radius: var(--r-sm);
}
/* Touch targets stay on @media, never @container. */
@media (pointer: coarse) {
  html[data-rail="expanded"] .rail-item,
  html[data-rail="overlay"]  .rail-item { height: var(--tap); }
}

.rail-label { display: none; }
html[data-rail="expanded"] .rail-label,
html[data-rail="overlay"]  .rail-label {
  display: block; min-width: 0;
  font-weight: 600; font-size: 13px; line-height: 1; color: inherit;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Nothing reaches the ellipsis at 176px — it is there so a future long label
   degrades instead of breaking the rail. */

/* Active: bar + colour + fill. The fill is expanded-only, deliberately — a 2px
   bar beside a 44px square reads fine, but beside a 152px row it is a mark at
   one end of a mostly-empty row. Three cues also survive a colour-blind viewer.
   var(--accent-soft) is exactly what .app-nav a.active already used, so the
   labels move into the rail carrying their existing treatment. */
html[data-rail="expanded"] .rail-item.active,
html[data-rail="overlay"]  .rail-item.active { background: var(--accent-soft); }
html[data-rail="expanded"] .rail-item.active::before,
html[data-rail="overlay"]  .rail-item.active::before { inset: 6px auto 6px 0; }
html[data-rail="expanded"] .rail-item.active .rail-label,
html[data-rail="overlay"]  .rail-item.active .rail-label { color: var(--accent); font-weight: 700; }

/* ⚠️ Tooltips OFF whenever the label is visible, and the second reason is not
   cosmetic. This tooltip is position:fixed (the #849 fix) and the block comment
   above warns that a transform on .rail makes .rail the containing block for
   fixed descendants, silently restoring the clip. The overlay animates with a
   transform. Killing the tooltip in the labelled states is what makes that
   transform safe — one rule, both problems. */
html[data-rail="expanded"] .rail-item::after,
html[data-rail="overlay"]  .rail-item::after { content: none; }

/* ── The tablet overlay (640–1023) ───────────────────────────────────────── */
/* This is what closes #825. That band is touch — there is no hover, so a
   tooltip was never a fix there. The grid track stays 48px, so the pane does
   NOT reflow: nothing is taken from the content. */

html[data-rail="overlay"] .rail {
  position: fixed;
  top: var(--titlebar-h);
  bottom: var(--statusbar-h);
  left: 0;
  width: 176px;
  z-index: var(--z-drawer);
  box-shadow: var(--shadow-lg);
  transform: translateX(-100%);          /* start state */
  transition: transform var(--dur-2) var(--ease-out);
}
html.rail-in[data-rail="overlay"] .rail { transform: translateX(0); }

/* Transform ONLY — never width or a layout property.
   The scrim covers the PANE, not the chrome: full-bleed would dim the very
   toggle you press to close, and a dimmed control you can still operate reads
   as broken. (Maya's spec says both; this is the reading that ships.)
   MarkIt's .fab and .mobile-tabbar are position:fixed at BODY level, outside
   .app-frame, so they were the obvious thing to check here. Both are already
   off in the overlay band — app.css:413 hides .fab at 640+ and .mobile-tabbar
   only displays below 640 — and their z-indexes (25 and 20) sit under the
   scrim's 39 in any case. Nothing to reconcile. */
.rail-scrim {
  display: none;
  position: fixed;
  top: var(--titlebar-h); bottom: var(--statusbar-h); left: 0; right: 0;
  background: var(--scrim-soft);
  z-index: calc(var(--z-drawer) - 1);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
html[data-rail="overlay"] .rail-scrim { display: block; }
html.rail-in[data-rail="overlay"] .rail-scrim { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  html[data-rail="overlay"] .rail,
  .rail-scrim { transition: none; }
}

/* ── The rail toggle ─────────────────────────────────────────────────────── */
/* Far left of .tb-left, before the brand — standard position (VS Code, Slack,
   Linear) and directly above the column it controls. Reuses MarkIt's own
   .icon-btn so app.css paints it. */
#rail-toggle .ti-close,
#rail-toggle .ti-open { display: none; }
html[data-rail="condensed"] #rail-toggle .ti-open,
html[data-rail="expanded"]  #rail-toggle .ti-close,
html[data-rail="overlay"]   #rail-toggle .ti-close { display: block; }
/* A control with nothing to control is worse than no control. */
html[data-rail="off"] #rail-toggle { display: none; }

/* ── The pane ────────────────────────────────────────────────────────────── */
/* The gutter is the pane's margin, so the frame shows on all four edges and
   the pane reads as floating on the chrome. */

.pane {
  position: relative;
  flex: 1; min-width: 0;
  min-height: 0;                  /* load-bearing: without it the body won't scroll */
  margin: var(--gutter);
  display: flex; flex-direction: column;
  background: var(--pane);
  border-radius: var(--pane-radius);
  box-shadow: var(--shadow-1);
  overflow: hidden;               /* clips content into the radius */
  /* No `container-type: inline-size`, unlike layoutTest's .pane. MarkIt has
     ZERO @container rules, so a size container would be dead weight. (On
     LinkIt I measured what containment actually does to position:fixed
     children in current Chrome — nothing — so this is a simplification, not a
     bug fix. MarkIt's one absolutely-positioned popover, .combo-menu, is
     position:absolute inside its own relative wrapper and is unaffected
     either way.) */
}

/* The pane body is the ONLY scrolling region in the app. It carries no
   padding — MarkIt's own .app-main (max-width 70rem + its existing padding,
   including the 6.5rem phone reserve for the tab bar and FAB) sits inside it
   and keeps content spacing exactly as it was. */
.pane-body {
  flex: 1; min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;   /* pane scroll never chains to the frame */
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--surface-3) transparent;
}
.pane-body::-webkit-scrollbar { width: 10px; height: 10px; }
.pane-body::-webkit-scrollbar-thumb {
  background: var(--surface-3); border-radius: 99px;
  border: 3px solid var(--pane);  /* inset look — keeps the floating illusion */
}
.pane-body::-webkit-scrollbar-track { background: transparent; }
.pane-body:focus { outline: none; }

/* The page used to be the scroller, so app.css:250's modal lock (body.modal-open
   { overflow: hidden }) targeted the body. Now the pane is the scroller and has
   to be locked too, or the view scrolls behind an open modal or sheet. */
body.modal-open .pane-body { overflow: hidden; }

/* ── Status bar (tablet + desktop) ───────────────────────────────────────── */

.statusbar {
  display: none;                  /* phone: gone */
  align-items: stretch; gap: 2px;
  padding: 0 var(--sp-2);
  background: var(--frame);
  font-size: .74rem; color: var(--ink-soft);
  overflow: hidden;
}
@media (min-width: 640px) { .statusbar { display: flex; } }

.sb-item {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 0 var(--sp-2);
  border: 0; background: transparent; color: inherit; font: inherit;
  border-radius: var(--r-sm); white-space: nowrap; text-decoration: none;
  min-width: 0;
}
.sb-item.is-static { cursor: default; }
a.sb-item:hover, button.sb-item:hover { background: var(--surface-2); color: var(--ink); }
.sb-spacer { flex: 1; }
.sb-ctx-text { overflow: hidden; text-overflow: ellipsis; }
.sb-num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* ── Phone ───────────────────────────────────────────────────────────────── */
/* NO shell bottom nav here. MarkIt already replaces the header nav with its own
   .mobile-tabbar below 640 (app.css:429 + :437), and app.css:425 already
   reserves calc(6.5rem + safe-area) at the bottom of .app-main for that bar and
   the FAB above it. Both are position:fixed at BODY level — outside .app-frame —
   so they keep working unchanged over the pane. The result is that the phone
   layout is essentially UNCHANGED by this retrofit: same one-row header height,
   same tab bar, same FAB. The shell's gain on this app is at 640+, not on a
   phone. (Contrast LinkIt #816, whose header wrapped to two rows below 720 and
   which therefore genuinely gained a row back.) */

@media (max-width: 639px) {
  .titlebar .brand-word { font-size: 1.05rem; }
  .tb-screen { display: block; }
}

/* ── Re-basing app.css's sticky offsets on the pane ──────────────────────── */
/* Three rules in app.css pin something with a `top` measured against the OLD
   page: a ~54px sticky .app-header with the WINDOW as the scroller. The sticky
   containing block is now .pane-body, whose top edge already sits below the
   title bar — so an un-rebased offset leaves a hole above every pinned element.
   Re-based here rather than edited in app.css, so the whole retrofit can still
   be reverted by removing two <link> tags.
     app.css:397 .guide-toc (top:76px)  ·  :375 guide headings (scroll-margin)
     app.css:543 .bkt-pager (top:56px)  — the bracket round pager */

.pane-body .guide-toc { top: var(--sp-4); }
.pane-body .guide-toc {
  max-height: calc(100dvh - var(--titlebar-h) - var(--statusbar-h)
                   - (var(--gutter) * 2) - var(--sp-6));
}
.pane-body .guide h1, .pane-body .guide h2,
.pane-body .guide h3, .pane-body .guide h4 { scroll-margin-top: var(--sp-3); }
.pane-body .bkt-pager { top: 0; }
