/* ============================================================================
   RoboWorx, Vertical-rail design system
   V0 · Shell stylesheet: design tokens + three-column shell + rails + nav +
   mega menu. Page-specific styling (hero, browse rows, profile, dashboard)
   is added by later phases in their own stylesheets.

   Architectural note: the whole .page grid is re-rendered on every route
   change, so there is no persistent chrome and no listener-stacking risk.
   Only the document-level delegated listeners in lib/shellInit.js survive
   a render.
   ============================================================================ */

/* --- Design tokens --------------------------------------------------------- */
/* sRGB hex only, never P3. Matches the locked brand palette. */
:root {

/* S11 (12 Jul 2026): two global correctness rules found by the /work audit.
   (a) An author display value silently beats the browser's [hidden] rule, so
   any styled element carrying the hidden attribute rendered PERMANENTLY
   VISIBLE, on /work every lever's edit row and the protect form were always
   open, which read as "edits that do nothing" and a rail of dead machinery.
   The attribute must always win. (b) Native select OPTIONS inherit the dark
   rail's white text onto the white dropdown, invisible options. */
[hidden] { display: none !important; }
select option { color: #1a1a1a; background: #fff; }
  --ink:      #1A1A1A;  /* primary text, dark backgrounds, rails */
  --ink-mute: #555555;  /* secondary / muted text */
  --rust:     #AD3D1A;  /* accents, links, CTAs, // labels. PF-101: nudged
                           from #B8431D, which measured 4.38:1 on the bone
                           surface (a hair under AA); this clears 4.5:1 on
                           bone, cream and white alike. */
  --rust-dk:  #8F3416;  /* hover / pressed, darker than --rust */
  --rust-bright: #D45A2C; /* vivid brand accent: wordmark, large display, on dark.
                             MEASURED 4.39:1 on --ink, fine for large text
                             (needs 3:1), NOT for 10-12px labels (needs 4.5). */
  /* S53 (B8). PF-101 recorded --rust-bright as "5.0:1 on ink" and --rust as
     "3.2:1". Both figures were wrong: measured on the rendered DOM they are
     4.39:1 and 2.87:1, so every small rust label on a dark ground, the whole
     global footer, the rail eyebrows, the live line, failed AA while the code
     carried a comment saying it passed. --rust-on-dark is the corrected token
     for SMALL rust text on --ink: 5.43:1. --rust-bright stays as it is for
     large display type, where 4.39:1 clears the 3:1 bar comfortably. */
  --rust-on-dark: #E0703C;
  /* PF-093 established the pattern: a token the page layer has referenced for
     ages via a hard-coded fallback but that was never defined, so the fallback
     always rendered. S53 does the same for --concrete (kbFigures diagrams, the
     sign-up clarifiers, the /work pager): 5.5:1 on white, 4.5:1 on cream. */
  --concrete: #6B6961;
  /* PF-093 (contrast-tokens): the secondary-text tokens the page layer has
     referenced (183 fallback sites) without a definition anywhere, so the
     hard-coded #8a867d fallback (3.63:1 on white) always rendered. Defined
     at AA-compliant values the high-contrast preference can also reach. */
  --ink-soft:  #5F5B52;  /* 6.8:1 on white, 5.4:1 on bone */
  --ink-faint: #6E6A60;  /* 5.4:1 on white, 4.7:1 on paper */
  --cream:    #EFE9DC;  /* hero card / primary light background */
  --sand:     #F5F0E2;  /* alternate light background */
  --line:     #C8C0AE;  /* borders, dividers */

  /* Workspace-card tokens (registered 12 Jul 2026, P1 S0). Single source for
     the Contract-workspace surfaces (designs 06v2-10); previously ad-hoc in
     mocks. Use these, never hard-coded hexes, on new workspace cards. */
  --card:     #ffffff;  /* card surface on cream */
  --card-2:   #f7f3ea;  /* card inner wells, row separators' ground */
  --hairline: #e4ddcd;  /* card borders, softer than --line */
  --radius-card: 14px;  /* workspace card radius (01 Jul geometry sweep) */

  /* Shell measurements, referenced by JS smoke tests, keep in sync. */
  --rail-left-w:  140px;
  --rail-right-w: 320px;

  --font-display: 'Inter', sans-serif;          /* 900 weight for big headings */
  --font-serif:   'Fraunces', serif;            /* italic emphasis + rail text */
  --font-mono:    'JetBrains Mono', monospace;  /* labels, metadata, kickers */
}

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

html, body { height: 100%; }

body {
  font-family: var(--font-display);
  background: var(--cream);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }

/* ============================================================================
   SHELL, three-column grid (left rail · main · right rail)
   ============================================================================ */
.page {
  display: grid;
  grid-template-columns: var(--rail-left-w) 1fr var(--rail-right-w);
  min-height: 100vh;
  position: relative; /* anchor for the mega-menu overlay */
}

/* ============================================================================
   LEFT RAIL, brand · vertical text · foot tag
   ============================================================================ */
.rail {
  background: var(--ink);
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: 32px 0;
  z-index: 30;
  /* Pin the rail to the viewport. Without this the rail stretches to the shared
     grid row height, so when the right rail's accordion grows (a project's tools
     expand) the row grows, this cell grows with it, and space-between re-spreads
     the brand/text/foot downward. align-self:start stops the stretch; height
     100vh + sticky give a stable, pinned column instead. */
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
}

.rail-brand {
  font-weight: 900;
  font-size: 18px;
  letter-spacing: -0.02em;
  cursor: pointer;
}
.rail-brand em {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--rust-bright);
  font-weight: 400;
}

