/*
 * shell.css — Universal UI shell (Phase 1 prototype).
 * Implements UNIVERSAL_UI_STANDARD.md: design tokens (Light/Dark), shared
 * header, context bar, hamburger disclosure, spacing scale, focus visibility,
 * and 44px minimum interactive targets.
 *
 * Scope: loaded ONLY by the approved shell pages (Phase 1: ace/menu.html,
 * cnio/menu.html; Phase 2A: ace/jobtype.html, cnio/jobtype.html,
 * ace/configs.html) until the consultant approves wider rollout.
 */

/* ---------- Design tokens ---------- */
:root {
  /* Light theme (default) — sunlight palette. The light theme IS the outdoor
     theme, so every token used for text here clears 7:1 against its own
     background and every meaningful border clears 3:1. Phase 2 (§4.1) closed
     the last exceptions: the header gradient's light end was darkened so white
     header text passes at every stop, one red (--sh-error) replaces the three
     failing values, and green split into a graphic token and a text token.
     Two deliberate non-text tokens remain: --sh-accent is a fill/graphic only,
     and --sh-success-graphic is valid for a disc, border or rail segment and
     invalid for a word. Dark mode is governed separately and needs no remap. */
  --sh-bg: #f7f9fc;
  --sh-surface: #ffffff;
  --sh-surface-2: #f4f7fb;
  --sh-heading: #0b1f33;
  --sh-text: #1f2937;
  --sh-muted: #3c4c60;
  --sh-border: #7b90a6;
  --sh-primary: #00456e;
  --sh-primary-hover: #003152;
  --sh-primary-text: #ffffff;
  --sh-accent: #00a3e0;
  /* §4.1 — green splits by role. #0b8a5b is 4.37:1 on white: valid for a disc,
     border or rail segment (graphics need 3:1) and invalid for a word. */
  --sh-success: #07603f;
  --sh-success-graphic: #0b8a5b;
  --sh-warning: #78400f;
  --sh-error: #8f1a12;
  --sh-focus: #00456e;
  /* §4.1 — only the light end darkened, which carries white past 7:1 at every
     stop (11.32 / 7.50 / 7.06), so the floor holds with no exception list. */
  --sh-header-bg: linear-gradient(135deg, #003b6f 0%, #005890 52%, #005f85 100%);
  --sh-header-text: #ffffff;
  --sh-header-fill: #00304f;
  --sh-menu-hover: #eef6fc;
  --sh-canvas: linear-gradient(135deg, #102846 0%, #1a3b63 52%, #0d2847 100%);

  /* Spacing scale (UNIVERSAL_UI_STANDARD.md) */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;

  /* Radii */
  --r-control: 6px; --r-button: 10px; --r-card: 14px;
}

:root[data-theme="dark"] {
  --sh-bg: #060d1a;
  --sh-surface: #111827;
  --sh-surface-2: #1e293b;
  --sh-heading: #f1f5f9;
  --sh-text: #f1f5f9;
  --sh-muted: #a8b3c2;
  --sh-border: #334155;
  --sh-primary: #60a5fa;
  --sh-primary-hover: #93c5fd;
  --sh-primary-text: #0b1220;
  --sh-accent: #38bdf8;
  --sh-success: #34d399;
  --sh-warning: #f59e0b;
  --sh-error: #f87171;
  --sh-focus: #38bdf8;
  --sh-header-bg: linear-gradient(135deg, #06182d 0%, #0b3154 52%, #07547a 100%);
  --sh-header-text: #f1f5f9;
  --sh-menu-hover: #1e293b;
  --sh-canvas: linear-gradient(135deg, #030712 0%, #0b1628 52%, #07111f 100%);
}

/* ---------- Base ---------- */
* { margin: 0; padding: 0; box-sizing: border-box; }

/* The hidden attribute always wins, even against explicit display rules
   (e.g. inline-flex buttons). */
[hidden] { display: none !important; }

html, body { min-height: 100%; }

body.shell-page {
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  background: var(--sh-canvas);
  color: var(--sh-text);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* iOS Safari zoom guard (carried over from portal-glue).
   §7 — the old rule reached form controls only, so labels, hints and other
   supporting type stayed small on touch. Widened to the copy that sits with
   a control; controls keep the !important 16px floor that prevents iOS zoom. */
@media (pointer: coarse) {
  input, select, textarea { font-size: max(1rem, 16px) !important; }
  .shell-field label,
  .shell-form label,
  .shell-label { font-size: max(0.906rem, 14.5px); }
  .shell-hint,
  .shell-field-error,
  .shell-status,
  .shell-formnote,
  .shell-marker { font-size: max(0.844rem, 13.5px); }
}

/* Visible focus: 2px ring, 2px offset */
.shell-page :focus-visible {
  outline: 2px solid var(--sh-focus);
  outline-offset: 2px;
  border-radius: var(--r-control);
}

/* ---------- Header ---------- */
/* Banner 9a: the header is a stack of full-width rows — the title row, then
   the context band. Horizontal padding lives on the row, not the header, so
   the band reaches both edges without negative-margin arithmetic. */
.shell-header {
  background: var(--sh-header-bg);
  color: var(--sh-header-text);
  display: flex;
  flex-direction: column;
  padding: 0;
  position: relative;
}

/* One structural row: app title + module badge, page name, hamburger. */
.shell-titlerow {
  min-height: 64px;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-2) var(--sp-5);
  /* At 200% text the nowrap brand and title are wider than a phone, which
     pushed the whole page into horizontal scroll. Wrapping costs nothing at
     normal sizes — the row only breaks once it genuinely cannot fit. */
  flex-wrap: wrap;
}

/* Same reasoning one level down: the product name and its module badge may
   stack rather than force the header wider than the viewport. */
.shell-brand { min-width: 0; flex-wrap: wrap; }

.shell-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  white-space: nowrap;
}

.shell-brand .shell-product {
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.01em;
}

.shell-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--sh-header-fill);
  color: var(--sh-header-text);
  font-weight: 700;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  padding: 3px 9px;
  border-radius: 999px;
}

.shell-badge .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--sh-accent);
  flex: none;
}

