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

   ── CURATED, NOT WHOLESALE. AND NO kit.css. ──────────────────────────────
   Measured before deciding (task brief): layoutTest's kit.css and LinkIt's
   app.css both write rules for 24 of the same classes —

     .btn (35 kit rules vs 15 LinkIt)   .input (14 v 9)    .switch (10 v 7)
     .switch-track (10 v 5)             .icon-btn (13 v 4) .modal (9 v 3)
     .segmented (8 v 1)                 .check (11 v 3)    .badge (7 v 3)
     .chip .chip-row .danger .danger-ghost .dot .field .field-hint
     .field-label .ghost .grow .muted .primary .row .sel .toast

   Those are not incidental — they are every control on every screen, plus the
   modifier words (.primary/.ghost/.danger/.muted) that LinkIt composes onto
   .btn. Loading kit.css would put two stylesheets in a specificity fight over
   the entire UI and would repaint LinkIt in MarkIt's green. So kit.css is not
   loaded; app.css keeps every component rule, unchanged.

   layoutTest's shell.css, by contrast, collides on only THREE classes —
   .brand, .brand-word and .danger — so it ports cleanly. This file is that
   port, and it deliberately does not define any of those three: LinkIt's own
   .brand/.brand-word/.danger rules in app.css stand untouched.

   Also deliberately left out (LinkIt is a single-view app and has no content
   for them): pane tab strips, seams/resizable columns, the multi-pane
   ws-* layouts, side/inspector panes, pane-head/pane-foot/crumbs.

   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 the AI Dashboard adoption used (#709-#714).

   ⚠️ NEW FILE — must be added to the SHELL array in service-worker.js or it
   404s offline. `npm run check:shell` now covers CSS and will say so.
   ========================================================================= */

/* ── Frame ───────────────────────────────────────────────────────────────── */
/* reset.css already does box-sizing and html,body{height:100%};
   app.css already does [hidden]{display:none!important}. 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 — that is 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 ───────────────────────────────────────────────────────────── */
/* NOTE: this element no longer carries .app-header, so app.css's sticky
   blurred-header rules do not apply. Its CHILDREN (.brand, .header-search,
   .header-tools, .icon-btn, .btn) are still styled entirely by app.css —
   which is how the chrome ends up in LinkIt's colours, not layoutTest's. */

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

/* The current screen name. Phone only — it replaces the wordmark, which is
   where the wrapped second header row used to go. */
.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 (#890, 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,
   and LinkIt's app.css is hand-edited by Shaun).

   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; } }

/* The search box sizes itself down inside the chrome; app.css owns its skin. */
.titlebar .header-search { margin-left: auto; }
.titlebar .header-tools  { flex: none; }

/* ── Activity rail (tablet + desktop) ────────────────────────────────────── */

.rail {
  display: none;                  /* phone: off — the bottom nav replaces 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 LinkIt 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 simply are not
   there. */
.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 svg { 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.
   This is the ONLY place a nav label is not permanently visible, and it is
   the 640-1023 band; at 1024+ the text nav is back in the title bar, and
   below 640 the bottom nav 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 #890)
   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. LinkIt's rail carries no badges, so two columns, not three. */
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 #819. 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.) */
.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. It reuses LinkIt'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` here, unlike layoutTest's .pane.
     LinkIt has ZERO @container rules (0 occurrences in app.css), so a size
     container would be dead weight — that is the whole reason it is omitted.
     I expected it to be worse than dead weight, because LinkIt's tag-suggestion
     menu (components/tag-input.js, fix #112) is position:fixed and anchored with
     getBoundingClientRect against the VIEWPORT, and layout containment used to
     re-anchor fixed descendants. MEASURED, rather than assumed: with
     container-type applied to this element (computed value confirmed
     'inline-size'), a position:fixed child at left:0 still rendered at 0px, not
     at the pane's 56px, and the real tag menu did not move either. So current
     Chrome does NOT re-anchor fixed children here — dropping it is a
     simplification, not a bug fix. Left out anyway; nothing uses it. */
}

/* The pane body is the ONLY scrolling region in the app. It carries no
   padding — LinkIt's own .app-main (max-width 1240 + its existing padding)
   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's modal lock targeted html/body.
   Now the pane is, and it has to be locked too or the view scrolls behind an
   open dialog. */
body:has(.modal-overlay) .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; }

/* The folder-sync pill reads as status, so it lives in the status bar now.
   app.css hides .sync-indicator below 720 with !important for the old cramped
   header; inside the status bar (which is itself gone below 640) that hiding
   is wrong, so it is lifted here. */
.statusbar .sync-indicator.show { display: inline-flex !important; padding: 0; }

/* ── Bottom nav — phones only ────────────────────────────────────────────── */
/* This REPLACES the rail below 640, and it also replaces the second header row
   LinkIt used to wrap to at <=720 (app.css line ~481). Net effect on a phone:
   one row of chrome at the top instead of two, and the four destinations move
   to where a thumb actually reaches. */

.bottomnav {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: var(--z-chrome);
  display: flex; align-items: stretch;
  height: calc(var(--bottomnav-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--frame);
  border-top: 1px solid var(--line);
}
@media (min-width: 640px) { .bottomnav { display: none; } }

.bn-item {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  border: 0; background: transparent;
  /* --ink-soft, not layoutTest's --ink-faint: at 10px on the --frame tone the
     faint ink measured too low to read in BOTH themes on a 390px shot. */
  color: var(--ink-soft);
  font-weight: 600; font-size: 10px; line-height: 1; text-decoration: none;
  position: relative; cursor: pointer;
}
.bn-item svg { width: 21px; height: 21px; }
.bn-item.active { color: var(--accent); }
.bn-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

/* ── Phone ───────────────────────────────────────────────────────────────── */

@media (max-width: 639px) {
  .titlebar .brand-word { display: none; }   /* app.css already does this at <=720 */
  .tb-screen { display: block; }
  /* The pane body must clear the bottom nav. app.css's .app-main already
     carries 72px of bottom padding for the old page; this replaces it with a
     value that tracks the nav height and the safe-area inset. */
  .pane-body > .app-main {
    padding-bottom: calc(var(--bottomnav-h) + env(safe-area-inset-bottom) + var(--sp-4));
  }
}

.pane-body > .app-main { min-height: 0; }

/* ── Re-basing app.css's sticky offsets on the pane ──────────────────────── */
/* Four rules in app.css pin something with `top: 76px`, and two cap it with
   `calc(100vh - 9Xpx)`. Both numbers were measured against the OLD page: a
   58px sticky .app-header plus an 18px gap, 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 76px would leave a 76px hole above
   every pinned panel, and a 100vh cap would overshoot the pane by the height
   of the chrome. Re-based here rather than edited in app.css, so the whole
   retrofit can still be reverted by removing two <link> tags.
     app.css:228 .detail-right · :349 .type-editor-col
     app.css:586 .settings-subnav · :649 .guide-toc · :616 guide headings */

.pane-body .detail-right,
.pane-body .type-editor-col,
.pane-body .settings-subnav,
.pane-body .guide-toc { top: var(--sp-4); }

.pane-body .type-editor-col,
.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); }