/* The big sideways Fraunces phrase. Size is set per page via a modifier. */
.rail-text {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 300;
  color: #fff;
  letter-spacing: -0.02em;
  line-height: 0.9;
  text-align: left;
}
.rail-text.size-lg { font-size: 80px; }
.rail-text.size-md { font-size: 64px; }
.rail-text.size-sm { font-size: 48px; }

.rail-foot {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.4);
  writing-mode: vertical-rl;
  transform: rotate(180deg);
}

/* ============================================================================
   MIDDLE COLUMN, nav strip + page content
   ============================================================================ */
.main {
  padding: 48px 64px 64px;
  background: var(--cream);
  position: relative;
  transition: filter 0.25s;
  /* S50: the middle grid column is 1fr, whose floor is this item's automatic
     minimum size, so any long unwrappable content (a breadcrumb trail, a
     wide table) silently widened the whole grid and pushed the right rail
     off-screen. min-width: 0 lets the column hold its 1fr share; inner
     content manages its own overflow (the crumb ellipsises/scrolls). Three
     pages already carried main{min-width:0} in page CSS for exactly this. */
  min-width: 0;
}
/* When the mega menu is open the middle column blurs and stops taking input. */
body.mega-open .main { filter: blur(4px); pointer-events: none; }

.nav-strip {
  display: flex;
  /* S52: allow the crumb to wrap onto its own row rather than being crushed to
     zero width by the non-shrinking nav links (see the .crumb rule below). */
  flex-wrap: wrap;
  row-gap: 6px;
  justify-content: space-between;
  align-items: baseline;
  gap: 32px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 48px;
}
.nav-strip .links {
  display: flex;
  flex-wrap: nowrap;        /* links stay on one line */
  gap: 24px;
  flex: 0 0 auto;           /* take natural width, never shrink */
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 500;
}
.nav-strip .links a { cursor: pointer; transition: color 0.15s; white-space: nowrap; }
.nav-strip .links a:hover { color: var(--rust); }
.nav-strip .links a.active,
.nav-strip .links a.is-open {
  color: var(--rust);
  font-weight: 700;
  position: relative;
}
.nav-strip .links a.is-open::after { content: '\25BE'; margin-left: 4px; }

/* The hamburger toggle is mobile-only; hidden on desktop where links are inline. */
.nav-toggle { display: none; }

/* The primary-nav rail disclosure toggle is mobile-only; on desktop the rail
   body is always shown in its normal right-hand position. */
.aside-toggle { display: none; }
.aside-body { display: block; }