.shell-title {
  flex: 1;
  text-align: center;
  font-size: 1.35rem;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Narrow-header title: below 480px the full title is retained accessibly
   (visually hidden) and an intentional short label is shown instead of
   letting the full title truncate to an ellipsis. */
.shell-title .shell-title-short { display: none; }

@media (max-width: 480px) {
  /* Tighten the brand block so header + short title + 44px trigger fit
     within 390px for the widest badge (CNIO) without overflow. */
  .shell-titlerow { gap: var(--sp-2); }
  .shell-brand .shell-product { font-size: 0.9rem; }
  .shell-title { font-size: 1.05rem; overflow: visible; }
  .shell-title .shell-title-full {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .shell-title .shell-title-short { display: inline; }
}

/* Hamburger trigger — 44x44, three lines, text-labeled for AT */
.shell-hamburger {
  width: 44px;
  height: 44px;
  flex: none;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: var(--sh-header-fill);
  border: 2px solid rgba(255, 255, 255, 0.62);
  border-radius: var(--r-button);
  cursor: pointer;
  padding: 0;
}

.shell-hamburger .bar {
  width: 20px;
  height: 2px;
  border-radius: 2px;
  background: var(--sh-header-text);
}

.shell-hamburger:hover { background: #003f68; }

/* ---------- Hamburger disclosure panel ---------- */
.shell-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: var(--sp-4);
  z-index: 60;
  min-width: 280px;
  background: var(--sh-surface);
  color: var(--sh-text);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-card);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
  overflow: hidden;
  padding: var(--sp-1) 0;
}

.shell-menu[hidden] { display: none; }

.shell-menu a,
.shell-menu button {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: var(--sp-3) var(--sp-4);
  background: none;
  border: none;
  color: var(--sh-text);
  font: 600 0.95rem/1.3 'Segoe UI', system-ui, sans-serif;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.shell-menu a:hover,
.shell-menu button:hover { background: var(--sh-menu-hover); }

.shell-menu .shell-menu-sep {
  height: 1px;
  background: var(--sh-border);
  margin: var(--sp-1) 0;
}

.shell-menu .shell-menu-grouplabel {
  padding: var(--sp-2) var(--sp-4) var(--sp-1);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sh-muted);
}

.shell-menu .shell-menu-group a { padding-left: var(--sp-5); }

.shell-menu .danger { color: var(--sh-error); }

/* Narrow: full-width panel below the header (per standard) */
@media (max-width: 640px) {
  .shell-menu {
    left: var(--sp-2);
    right: var(--sp-2);
    min-width: 0;
  }
}

/* ---------- Context bar ---------- */
.shell-context {
  background: var(--sh-surface);
  border-bottom: 1px solid var(--sh-border);
  padding: var(--sp-2) var(--sp-5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  font-size: 0.9rem;
}

.shell-context .ctx-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}

.shell-context .ctx-label { color: var(--sh-muted); white-space: nowrap; }

.shell-context .ctx-value {
  color: var(--sh-heading);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.shell-context .ctx-change {
  color: var(--sh-primary);
  text-decoration: none;
  font-weight: 600;
  white-space: nowrap;
  /* contextual text link, padded to a comfortable target */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--sp-2);
}

.shell-context .ctx-change:hover { text-decoration: underline; }

/* ---------- Upper contextual navigation ---------- */
.shell-upper-nav {
  background: var(--sh-surface);
  border-bottom: 1px solid var(--sh-border);
  padding: 0 var(--sp-5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
}

.shell-upper-nav .shell-back { margin: 0; }

/* ---------- Integrated application card ---------- */
.shell-frame {
  position: relative;
  width: calc(100% - 32px);
  max-width: 620px;
  margin: clamp(24px, 5vh, 56px) auto var(--sp-4);
  background: var(--sh-surface);
  border: 1px solid var(--sh-border);
  border-radius: 20px;
  box-shadow: 0 24px 54px rgba(3, 12, 28, 0.34);
}

.shell-frame.wide { max-width: 900px; }

/* Organization pages use a custom downward-opening list, so the normal
   centered-card placement is safe at every viewport height. */
.org-select-page .shell-frame { margin-top: clamp(24px, 5vh, 56px); }

.shell-frame::before {
  content: "";
  position: absolute;
  top: -1px;
  left: -1px;
  right: -1px;
  height: 5px;
  background: var(--sh-header-bg);
  border-radius: 20px 20px 0 0;
}

/* Banner 9a: the framed header is a stack of full-width rows, so the context
   band below reaches both edges. The title row owns the padding. */
.shell-frame .shell-header {
  background: var(--sh-header-bg);
  color: #ffffff;
  display: flex;
  flex-direction: column;
  padding: 0;
  border-bottom: 1px solid var(--sh-border);
  border-radius: 19px 19px 0 0;
  box-shadow: inset 0 -18px 34px rgba(0, 29, 58, 0.18);
  /* No overflow clipping here. The header now owns the context sheet and the
     hamburger menu, and both are meant to hang below it; clipping cut them off
     and, because a clip box is also a scroll container, moving focus into the
     hidden panel scrolled the header and dragged the title row out of view.
     The header's own gradient is already clipped to its border-radius, so
     nothing needed the clip in the first place. */
}

/* The hamburger is a real flex sibling here, never an absolute overlay, so it
   can never land on top of the band or the page name. */
.shell-frame .shell-titlerow {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  flex-wrap: wrap;
}

/* Shrinkable, so at 200% text the badge wraps under the product name instead
   of forcing the header wider than a 320px viewport. */
.shell-frame .shell-brand { justify-content: flex-start; flex: 0 1 auto; min-width: 0; }

.shell-frame .shell-brand .shell-product {
  color: #ffffff;
  font-size: 1.35rem;
  letter-spacing: -0.01em;
}

.shell-frame .shell-badge {
  background: var(--sh-header-fill);
  color: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.42);
}

.shell-frame .shell-title {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
  text-align: left;
  color: #ffffff;
  font-size: 0.92rem;
  font-weight: 500;
  line-height: 1.35;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

/* Presentational divider between the app title and the page name. It is a
   pseudo-element so it stays out of the accessibility tree and needs no markup
   change across every page. Phone keeps the stacked compromise, so the rule
   applies from 768px where the two sit on one line. */
@media (min-width: 768px) {
  .shell-frame .shell-title::before {
    content: "";
    display: inline-block;
    width: 1px;
    height: 1em;
    margin-right: var(--sp-3);
    vertical-align: -0.15em;
    background: rgba(255, 255, 255, 0.42);
  }
}

.shell-frame .shell-title .shell-title-full {
  position: static;
  width: auto;
  height: auto;
  padding: 0;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
}

.shell-frame .shell-title .shell-title-short { display: none; }

.shell-frame .shell-hamburger {
  flex: none;
  margin-left: auto;
  background: var(--sh-header-fill);
  border-color: rgba(255, 255, 255, 0.62);
  box-shadow: 0 4px 14px rgba(3, 12, 28, 0.22);
}

.shell-frame .shell-hamburger .bar { background: #ffffff; }
.shell-frame .shell-hamburger:hover { background: #003f68; }

.shell-frame .shell-context {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: center;
  flex-direction: column;
  /* At 200% text these rows must be allowed to grow instead of being
     clipped to a line that no longer fits. */
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;
  padding: 0;
  background: transparent;
  border: 0;
  color: #ffffff;
  font-size: 0.72rem;
  font-style: italic;
  line-height: 1.25;
}

.shell-frame .shell-context .ctx-item {
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 4px;
  width: 100%;
}

.shell-frame .shell-context .ctx-label { color: #ffffff; }
.shell-frame .shell-context .ctx-value { color: #ffffff; font-weight: 600; }

/* These are real targets, so the framed variant keeps the §2 laptop minimum
   rather than shrinking to 22px. Only the size changes here — the header's
   content and breakpoint behaviour are Phase 2. */
.shell-frame .shell-context .ctx-change {
  min-height: 44px;
  padding: 0 3px;
  color: #ffffff;
  font-style: normal;
  font-size: 0.7rem;
  text-decoration: underline;
}

.shell-frame .shell-upper-nav {
  justify-content: center;
  padding: var(--sp-1) var(--sp-5) var(--sp-2);
  gap: 0 var(--sp-4);
}

/* Laptop left-aligns Back on the same gutter the page content sits on; the
   phone rule above keeps it centred. */
@media (min-width: 768px) {
  .shell-frame .shell-upper-nav { justify-content: flex-start; }
}

/* The framed variant used to shrink the page's only Back control to 36px,
   below every §2 minimum. It keeps the laptop floor; the phone block at the
   end of this file raises it further. */
.shell-frame .shell-upper-nav .shell-back { min-height: 44px; }

.shell-frame .shell-main {
  flex: none;
  max-width: none;
  padding: 0;
}

/* ---------- Main content ---------- */
.shell-main {
  flex: 1;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--sp-5);
}

.shell-card {
  background: var(--sh-surface);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-card);
  padding: var(--sp-6) var(--sp-5);
  max-width: 560px;
  margin: 0 auto;
}

/* Wider card for tile grids and reference tables (Phase 2A pages). */
.shell-card.wide { max-width: 900px; }

/* The frame is the only outer card. Existing page cards retain their layout
   and padding but shed duplicate outer chrome. */
.shell-frame .shell-main > .shell,
.shell-frame .shell-main > .report-card,
.shell-frame .shell-main > .shell-card {
  width: 100%;
  margin-left: auto;
  margin-right: auto;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* Reports retain their intentionally compact controls, centered within the
   same outer work surface used by creators. */
.shell-frame .shell-main > .report-card > * {
  width: 100%;
  max-width: 620px;
  margin-left: auto;
  margin-right: auto;
}

.shell-frame input,
.shell-frame select,
.shell-frame textarea { max-width: 100%; }

/* ---------- Contextual back action (tertiary) ---------- */
.shell-back {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: 0 var(--sp-2);
  margin-bottom: var(--sp-2);
  color: var(--sh-primary);
  font-weight: 600;
  text-decoration: none;
}

.shell-back:hover { text-decoration: underline; }

/* ---------- Job-type tile grid (shared by ACE + CNIO jobtype) ---------- */
.shell-tilegrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}

.shell-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
  text-align: left;
  width: 100%;
  min-height: 44px;
  padding: var(--sp-4);
  background: var(--sh-surface-2);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-card);
  color: var(--sh-text);
  font-family: inherit;
  font-size: 1rem;
  text-decoration: none;
  cursor: pointer;
}

/* Accent on a white surface reads at 1.7:1, so meaningful borders on light
   surfaces use the primary. --sh-accent is kept for fills and dark surfaces. */
.shell-tile:hover { border-color: var(--sh-primary); box-shadow: 0 6px 16px rgba(0, 102, 179, 0.14); }

.shell-tile .tile-icon { color: var(--sh-primary); }

.shell-tile .tile-title {
  color: var(--sh-heading);
  font-size: 1.02rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.shell-tile .tile-sub {
  color: var(--sh-muted);
  font-size: 0.9rem;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.shell-tile.full { grid-column: 1 / -1; }

.shell-tile.secondary { background: var(--sh-menu-hover); }

@media (max-width: 640px) {
  .shell-tilegrid { grid-template-columns: 1fr; }
}

.shell-card h2 {
  color: var(--sh-heading);
  font-size: 1.15rem;
  margin-bottom: var(--sp-4);
}

.shell-actions { display: grid; gap: var(--sp-4); }

.shell-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 48px;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-button);
  font: 700 1rem/1.3 'Segoe UI', system-ui, sans-serif;
  text-decoration: none;
  cursor: pointer;
  border: 2px solid transparent;
}

.shell-btn.primary {
  background: var(--sh-primary);
  color: var(--sh-primary-text);
}

.shell-btn.primary:hover { background: var(--sh-primary-hover); }

.shell-btn.secondary {
  background: var(--sh-surface-2);
  color: var(--sh-primary);
  border-color: var(--sh-primary);
}

.shell-btn.secondary:hover { background: var(--sh-menu-hover); }

/* Disabled buttons are unmistakably inactive: muted fill + dashed border +
   muted readable text + not-allowed cursor. Never opacity-based, and hover
   must not re-apply action styling. */
.shell-btn:disabled,
.shell-btn[disabled] {
  background: var(--sh-surface-2);
  color: var(--sh-muted);
  border: 2px dashed var(--sh-border);
  cursor: not-allowed;
  box-shadow: none;
}

:root[data-theme="light"] .shell-btn:disabled,
.shell-btn:disabled { background: #eef1f6; }

:root[data-theme="dark"] .shell-btn:disabled { background: #1a2436; }

.shell-btn:disabled:hover,
.shell-btn[disabled]:hover {
  background: #eef1f6;
  color: var(--sh-muted);
}

:root[data-theme="dark"] .shell-btn:disabled:hover { background: #1a2436; }

/* ---------- Footer ---------- */
.shell-footer {
  padding: var(--sp-4) var(--sp-5);
  text-align: center;
  color: #ffffff;
  font-size: 0.85rem;
}

/* ---------- About dialog ---------- */
.shell-about-ov {
  position: fixed; inset: 0;
  background: rgba(15, 23, 42, 0.55);
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
}

.shell-about {
  background: var(--sh-surface);
  color: var(--sh-text);
  border: 1px solid var(--sh-border);
  border-radius: var(--r-card);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
  padding: var(--sp-5);
  min-width: 300px; max-width: 90vw;
}

.shell-about h2 { color: var(--sh-heading); margin-bottom: var(--sp-3); font-size: 1.1rem; }

.shell-about dl {
  display: grid; grid-template-columns: auto 1fr;
  gap: var(--sp-1) var(--sp-4);
  margin-bottom: var(--sp-4);
  font-size: 0.9rem;
}

.shell-about dt { color: var(--sh-muted); font-weight: 600; }
.shell-about dd { margin: 0; }

.shell-about .shell-btn { min-height: 44px; }

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  .shell-titlerow { padding: var(--sp-2) var(--sp-4); }
  .shell-main { padding: var(--sp-4); }
}

@media (max-width: 640px) {
  .shell-titlerow { min-height: 56px; gap: var(--sp-2); }
  .shell-title { font-size: 1.15rem; }
  .shell-brand .shell-product { font-size: 0.95rem; }
  .shell-context { padding: var(--sp-2) var(--sp-4); }
  .shell-upper-nav { padding: 0 var(--sp-4); gap: 0 var(--sp-3); }
  .shell-card { padding: var(--sp-5) var(--sp-4); }
}

@media (max-width: 640px) {
  .shell-frame {
    width: calc(100% - 16px);
    margin-top: var(--sp-2);
    border-radius: var(--r-card);
  }

  .shell-frame::before { border-radius: var(--r-card) var(--r-card) 0 0; }

  .shell-frame .shell-titlerow {
    min-height: 0;
    padding: var(--sp-3);
    gap: var(--sp-2);
  }

  .shell-frame .shell-brand { justify-content: flex-start; }
  .shell-frame .shell-brand .shell-product { font-size: 1.05rem; }
  .shell-frame .shell-title { flex: 1 0 100%; order: 3; text-align: left; font-size: 0.82rem; }
  .shell-frame .shell-context {
    width: 100%;
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px solid rgba(255, 255, 255, 0.24);
  }
  .shell-frame .shell-upper-nav { padding: var(--sp-1) var(--sp-3); }
  .shell-frame .shell-main { padding: 0; }
}

/* (Narrow title handling lives with .shell-title above; the context bar
   wraps so the selected values remain discoverable at mobile widths.) */

/* Narrow-header fit (kept last for cascade priority over the 640px rules):
   at 390px the widest badge (CNIO) + short title + 44px trigger must fit
   without horizontal overflow. */
@media (max-width: 480px) {
  .shell-brand .shell-product { font-size: 0.85rem; }
  .shell-badge { font-size: 0.62rem; padding: 3px 7px; }
}

/* ---------- Phase 3: shared form controls + async state primitives ---------- */
.shell-form .shell-field { margin-bottom: var(--sp-4); }

.shell-form label {
  display: block;
  font-weight: 600;
  color: var(--sh-heading);
  margin-bottom: var(--sp-1);
  font-size: 0.95rem;
}

.shell-form input,
.shell-form select {
  width: 100%;
  min-height: 44px;
  padding: var(--sp-2) var(--sp-3);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-control);
  font-size: 1rem;
  font-family: inherit;
  background: var(--sh-bg);
  color: var(--sh-text);
}

.shell-form input:focus,
.shell-form select:focus {
  border-color: var(--sh-primary);
  background: var(--sh-surface);
}

.shell-form input[aria-invalid="true"],
.shell-form select[aria-invalid="true"] { border-color: var(--sh-error); }

.shell-form input:disabled,
.shell-form select:disabled { background: var(--sh-surface-2); color: var(--sh-muted); }

/* ---------- Searchable custom combobox ---------- */
.shell-combobox { position: relative; }

.shell-combobox input[role="combobox"] {
  padding-right: 40px;
}

.shell-combobox::after {
  content: "▾";
  position: absolute;
  /* centred rather than pinned, so it tracks the taller phone-shape input */
  top: 50%;
  transform: translateY(-50%);
  right: 14px;
  color: var(--sh-muted);
  pointer-events: none;
}

.shell-combobox-list {
  position: absolute;
  z-index: 30;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  max-height: min(280px, 48vh);
  overflow-y: auto;
  padding: var(--sp-1);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-control);
  background: var(--sh-surface);
  box-shadow: 0 12px 28px rgba(3, 12, 28, 0.22);
}

.shell-combobox-option {
  display: block;
  width: 100%;
  min-height: 40px;
  padding: var(--sp-2) var(--sp-3);
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--sh-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.shell-combobox-option:hover,
.shell-combobox-option.active { background: var(--sh-menu-hover); }
.shell-combobox-option[aria-selected="true"] { color: var(--sh-primary); font-weight: 700; }
.shell-combobox-empty { padding: var(--sp-3); color: var(--sh-muted); }

/* Async status line (loading etc.) — a polite live region near the control */
.shell-status {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--sh-muted);
  font-size: 0.9rem;
  margin: var(--sp-2) 0;
}

.shell-status[hidden] { display: none; }

.shell-spinner {
  width: 16px; height: 16px; flex: none;
  border: 2px solid var(--sh-border);
  border-top-color: var(--sh-primary);
  border-radius: 50%;
  animation: shell-spin 0.8s linear infinite;
}

@keyframes shell-spin { to { transform: rotate(360deg); } }

/* Alert (error) and empty-state boxes — icon + text label, never color-only */
.shell-alert {
  display: none;
  margin-top: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-control);
  font-size: 0.95rem;
  /* §4.1 — one red. #8f1a12 is 8.41:1 on the #fdf5f4 error panel, and the
     2px outline is the §6 error treatment. */
  border: 2px solid var(--sh-error);
  background: #fdf5f4;
  color: var(--sh-error);
}

.shell-alert.visible { display: block; }

:root[data-theme="dark"] .shell-alert { background: #3f1d1d; color: #fca5a5; border-color: #7f1d1d; }

.shell-empty {
  display: none;
  margin-top: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px dashed var(--sh-border);
  border-radius: var(--r-control);
  color: var(--sh-muted);
  font-size: 0.95rem;
}

.shell-empty.visible { display: block; }

/* Inline (non-full-width) button variant for Retry-style actions */
.shell-btn.inline { width: auto; display: inline-flex; min-height: 44px; padding: var(--sp-2) var(--sp-4); margin-top: var(--sp-2); }

/* Muted helper/link block under forms */
.shell-formnote { margin-top: var(--sp-4); font-size: 0.9rem; color: var(--sh-muted); }
.shell-formnote a { color: var(--sh-primary); display: inline-flex; align-items: center; min-height: 44px; }

/* ---------- Target minimums, phone shape (<768px) ----------
   The rules above carry the laptop shape (44px decision/primary/input/
   secondary, 40px icon-only). Hand-held use needs more: 52px for anything
   carrying a decision or a primary action, 48px for inputs and secondary
   actions, 44px square for icon-only controls. Kept last so it wins over the
   more specific variants declared earlier (.shell-about .shell-btn,
   .shell-btn.inline). */
@media (max-width: 767px) {
  .shell-btn,
  .shell-about .shell-btn { min-height: 52px; }
  .shell-btn.inline { min-height: 48px; }
  .shell-tile { min-height: 52px; }
  .shell-back,
  .shell-frame .shell-upper-nav .shell-back { min-height: 48px; }
  .shell-context .ctx-change,
  .shell-frame .shell-context .ctx-change { min-height: 48px; }
  .shell-form input,
  .shell-form select { min-height: 48px; }
  .shell-combobox-option { min-height: 48px; }
}

/* ============================================================================
   PHASE 2 — shared responsive patterns (§7), component states (§6) and the
   §5.9 addendum. These exist so the creators stop inventing their own button,
   input, label and stepper styles. Every pattern defines BOTH form factors:
   the base rules carry the laptop shape (>=768px) and a phone block raises
   targets for the hand-held shape (<768px).
   ========================================================================= */

/* ---------- §5.9.1 Compact context — one row at every width -------------
   One target, never a separate 22px "Change" link. The row itself opens the
   context sheet, which is the only place organisation and the two Change
   actions live. Organisation is never load-bearing in the collapsed row. */
/* Banner 9a: a flat full-width band inside the gradient header. It carries its
   own fill and top rule; the trigger inside it is plain text with no capsule
   at any width. */
.shell-ctxbar {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  padding: var(--sp-1) var(--sp-5) var(--sp-2);
  background: rgba(0, 0, 0, 0.18);
  border-top: 1px solid rgba(255, 255, 255, 0.34);
}

.shell-ctx {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 44px;
  padding: 0;
  background: transparent;
  color: #ffffff;
  border: 0;
  border-radius: 0;
  font: 600 0.85rem/1.3 'Segoe UI', system-ui, sans-serif;
  text-align: left;
  cursor: pointer;
}

.shell-ctx-text {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  overflow: hidden;
  white-space: nowrap;
}

.shell-ctx-text > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.shell-ctx-sep { flex: none; opacity: 0.72; }

/* Label vs value is carried by style, not by colour or punctuation alone, so
   it survives greyscale and high-contrast modes. */
.shell-ctx-label { flex: none; font-style: italic; font-weight: 400; opacity: 0.86; }
.shell-ctx-value { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }

/* Chevron is the non-colour cue that the row opens something. */
.shell-ctx-chev {
  flex: none;
  width: 10px; height: 10px;
  border-right: 2px solid #ffffff;
  border-bottom: 2px solid #ffffff;
  transform: rotate(45deg);
  margin: -5px 4px 0 0;
}

.shell-ctx[aria-expanded="true"] .shell-ctx-chev {
  transform: rotate(225deg);
  margin: 4px 4px 0 0;
}

/* Organisation only appears from 1024px; below that it lives in the sheet. */
.shell-ctx .shell-ctx-org { display: none; }

@media (min-width: 1024px) {
  .shell-ctx .shell-ctx-org { display: inline; }
}

/* Phone keeps only its genuine differences — a 48px row in a 56px band and a
   slightly larger type size. The flat treatment itself is the base rule now,
   so there is no capsule to undo at any width. */
@media (max-width: 767px) {
  .shell-ctxbar { padding: var(--sp-1) var(--sp-4); min-height: 56px; }
  .shell-ctx { min-height: 48px; font-size: 0.875rem; }
}

/* Laptop: the bar sizes to its chip rather than spanning the header. */
@media (min-width: 768px) {
  .shell-ctx { width: auto; max-width: min(100%, 720px); }
}

/* ---------- Context sheet ---------- */
.shell-ctx-sheet {
  position: absolute;
  z-index: 70;
  top: calc(100% - 2px);
  left: var(--sp-4);
  right: var(--sp-4);
  max-width: 420px;
  padding: var(--sp-4);
  background: var(--sh-surface);
  color: var(--sh-text);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-card);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
}

.shell-ctx-sheet[hidden] { display: none; }

.shell-ctx-sheet dl {
  display: block;
  margin: 0 0 var(--sp-3);
}

.shell-ctx-sheet dt {
  color: var(--sh-muted);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.shell-ctx-sheet dd {
  margin: 0 0 var(--sp-3);
  color: var(--sh-heading);
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Both Change actions are full targets, never 22px text links. */
.shell-ctx-actions { display: grid; gap: var(--sp-2); }

.shell-ctx-actions a {
  display: flex;
  align-items: center;
  min-height: 52px;
  padding: 0 var(--sp-4);
  border: 2px solid var(--sh-primary);
  border-radius: var(--r-button);
  background: var(--sh-surface-2);
  color: var(--sh-primary);
  font-weight: 700;
  text-decoration: none;
}

/* ---------- Progress navigation — outlined step tiles -------------------
   Supersedes the bars/discs rail. One presentation at every width: a wrapping
   grid of tiles that each keep their full label and a numeric circle, so
   nothing is ever hidden behind a "Step n of m" caption. Only three colours
   exist here — neutral, current and invalid. There is deliberately NO
   completion colour: green would read as "the job is done" when it only means
   a step validated, and the Generate gate already carries that meaning. */
.shell-rail { margin-bottom: var(--sp-5); }

.shell-rail-steps {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.shell-rail-step { min-width: 0; }
.shell-rail-step[hidden] { display: none !important; }

.shell-rail-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 52px;
  padding: var(--sp-2) var(--sp-3);
  background: var(--sh-surface);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-button);
  color: var(--sh-text);
  font: 700 0.95rem/1.25 'Segoe UI', system-ui, sans-serif;
  text-align: left;
  cursor: pointer;
}

.shell-rail-disc {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 2px solid var(--sh-border);
  background: var(--sh-surface);
  color: var(--sh-muted);
  font-size: 0.85rem;
  font-weight: 700;
}

/* Labels wrap; they are never truncated, ellipsized or hidden. */
.shell-rail-label {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Current — restrained primary outline plus a pale fill, on the whole tile. */
.shell-rail-step[data-state="current"] .shell-rail-btn {
  border-color: var(--sh-primary);
  background: #e6f2fb;
  color: var(--sh-primary);
}
.shell-rail-step[data-state="current"] .shell-rail-disc {
  border-color: var(--sh-primary);
  background: var(--sh-primary);
  color: var(--sh-primary-text);
}

/* Invalid — subtle red border, text and pale error fill, applied only after
   the step has been attempted. Outranks current, so a step you just failed to
   leave stays selected and still reads red. */
.shell-rail-step[data-state="invalid"] .shell-rail-btn {
  border-color: var(--sh-error);
  background: #fdf5f4;
  color: var(--sh-error);
}
.shell-rail-step[data-state="invalid"] .shell-rail-disc {
  border-color: var(--sh-error);
  background: var(--sh-surface);
  color: var(--sh-error);
}

/* The ring wraps the entire button, never just the circle. */
.shell-rail-btn:focus-visible {
  outline: 2px solid var(--sh-focus);
  outline-offset: 2px;
}

@media (hover: hover) {
  .shell-rail-btn:hover { border-color: var(--sh-primary); }
}

/* ---------- Phone rail (<=767px) ----------
   A horizontal segmented rail replaces the vertical tile stack. Nothing here
   sets a height except the 7px segment; the band sizes to its own content so
   it reflows at 200% text instead of clipping. Above 767px every part below
   is hidden and the numbered-disc list is the rail, unchanged. Declared after
   the base rules so the phone display rules win. */
.shell-rail-head,
.shell-rail-track,
.shell-rail-jump { display: none; }

@media (max-width: 767px) {
  .shell-rail {
    padding-bottom: var(--sp-3);
    margin-bottom: var(--sp-4);
    border-bottom: 1.5px solid var(--sh-border);
  }

  .shell-rail-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: var(--sp-3);
  }

  .shell-rail-name {
    min-width: 0;
    overflow-wrap: anywhere;
    font: 700 17px/1.3 'Segoe UI', system-ui, sans-serif;
    color: var(--sh-heading);
  }

  .shell-rail-count {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    margin-left: auto;
    text-align: right;
    font: 12px/1.4 ui-monospace, 'Cascadia Mono', Consolas, monospace;
    color: var(--sh-muted);
  }

  /* Text marker, so "needs attention" survives greyscale and high contrast. */
  .shell-rail-flag { font-weight: 700; color: var(--sh-error); }
  .shell-rail-flag[hidden] { display: none; }

  .shell-rail-track {
    display: flex;
    gap: 5px;
    margin-bottom: var(--sp-2);
  }

  .shell-rail-seg {
    flex: 1;
    min-width: 0;
    height: 7px;
    border-radius: 4px;
    background: var(--sh-surface);
    border: 1.5px solid var(--sh-border);
  }
  .shell-rail-seg[data-state="done"],
  .shell-rail-seg[data-state="current"] {
    background: var(--sh-primary);
    border-color: var(--sh-primary);
  }
  .shell-rail-seg[data-state="invalid"] {
    background: var(--sh-error);
    border-color: var(--sh-error);
  }

  .shell-rail-jump {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 48px;
    padding: var(--sp-2) 0;
    background: none;
    border: 0;
    color: var(--sh-primary);
    font: 700 13px/1.35 'Segoe UI', system-ui, sans-serif;
    text-align: left;
    cursor: pointer;
  }

  .shell-rail-chev {
    flex: none;
    width: 8px; height: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    margin-top: -4px;
  }
  .shell-rail-jump[aria-expanded="true"] .shell-rail-chev {
    transform: rotate(225deg);
    margin-top: 3px;
  }

  /* The step list is the jump target on phone: same buttons, same handler. */
  .shell-rail-steps { display: none; }
  .shell-rail[data-jump-open="true"] .shell-rail-steps { display: grid; }
}

@media (min-width: 768px) {
  /* Tiles sit side by side when there is room, and wrap when there is not —
     never a horizontally scrolling rail. */
  .shell-rail-steps { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
  .shell-rail-btn { min-height: 44px; }
}

/* ---------- §7 .shell-choice — tiles, chips, segmented pairs ------------- */
.shell-choice { display: grid; gap: var(--sp-3); }

.shell-choice-error {
  display: none;
  color: var(--sh-error);
  font-size: 0.875rem;
  font-weight: 600;
}
.shell-choice-error.visible { display: block; }

/* Tiles — full-width stacked on phone, 2-up (creation) or 3-up (utility)
   from 768px via one grid change. Same tiles, not a second set. */
.shell-tiles { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }

.shell-opt {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  width: 100%;
  min-height: 52px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--sh-surface-2);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-card);
  color: var(--sh-text);
  font-family: inherit;
  font-size: 1rem;
  text-align: left;
  cursor: pointer;
}

.shell-opt-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.shell-opt-title { color: var(--sh-heading); font-weight: 700; overflow-wrap: anywhere; }
.shell-opt-sub { color: var(--sh-muted); font-size: 0.875rem; line-height: 1.35; overflow-wrap: anywhere; }

/* Selected is never colour alone: border weight + fill + a check disc. */
.shell-opt-check {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  margin-top: 1px;
  border: 2px solid var(--sh-border);
  border-radius: 50%;
  background: var(--sh-surface);
  color: transparent;
  font-size: 0.85rem;
  font-weight: 700;
}

.shell-opt[aria-checked="true"],
.shell-opt[aria-selected="true"] {
  border-width: 2.5px;
  border-color: var(--sh-primary);
  background: #e6f2fb;
}

.shell-opt[aria-checked="true"] .shell-opt-check,
.shell-opt[aria-selected="true"] .shell-opt-check {
  background: var(--sh-primary);
  border-color: var(--sh-primary);
  color: #ffffff;
}

.shell-opt:disabled,
.shell-opt[aria-disabled="true"] {
  background: #f1f4f8;
  border-color: #c3cfdc;
  color: #8b9aab;
  cursor: not-allowed;
}
.shell-opt:disabled .shell-opt-title,
.shell-opt[aria-disabled="true"] .shell-opt-title { color: #8b9aab; }

.shell-choice.has-error .shell-opt { border-color: var(--sh-error); }

@media (min-width: 768px) {
  .shell-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .shell-tiles.utility { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .shell-tiles .shell-opt.full { grid-column: 1 / -1; }
  .shell-opt { min-height: 44px; }
}

/* Chips — a wrapping row. Padding is deliberately generous; do not reduce it
   to fit more per row (§2). */
.shell-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.shell-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 var(--sp-4);
  background: var(--sh-surface);
  border: 2px solid var(--sh-border);
  border-radius: 999px;
  color: var(--sh-text);
  font: 600 1rem/1.2 'Segoe UI', system-ui, sans-serif;
  cursor: pointer;
}

.shell-chip[aria-checked="true"] {
  background: var(--sh-primary);
  border-color: var(--sh-primary);
  color: #ffffff;
  font-weight: 700;
}

.shell-chip:disabled,
.shell-chip[aria-disabled="true"] {
  background: #f1f4f8;
  border-color: #c3cfdc;
  color: #8b9aab;
  cursor: not-allowed;
}

@media (min-width: 768px) {
  .shell-chip { min-height: 44px; }
}

/* Segmented pair — full-width halves on phone, content-sized from 768px. */
/* No overflow clipping: §6 requires a DETACHED 2px offset ring, which an
   overflow-hidden parent would swallow. The halves carry their own radii so
   the outer shape is unchanged. */
.shell-seg {
  display: inline-flex;
  width: 100%;
  border: 2px solid var(--sh-border);
  border-radius: var(--r-button);
  background: var(--sh-surface);
}

.shell-seg-opt {
  flex: 1;
  min-height: 48px;
  padding: 0 var(--sp-3);
  background: var(--sh-surface);
  border: 0;
  color: var(--sh-muted);
  font: 700 1rem/1.2 'Segoe UI', system-ui, sans-serif;
  cursor: pointer;
}

.shell-seg-opt + .shell-seg-opt { border-left: 2px solid var(--sh-border); }

.shell-seg-opt:first-child {
  border-radius: calc(var(--r-button) - 2px) 0 0 calc(var(--r-button) - 2px);
}
.shell-seg-opt:last-child {
  border-radius: 0 calc(var(--r-button) - 2px) calc(var(--r-button) - 2px) 0;
}

.shell-seg-opt[aria-checked="true"] {
  background: var(--sh-primary);
  color: #ffffff;
}

.shell-seg[aria-disabled="true"] { background: #f1f4f8; border-color: #c3cfdc; }
.shell-seg[aria-disabled="true"] .shell-seg-opt { color: #8b9aab; cursor: not-allowed; }
.shell-seg[aria-disabled="true"] .shell-seg-opt[aria-checked="true"] { background: #c3cfdc; color: #35485e; }

.shell-choice.has-error .shell-seg { border-color: var(--sh-error); }

/* The focus ring goes on the focused half only, never the whole control. */
.shell-seg-opt:focus-visible {
  outline: 2px solid var(--sh-focus);
  outline-offset: 2px;
  position: relative;
  z-index: 1;
}

@media (min-width: 768px) {
  .shell-seg { width: auto; }
  .shell-seg-opt { min-height: 44px; padding: 0 var(--sp-5); }
}

/* ---------- §7 .shell-actionbar ---------------------------------------- */
.shell-actionbar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

/* Back sits left; the primary takes the remaining width. When Back does not
   apply it is omitted from the DOM (§5.9.6), so no empty column is left. */
.shell-actionbar .shell-actionbar-primary { flex: 1; }

@media (min-width: 768px) {
  .shell-actionbar { justify-content: flex-end; }
  .shell-actionbar .shell-actionbar-primary { flex: 0 0 auto; width: auto; min-width: 220px; }
  .shell-actionbar .shell-actionbar-secondary { margin-right: auto; }
}

/* Clear Form is a quiet text link in both shapes — never a button beside the
   primary action. */
.shell-quiet-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--sp-2);
  background: none;
  border: 0;
  color: var(--sh-primary);
  font: 600 0.9rem/1.3 'Segoe UI', system-ui, sans-serif;
  text-decoration: underline;
  cursor: pointer;
}

.shell-quiet-row { display: flex; justify-content: center; margin-top: var(--sp-3); }

@media (max-width: 767px) {
  /* Pinned to the bottom, clear of the home indicator. */
  .shell-actionbar.pinned {
    position: sticky;
    bottom: 0;
    z-index: 40;
    margin-left: calc(-1 * var(--sp-4));
    margin-right: calc(-1 * var(--sp-4));
    padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
    background: var(--sh-surface);
    border-top: 2px solid var(--sh-border);
  }
  .shell-actionbar .shell-btn { min-height: 52px; }
  /* Flex items default to min-width:auto, so the primary could not shrink
     below its own label; with Back held at a hard 104px the pair no longer
     fitted at 360px and the primary was clipped off the right.
     width:auto matters as much as the flex values: .shell-btn sets
     width:100%, which would otherwise resolve Back's flex-basis to the full
     bar and collapse the primary to its min-content instead. Basing Back on
     its own content leaves the rest to the primary, and 88px keeps Back far
     above the 44px interaction floor. */
  .shell-actionbar .shell-actionbar-primary { min-width: 0; }
  .shell-actionbar .shell-actionbar-secondary { flex: 0 1 auto; min-width: 88px; width: auto; }
  .shell-quiet-link { min-height: 48px; }
}

/* A phone at 200% text has very little height left, so pinning the action bar
   costs more of the form than it buys. Below 480px of viewport height the bar
   returns to normal flow at the end of the form. This follows the width query
   deliberately: both match on a short phone, and this one must win. */
@media (max-height: 480px) {
  .shell-actionbar.pinned {
    position: static;
    margin-left: 0;
    margin-right: 0;
    border-top: 0;
    background: transparent;
  }
}

/* ---------- §7 .shell-field — one label/control/hint/error contract ------ */
.shell-fieldset {
  display: grid;
  gap: var(--sp-4);
  width: 100%;
  max-width: 800px;
  margin-left: auto;
  margin-right: auto;
}

@media (min-width: 768px) {
  .shell-fieldset { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4) var(--sp-5); }
  .shell-fieldset > .full { grid-column: 1 / -1; }
}

.shell-field > label,
.shell-label {
  display: block;
  margin-bottom: var(--sp-1);
  color: var(--sh-heading);
  font-size: 14.5px;
  font-weight: 700;
}

/* §5.9.5 — label, 6px gap, *, 4px gap, (Mandatory). Both marks are
   aria-hidden so the accessible name is the label text alone; the control
   carries required + aria-required. */
.shell-req {
  margin-left: 6px;
  color: var(--sh-error);
  font-size: 14.5px;
  font-weight: 700;
}

.shell-req-text {
  margin-left: 4px;
  color: var(--sh-error);
  font-size: 12.5px;
  font-weight: 700;
}

/* Optional is the same size but neutral, so it never competes for attention. */
.shell-optional {
  margin-left: 6px;
  color: var(--sh-muted);
  font-size: 12.5px;
  font-weight: 600;
}

.shell-hint {
  margin-top: var(--sp-1);
  color: var(--sh-muted);
  font-size: 0.85rem;
  line-height: 1.35;
}

.shell-field-error {
  display: none;
  margin-top: var(--sp-1);
  color: var(--sh-error);
  font-size: 0.875rem;
  font-weight: 600;
}
.shell-field-error.visible { display: block; }

.shell-form textarea {
  width: 100%;
  min-height: 76px;
  padding: var(--sp-2) var(--sp-3);
  border: 2px solid var(--sh-border);
  border-radius: var(--r-control);
  font-size: 1rem;
  font-family: inherit;
  line-height: 1.4;
  background: var(--sh-bg);
  color: var(--sh-text);
  resize: vertical;
}

.shell-form textarea:focus { border-color: var(--sh-primary); background: var(--sh-surface); }

/* §6 input states */
.shell-form input[aria-invalid="true"],
.shell-form select[aria-invalid="true"],
.shell-form textarea[aria-invalid="true"] { border-color: var(--sh-error); border-width: 2px; }

.shell-form input:disabled,
.shell-form select:disabled,
.shell-form textarea:disabled {
  background: #f1f4f8;
  color: #8b9aab;
  border-color: #c3cfdc;
  cursor: not-allowed;
}

/* Read-only is styled distinctly from disabled: it still reads as content. */
.shell-form input[readonly] {
  background: var(--sh-surface-2);
  color: var(--sh-text);
  border-style: dashed;
}

/* ---------- §5.9.4 FCON import field ------------------------------------ */
.shell-file {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  min-height: 56px;
  padding: var(--sp-2) var(--sp-3);
  border: 2px dashed var(--sh-border);
  border-radius: var(--r-control);
  background: var(--sh-bg);
}

.shell-file input[type="file"] {
  flex: 0 1 auto;
  min-height: 0;
  border: 0;
  background: none;
  padding: 0;
}

.shell-file-name {
  flex: 1 1 100%;
  min-width: 0;
  color: var(--sh-muted);
  font-size: 13px;
  overflow-wrap: anywhere;
}

/* ---------- §5.9.3 FTV action ------------------------------------------- */
.shell-ftv-break { display: block; }

@media (min-width: 768px) {
  .shell-ftv-break { display: inline; }
  .shell-ftv-break::before { content: " "; }
}

/* ---------- §5.7 AFO route panel ---------------------------------------- */
.shell-route { position: relative; margin-top: var(--sp-3); }

.shell-route-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}

.shell-route-fixed {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--sp-2);
  border: 2px solid var(--sh-border);
  border-radius: 999px;
  color: var(--sh-muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.shell-route-list { list-style: none; margin: 0; padding: 0; position: relative; }

/* The spine sits at a fixed left offset aligned to the node centres, so a
   wrapping location name never shifts it. */
.shell-route-list::before {
  content: "";
  position: absolute;
  left: 15px;
  top: 24px;
  bottom: 24px;
  width: 2px;
  background: var(--sh-border);
}

.shell-route-row {
  position: relative;
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-2) 0;
}

.shell-route-node {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  background: var(--sh-surface);
  border: 2px solid var(--sh-primary);
  color: var(--sh-primary);
  font-size: 0.8rem;
  font-weight: 700;
}

/* A distinct flat shape per stop — shape survives 40px and glare where a
   detailed pictogram does not. */
.shell-route-node[data-shape="splitter"] { border-radius: 4px; }
.shell-route-node[data-shape="terminal"] { border-radius: 50%; }
.shell-route-node[data-shape="tether"] { border-radius: 999px 999px 4px 4px; }
.shell-route-node[data-shape="nid"] { border-radius: 4px; transform: rotate(45deg); }
.shell-route-node[data-shape="nid"] > span { display: inline-block; transform: rotate(-45deg); }
.shell-route-node[data-shape="jack"] { border-radius: 4px 14px 4px 14px; }
.shell-route-node[data-shape="ont"] { border-radius: 14px 4px 14px 4px; }

.shell-route-body { min-width: 0; }

.shell-route-name {
  color: var(--sh-heading);
  font-size: 0.95rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Badges wrap beneath the name rather than compressing. */
.shell-route-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }

.shell-route-badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  border: 2px solid var(--sh-primary);
  border-radius: 999px;
  color: var(--sh-primary);
  font-size: 0.72rem;
  font-weight: 700;
}