/* S52 (ROOT CAUSE of "there is no way back"): the crumb was `flex: 0 1 auto`
   while `.links` is `flex: 0 0 auto` (never shrinks), so the breadcrumb absorbed
   100% of any space shortfall. Measured on /settings: 1600px → 173px visible,
   1440px → 130px, 1366px → 56px, 1280px → **0px**. On a normal laptop the only
   hierarchical back-link was clipped out of existence, which is exactly what a
   user experiences as "there are no navigation buttons to go back".
   Fix: the strip may wrap, and the crumb keeps a real minimum. When both fit it
   sits right-aligned on the nav row as before; when they don't it drops to its
   own full-width row and stays completely readable and clickable. */
.nav-strip .crumb {
  flex: 1 1 auto;
  min-width: 190px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
}
.nav-strip .crumb strong { color: var(--ink); }
/* S52: a crumb LINK is a navigation control, not decoration. At 10px muted it
   read as a label and users reported "there is no way back". Links now carry
   the rust action colour, an underline affordance and real padding so they are
   both perceivable and tappable (the mobile rule below takes them to a full
   44px target). The non-link parts stay quiet so the trail still reads calmly. */
.nav-strip .crumb a {
  color: var(--rust);
  text-decoration: none;
  border-bottom: 1px solid rgba(184, 67, 29, 0.35);
  padding: 2px 1px;
}
.nav-strip .crumb a:hover,
.nav-strip .crumb a:focus-visible { color: var(--ink); border-bottom-color: var(--ink); }

/* ============================================================================
   RIGHT RAIL, container only. Per-page content is injected by the page.
   ============================================================================ */
.aside {
  background: var(--ink);
  color: #fff;
  padding: 48px 36px;
  display: flex;
  flex-direction: column;
  z-index: 30;
  position: relative;
}
.aside .aside-label {
  font-family: var(--font-mono);
  font-size: 11px;   /* PF-101/PF-103: 10px rust-on-ink measured 2.87:1 */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rust-on-dark);  /* S53: 5.43:1 on ink (--rust-bright was 4.39) */
  font-weight: 700;
  margin-bottom: 24px;
}

/* ============================================================================
   MEGA MENU, overlays the middle column only; rails stay visible/usable.
   ============================================================================ */
.mega-overlay {
  position: absolute;
  top: 0;
  left: var(--rail-left-w);
  right: var(--rail-right-w);
  bottom: 0;
  background: var(--cream);
  z-index: 40;
  display: none;
  flex-direction: column;
  padding: 48px 64px 64px;
  overflow-y: auto;
  box-shadow: 0 0 60px rgba(0,0,0,0.15);
}
body.mega-open .mega-overlay { display: flex; }
/* Lock the page scroll behind the overlay so there is only ONE scrollbar (the
   overlay's own). Without this the body keeps its scrollbar while the overlay
   adds a second, the double-scrollbar UX issue. The overlay is fixed to the
   viewport so its scroll is independent of page length. (Mobile already locks
   body scroll in responsive.css.) */
body.mega-open { overflow: hidden; }
body.mega-open .mega-overlay {
  position: fixed;
  top: 0;
  bottom: 0;
  left: var(--rail-left-w);
  height: 100vh;
}
/* While Browse is open the overlay must cover the RIGHT rail too, otherwise the
   underlying page's right-rail content (e.g. the Community Who-to-follow cards)
   shows beside Browse. The overlay is its own surface; nothing of the page
   beneath it should be visible except the decorative left brand rail. */
body.mega-open .mega-overlay { right: 0; }
/* The left rail stays as the brand anchor, but its page-specific vertical
   phrase (.rail-text) belongs to the page underneath. Hide it while Browse is
   open so the rail does not show the PREVIOUS page's words (e.g. "a trade,
   direct." carried over from Home). Brand mark + foot tag remain. */
body.mega-open .rail .rail-text { visibility: hidden; }

.mega-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 48px;
}
.mega-nav .links {
  display: flex;
  gap: 24px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 500;
}
.mega-nav .links a { cursor: pointer; transition: color 0.15s; }
.mega-nav .links a:hover { color: var(--rust); }
.mega-nav .links a.is-open { color: var(--rust); font-weight: 700; }
.mega-nav .links a.is-open::after { content: '\25BE'; margin-left: 4px; }

.mega-close {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rust);
  font-weight: 700;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
}

.mega-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 64px;
  line-height: 0.9;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.mega-title em {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--rust);
  font-weight: 300;
  text-transform: lowercase;
}
.mega-sub {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 20px;
  color: var(--ink);
  margin-bottom: 48px;
  max-width: 540px;
  line-height: 1.4;
}

/* --- Mega menu: the discipline index -------------------------------------- */
.mega-head { margin-bottom: 30px; }
.mega-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--rust);
  font-weight: 700;
  margin-bottom: 14px;
}
/* the reference-document scale line */
.mega-scope {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-top: 18px;
}
.mega-scope b { color: var(--rust); font-weight: 700; }
.mega-scope-sep { margin: 0 9px; color: var(--line); }

.mega-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0 48px;
}
.mega-disc {
  min-width: 0;
  padding: 14px 0 16px;
  border-top: 1px solid var(--line);
}
.mega-disc-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  text-decoration: none;
  cursor: pointer;
  margin-bottom: 6px;
}
.mega-disc-num {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--rust);
}
.mega-disc-name {
  flex: 1;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink);
  transition: color 0.15s;
}
.mega-disc-head:hover .mega-disc-name { color: var(--rust); }
/* Touch feedback: pressing the discipline head briefly flashes a soft sand
   background plus the rust colour, so the user sees confirmation of the
   tap even with a finger on the link. No effect on desktop hover, which
   is already handled above. */
.mega-disc-head:active {
  background: var(--sand);
}
.mega-disc-head:active .mega-disc-name,
.mega-disc-head:active .mega-disc-num {
  color: var(--rust);
}
.mega-disc-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 5px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rust);
  border: 1px solid var(--rust);
  border-radius: 2px;
}
.mega-disc-subs { list-style: none; }
.mega-disc-sub {
  display: block;
  padding: 3.5px 0 3.5px 14px;
  text-indent: -14px;
  font-family: var(--font-display);
  font-size: 13px;
  line-height: 1.3;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  transition: color 0.15s;
}
.mega-disc-sub::before {
  content: '\00B7';
  margin-right: 8px;
  color: var(--line);
}
.mega-disc-sub:hover { color: var(--rust); }
.mega-disc-sub:hover::before { color: var(--rust); }
/* Touch feedback: same pattern as the discipline head. */
.mega-disc-sub:active {
  background: var(--sand);
  color: var(--rust);
}
.mega-disc-sub:active::before { color: var(--rust); }

/* "+ N more" link into the directory, shown when a discipline has more
   sub-disciplines than the launcher card lists. */
.mega-disc-more {
  display: block;
  padding: 5px 0 0 14px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rust);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}
.mega-disc-more:hover { color: var(--rust-dk); }
/* Touch feedback. */
.mega-disc-more:active {
  background: var(--sand);
  color: var(--rust-dk);
}

/* --- Mega menu: engagement / region footer band --------------------------- */
.mega-foot {
  margin-top: 44px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 32px;
  flex-wrap: wrap;
}
.mega-quick-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.mega-quick {
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.mega-quick:hover { border-color: var(--rust); color: var(--rust); }
.mega-foot-all {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--rust);
  cursor: pointer;
  white-space: nowrap;
}
.mega-foot-all:hover { text-decoration: underline; }

/* ============================================================================
   V0 PLACEHOLDER, shown in the middle column and right rail until a real
   page module is registered for the route. Removed phase by phase.
   ============================================================================ */