.shell-route-badge .shell-route-badge-long { display: inline; }
.shell-route-badge .shell-route-badge-abbr { display: none; }

/* Below 360px badges drop to abbreviations but never disappear. */
@media (max-width: 359px) {
  .shell-route-badge .shell-route-badge-long { display: none; }
  .shell-route-badge .shell-route-badge-abbr { display: inline; }
}

/* ---------- §6 focus, hover and shared interaction rules ---------------- */
.shell-opt:focus-visible,
.shell-chip:focus-visible,
.shell-rail-btn:focus-visible,
.shell-ctx:focus-visible,
.shell-quiet-link:focus-visible {
  outline: 2px solid var(--sh-focus);
  outline-offset: 2px;
}

/* Hover is a laptop-only enhancement and must never be the sole indicator of
   anything. Suppressed on touch so a tap does not leave a stuck hover. */
@media (hover: hover) {
  .shell-opt:hover:not(:disabled):not([aria-disabled="true"]) { border-color: var(--sh-primary); }
  .shell-chip:hover:not(:disabled) { border-color: var(--sh-primary); }
  .shell-seg-opt:hover:not([aria-checked="true"]) { background: var(--sh-menu-hover); }
  .shell-ctx:hover { background: rgba(255, 255, 255, 0.10); }
}