.v0-kicker {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--rust);
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 28px;
}
.v0-kicker::before {
  content: '';
  display: block;
  width: 48px;
  height: 1px;
  background: var(--rust);
}
.v0-heading {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 72px;
  line-height: 0.9;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 28px;
}
.v0-heading em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 300;
  color: var(--rust);
  text-transform: lowercase;
}
.v0-note {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 22px;
  line-height: 1.45;
  max-width: 600px;
  color: var(--ink);
}
.v0-route-pill {
  display: inline-block;
  margin-top: 32px;
  padding: 8px 14px;
  background: var(--ink);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.v0-aside-note {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 16px;
  line-height: 1.5;
  color: rgba(255,255,255,0.7);
}

/* ============================================================================
   SITE FOOTER, global chrome, rendered on every page by pageShell.js.
   ============================================================================ */
.site-footer {
  background: var(--ink);
  color: rgba(255, 255, 255, 0.6);
  padding: 64px 64px 0;
}
.ft-inner {
  display: grid;
  grid-template-columns: 1.7fr 1fr 1fr 1fr;
  gap: 48px;
}
.ft-mark {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 900;
  letter-spacing: -0.02em;
  color: #fff;
  margin-bottom: 14px;
}
.ft-x { color: var(--rust-on-dark); }  /* S53: --rust was 2.87:1 on ink, failing
                                          even the 3:1 large-text bar */
.ft-blurb {
  font-size: 13px;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.5);
  max-width: 40ch;
  margin-bottom: 16px;
}
.ft-mail {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  /* S53 mobile MEDIUM-1: was 14px tall. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--rust-on-dark);  /* S53: on-dark accent, 5.43:1 on ink */
}
.ft-mail:hover { color: #fff; }
.ft-col { display: flex; flex-direction: column; align-items: flex-start; }
.ft-col-h {
  font-family: var(--font-mono);
  font-size: 11px;   /* PF-101/PF-103 */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--rust-on-dark);  /* S53 */
  margin-bottom: 16px;
}
.ft-col a {
  font-size: 13px;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.62);
  /* S53 mobile MEDIUM-1: 6px gave a 30.2px target on 26 footer links. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 0;
  transition: color 0.15s;
}
.ft-col a:hover { color: #fff; }
.ft-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  margin-top: 56px;
  padding: 22px 0 24px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.ft-copy {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  color: rgba(255, 255, 255, 0.62);   /* PF-101: 40% white was 3.8:1 */
}
.ft-tag {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  color: var(--rust-on-dark);  /* S53 */
}
@media (max-width: 860px) {
  /* S53 mobile HIGH-2: the footer is a SIBLING of .page, so .main's 80px
     mobile bottom padding never reached it, scrolled fully down, the fixed
     56px bottom bar sat exactly on footer.bottom, hiding "// CUTTING EDGE"
     entirely and all but 2px of the copyright line, on every route. The
     clearance has to be on the footer itself. */
  .site-footer {
    padding: 48px 24px calc(56px + env(safe-area-inset-bottom, 0px));
  }
  .ft-inner { grid-template-columns: 1fr 1fr; gap: 32px; }
  .ft-bar { flex-direction: column; align-items: flex-start; gap: 8px; }
}
@media (max-width: 520px) {
  .ft-inner { grid-template-columns: 1fr; }
}

/* ============================================================================
   CLS, layout-shift mitigations (May 2026)
   The perf harness measured CLS of 0.149 to 0.168 on some scenarios, over
   the 0.1 "good" target. These rules reserve space for content that arrives
   async, so the visible layout does not jump when it lands.
   ============================================================================ */

/* The main column reserves at least one viewport-worth of height so the
   footer does not "pop up" then push down when page content renders. */
.main {
  min-height: calc(100vh - 64px);
}

/* The nav strip reserves its rendered height so the crumb update on route
   change does not cause a brief height collapse. */
.nav-strip {
  min-height: 56px;
}

/* All <img> tags inside content carry intrinsic aspect-ratio. Without an
   explicit width and height, an image taking time to load causes the
   content below it to shift down when the dimensions become known. */
img {
  height: auto;
  max-width: 100%;
}
/* Note: explicit width and height attributes on an <img> let modern browsers
   derive the intrinsic ratio natively, so no CSS rule is needed here. An earlier
   rule using attr() in this position was removed: attr() is not supported outside
   the content property, so the parser dropped it and emitted a console warning
   for no benefit. */

/* CLS mitigations end here. */

/* ============================================================================
   Page-render loading indicator. Shows during async page renders (when the
   page fetches data, etc.). For sync renders the state flickers on and off
   in the same microtask and is never visible, which is what we want.
   ============================================================================ */
body.is-loading { cursor: progress; }
/* Thin rust progress bar at the very top during any render (kept, instant feedback). */
body.is-loading::after {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: var(--rust);
  z-index: 1000;
  animation: pageLoadingBar 1.4s ease-out infinite;
  transform-origin: left center;
}
@keyframes pageLoadingBar {
  0%   { transform: scaleX(0);   opacity: 1; }
  60%  { transform: scaleX(0.9); opacity: 1; }
  100% { transform: scaleX(1);   opacity: 0; }
}

/* RoboWorx-branded full-screen loading overlay (Facebook-style splash, on brand).
   Hidden by default; the router's body.is-loading reveals it. It fades in only
   AFTER a short delay so quick navigations never flash it, slow sections (the
   ones that take a moment) get a clean branded screen instead of a blank wait. */
.rwx-loading {
  position: fixed; inset: 0; z-index: 900;
  background: var(--ink);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  opacity: 0; visibility: hidden;
  transition: opacity 240ms ease, visibility 0s linear 240ms;
}
body.is-loading .rwx-loading {
  visibility: visible;
  opacity: 1;
  /* 220ms reveal delay: faster renders finish before the splash ever appears. */
  transition: opacity 240ms ease 220ms, visibility 0s;
}
.rwx-loading-inner { display: flex; flex-direction: column; align-items: center; gap: 28px; }
.rwx-loading-mark {
  font-family: var(--font-display); font-weight: 900; font-size: 84px; line-height: 1;
  letter-spacing: -0.03em; color: #fff;
  animation: rwxMarkPulse 1.6s ease-in-out infinite;
}
.rwx-loading-mark em { font-style: normal; color: var(--rust); }
@keyframes rwxMarkPulse {
  0%, 100% { transform: scale(1);    opacity: 1; }
  50%      { transform: scale(1.04); opacity: 0.88; }
}
.rwx-loading-bar {
  width: 140px; height: 3px; border-radius: 3px;
  background: rgba(255,255,255,0.12); overflow: hidden;
}
.rwx-loading-bar span {
  display: block; width: 40%; height: 100%; border-radius: 3px;
  background: var(--rust);
  animation: rwxBar 1.1s ease-in-out infinite;
}
@keyframes rwxBar {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(360%); }
}
/* 0.62 alpha, not 0.4. At 0.4 this composites to about #757575 on the ink
   ground, which is 3.75:1 at 11px, under the 4.5:1 bar; 0.62 gives 7.3:1 and
   still reads as subdued rather than as a heading.

   WHY IT WAS NEVER CAUGHT: the splash has a 220 ms reveal delay so fast
   navigations do not flash it, and on an idle machine every render finishes
   inside that window, so the design gate never saw the element. It surfaced
   at S57c only because the full battery had the box busy enough for pages to
   resolve slowly, which is precisely the condition a real user on a poor
   connection is in. The gate was right and it was right the whole time; it
   just needed the machine to be as slow as a phone on 3G to prove it. */
.rwx-loading-foot {
  position: absolute; bottom: 48px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; color: rgba(255,255,255,0.62);
}
@media (prefers-reduced-motion: reduce) {
  .rwx-loading-mark, .rwx-loading-bar span { animation: none; }
}

/* ============================================================================
   P2.0 H5: the sample-data banner. Appended to <body> by lib/dataSource.js
   the first time a bundled sample record is actually served, so local review
   can never be mistaken for real members. Fixed, unmissable, session-long.
   ============================================================================ */
/* S53 mobile LOW-1: at z-index 9999 and bottom:0 this banner completely
   buried the 56px bottom bar (z-index 45), the entire primary mobile
   navigation. It only renders in local review (SAMPLE_FALLBACK_ALLOWED is
   false off loopback), but the conflict is latent, so it now sits above the
   bar rather than on top of it. */
.sample-banner {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 9999;
  padding: 10px 16px;
  background: #b7410e;
  color: #fff;
  font-size: 13px;
  line-height: 1.5;
  text-align: center;
  box-shadow: 0 -2px 12px rgba(0,0,0,0.35);
}