@media (hover: none) {
  .shell-opt:hover { background-color: var(--sh-surface-2); }
  .shell-opt[aria-checked="true"]:hover,
  .shell-opt[aria-selected="true"]:hover { background-color: #e6f2fb; }
  .shell-chip:hover { background-color: var(--sh-surface); }
  .shell-chip[aria-checked="true"]:hover { background-color: var(--sh-primary); }
  .shell-seg-opt:hover { background-color: var(--sh-surface); }
  .shell-seg-opt[aria-checked="true"]:hover { background-color: var(--sh-primary); }
  .shell-tile:hover { border-color: var(--sh-border); box-shadow: none; }
  .shell-ctx:hover { background-color: transparent; }
}

/* The compact context sits as its own band under the framed header. */
/* Banner 9a: the band is part of the gradient header, so it keeps the shared
   translucent fill and top rule rather than the white card surface it used to
   paint. Padding matches the framed title row so the text lines up. */
.shell-frame .shell-ctxbar {
  padding: var(--sp-2) var(--sp-4);
}

/* Phone keeps the compact 48px row in its ~56px band; the 9a top rule adds
   the single extra pixel. */
@media (max-width: 767px) {
  .shell-frame .shell-ctxbar { padding: var(--sp-1) var(--sp-4); }
}

.shell-frame .shell-ctxbar .shell-ctx-sheet { left: var(--sp-4); right: auto; }

/* ---------- §6 loading states — skeletons, never a bare spinner ---------- */
.shell-skeleton {
  position: relative;
  overflow: hidden;
  background: var(--sh-surface-2);
  border-radius: var(--r-control);
}

.shell-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(123, 144, 166, 0.28) 50%, transparent 100%);
  animation: shell-skeleton-sweep 1.4s ease-in-out infinite;
}

@keyframes shell-skeleton-sweep {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

/* Respect a reduced-motion preference: the shape still reads as pending. */
@media (prefers-reduced-motion: reduce) {
  .shell-skeleton::after { animation: none; }
}

/* Skeleton list rows stand in for the real rows at the same height, so the
   list does not collapse and then jump when the data lands. */
.ws-item.shell-skeleton-row,
.shell-skeleton-row {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: var(--sp-2) var(--sp-3);
  cursor: default;
}

.shell-skeleton-row .shell-skeleton {
  height: 14px;
  width: var(--skeleton-width, 62%);
}

@media (min-width: 768px) {
  .shell-skeleton-row { min-height: 44px; }
}

/* AFO route panel: a skeleton spine while the workflow resolves (§6). */
.shell-route-list.is-loading .shell-route-node {
  border-color: var(--sh-border);
  color: transparent;
}

.shell-route-list.is-loading .shell-route-name {
  height: 14px;
  width: 58%;
}
