/* ==========================================================================
   Nick Farrugia — portfolio
   "Ops console": the portfolio presented as the kind of panel you'd operate.
   Rail + status strip + metric tiles + panels. Zero dependencies, no build.
   ========================================================================== */

/* --------------------------------------------------------------- tokens -- */
:root {
  /* Surfaces — a cool blue-grey ramp, rail darkest, panels lifted off it. */
  --bg:            #0a0d13;
  --rail:          #05070d;
  --strip:         #07090f;
  --panel:         #0d1017;
  --panel-2:       #101319;
  --border-soft:   #1c1f27;
  --border:        #252930;
  --border-strong: #343840;

  /* Text */
  --fg:            #e2e5ea;
  --fg-muted:      #a6abb5;
  --fg-subtle:     #99a0ac;

  /* Colour is semantic here: blue = interactive, green = live/ok,
     amber = in progress / warning, red = severity. Never decorative. */
  --accent:        #4baeed;
  --accent-bright: #6dcfff;
  --ok:            #62bb78;
  --ok-bright:     #76cf8a;
  --warn:          #e8aa4e;
  --danger:        #f9786a;
  /* Violet marks the DECLARED side of the system — what Git and ArgoCD own —
     against green for what is actually running. Not decoration: the resource
     graph reads left-to-right as declaration → runtime. */
  --violet:        #9d86f2;
  --violet-bright: #b9a6ff;
  --on-ok:         #041107;
  --on-accent:     #041018;

  /* Type */
  --font-sans: 'Space Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;

  --fs-cap:  0.6875rem;   /* 11px — the floor; nothing smaller carries text */
  --fs-sm:   0.8125rem;
  --fs-base: 0.9375rem;
  --fs-body: 1rem;
  --fs-lead: clamp(1rem, 1.5vw, 1.125rem);
  --fs-h3:   clamp(1.0625rem, 2vw, 1.25rem);
  --fs-h2:   clamp(1.375rem, 2.8vw, 1.75rem);
  --fs-h1:   clamp(1.875rem, 4.2vw, 2.75rem);

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;

  --radius-sm: 5px;
  --radius:    8px;
  --radius-lg: 12px;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur:      190ms;

  --rail-w:  208px;
  --header-h: 0px;   /* desktop: the rail is beside the content, not above it */
  --content-max: 1860px;  /* beyond this the panels stop gaining anything */
  --gutter:  clamp(1rem, 2.5vw, 1.75rem);
  --view-y:  clamp(1.75rem, 3.5vw, 2.75rem);
}

/* ---------------------------------------------------------------- reset -- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* Matches the sticky header height per breakpoint; see --header-h. */
  scroll-padding-top: calc(var(--header-h) + var(--space-4));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* `overflow-x: hidden` computes overflow-y to `auto`, which makes <body> a
     scroll container. Fragment navigation and scrollIntoView then target that
     box instead of the viewport, so #hash links land nowhere. `clip` gives the
     same protection against sideways overflow WITHOUT creating a scroll box. */
  overflow-x: clip;
}

h1, h2, h3 {
  margin: 0;
  color: var(--fg);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

p { margin: 0; }
img, svg { max-width: 100%; display: block; }
ul, ol { margin: 0; padding: 0; list-style: none; }
dl, dd { margin: 0; }

a { color: inherit; text-decoration: none; transition: color var(--dur) var(--ease-out); }

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.05em 0.35em;
  color: var(--fg);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

::selection { background: var(--accent); color: var(--on-accent); }

/* ------------------------------------------------------------- utilities -- */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: -100px;
  z-index: 999;
  background: var(--accent);
  color: var(--on-accent);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  font-weight: 600;
  transition: top var(--dur) var(--ease-out);
}
.skip-link:focus { top: var(--space-4); }

/* The console's small-caps label. 11px is the floor — never smaller. */
.cap {
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--fg-subtle);
}

.mono { font-family: var(--font-mono); }

/* ----------------------------------------------------------------- shell -- */
.shell { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); }

.rail {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding: var(--space-5) 0;
  background: var(--rail);
  border-right: 1px solid var(--border);
}

.brand {
  display: block;
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius);
}
.brand-name { display: block; font-size: var(--fs-base); font-weight: 600; color: var(--fg); letter-spacing: -0.01em; }
.brand:hover .brand-name { color: var(--accent); }

.rail-nav ul { display: flex; flex-direction: column; gap: 1px; }
.rail-nav a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  padding: 0 var(--space-5);
  font-size: var(--fs-base);
  color: var(--fg-muted);
  border-left: 2px solid transparent;
  transition: color var(--dur) var(--ease-out),
              background var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.rail-nav a svg { width: 15px; height: 15px; flex-shrink: 0; opacity: 0.75; }
.rail-nav a:hover { color: var(--fg); background: var(--panel); }
/* Reading position, set by the scroll spy in main.js. */
.rail-nav a[aria-current='true'] {
  color: var(--fg);
  background: rgba(75, 174, 237, 0.1);
  border-left-color: var(--accent);
}
.rail-nav a[aria-current='true'] svg { opacity: 1; color: var(--accent); }

.rail-foot { margin-top: auto; padding: 0 var(--space-5); }

/* ------------------------------------------------------------ status bar -- */
.statusbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--gutter);
  background: rgba(7, 9, 15, 0.92);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
.statusbar-main {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--fs-base);
  color: var(--fg);
}
.statusbar-note { margin-left: auto; }

/* Static by design. A pulsing dot reads as live telemetry, and this page
   fetches nothing — the strip is a dated snapshot, so the marker holds still. */
.status-dot {
  width: 8px; height: 8px;
  border-radius: 2px;
  background: var(--ok);
  flex-shrink: 0;
}

/* ----------------------------------------------------------------- views -- */
/* The console fills the window — capping the shell just leaves a dead gutter
   beside the rail. What actually needs limiting is the line length, and that
   is capped per text block instead (see .case-facts dd, .row p). */
/* The cap goes on the content column, never on the card contents: a panel
   whose border stretches past its own body leaves a hole INSIDE the box, which
   reads as broken. Capping here keeps header, border and body on one edge and
   puts the surplus outside, where it reads as page margin. */
.view, .statusbar, .site-footer {
  width: 100%;
  max-width: calc(var(--content-max) + var(--gutter) * 2);
  margin-inline: auto;
}
.view { padding: var(--view-y) var(--gutter); }
.view + .view { padding-top: 0; }

.view-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-4); }
.view-head h2 { font-size: var(--fs-h2); }

/* --------------------------------------------------------------- overview -- */
.overview h1 { font-size: var(--fs-h1); letter-spacing: -0.03em; max-width: 22ch; }
.overview h1 .accent { color: var(--ok-bright); }
.overview-lede {
  margin-top: var(--space-4);
  font-size: var(--fs-lead);
  color: var(--fg-muted);
  max-width: 62ch;
}
.overview-lede strong { color: var(--fg); font-weight: 600; }

.actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }

/* --------------------------------------------------------------- buttons -- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) var(--space-5);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.btn:active { transform: scale(0.98); }
.btn svg { width: 15px; height: 15px; }

.btn-primary { background: var(--ok); color: var(--on-ok); font-weight: 600; }
.btn-primary:hover { background: var(--ok-bright); }

.btn-ghost { border-color: var(--border-strong); color: var(--fg); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); background: rgba(75, 174, 237, 0.08); }

.btn-quiet { color: var(--fg-muted); }
.btn-quiet:hover { color: var(--fg); background: var(--panel-2); }

.btn-rail { width: 100%; }

/* ----------------------------------------------------------------- tiles -- */
.tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-8);
}
.tile {
  padding: var(--space-4);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.tile dt { margin-bottom: var(--space-2); }
.tile dd {
  font-size: clamp(1.5rem, 3vw, 1.875rem);
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.tile dd span { font-size: 0.9rem; color: var(--fg-subtle); font-weight: 400; }

/* Bars read as gauges, so they encode something true: a share of a whole
   where one exists, and a full bar where the number is simply a count. */
.tile-bar { height: 3px; margin-top: var(--space-3); background: var(--border); border-radius: 2px; overflow: hidden; }
.tile-bar i { display: block; height: 100%; transform-origin: left center; }
[data-reveal-ready] .tile-bar i { transform: scaleX(0); }
[data-reveal-ready] .is-visible .tile-bar i {
  transform: scaleX(1);
  transition: transform 820ms var(--ease-out) 180ms;
}

/* ---------------------------------------------------------------- panels -- */
.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.panel + .panel { margin-top: var(--space-3); }

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border);
  background: var(--panel-2);
}
.panel-title { font-size: var(--fs-base); font-weight: 600; color: var(--fg); }
.panel-body { padding: var(--space-5); }

/* ---------------------------------------------------------- architecture -- */
.arch-flow { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--space-3); }
.arch-node {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.arch-node--terminal { border-color: rgba(98, 187, 120, 0.45); background: rgba(98, 187, 120, 0.07); }
/* Same idea as the resource graph: violet is the declared side — ArgoCD owns
   whatever Git says — and green is what ends up actually running. */
.arch-node--gitops { border-color: rgba(157, 134, 242, 0.5); background: rgba(157, 134, 242, 0.08); }
.arch-node--gitops .arch-step { color: var(--violet-bright); }
.arch-step { font-family: var(--font-mono); font-size: var(--fs-cap); color: var(--accent); letter-spacing: 0.1em; }
.arch-node--terminal .arch-step { color: var(--ok-bright); }
.arch-title { font-size: var(--fs-base); font-weight: 600; color: var(--fg); }
.arch-desc { font-size: var(--fs-sm); color: var(--fg-subtle); line-height: 1.45; }

.arch-node + .arch-node::before {
  content: '';
  position: absolute;
  left: calc(var(--space-3) / -2 - 3px);
  top: 50%;
  width: 6px; height: 6px;
  border-top: 1.5px solid var(--border-strong);
  border-right: 1.5px solid var(--border-strong);
  transform: translateY(-50%) rotate(45deg);
}

.arch-obs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  align-items: baseline;
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--border-strong);
}
.arch-obs-item { font-size: var(--fs-sm); color: var(--fg); }
.arch-obs-item span { color: var(--fg-subtle); }

/* -------------------------------------------------------------- services -- */
.case { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--space-6); align-items: start; }

.case-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-4); }
.case-id { font-family: var(--font-mono); font-size: var(--fs-base); color: var(--fg); font-weight: 500; }
.case h3 { font-size: var(--fs-h3); margin-top: var(--space-1); }

.case-facts { display: flex; flex-direction: column; gap: var(--space-4); }
.case-facts dt {
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--space-1);
}
.case-facts dd { font-size: var(--fs-base); color: var(--fg-muted); max-width: 80ch; }
.case-facts strong { color: var(--fg); font-weight: 600; }

/* Detail ships open, so desktop and no-JS show the whole case; main.js
   collapses it at phone width where the outcome alone carries the row. */
.case-facts--result { margin-top: var(--space-4); }

/* ------------------------------------------------------------- disclose -- */
/* One pattern for every collapsible: case studies, diagrams, timelines.
   Ships open, so desktop and no-JS render everything; main.js collapses the
   ones marked data-collapse="mobile" below 720px. */
.disclose > summary { display: none; }

/* A closed panel ALWAYS offers its control, at any width, so it is never a
   dead end. Never force a closed panel's contents visible instead — a closed
   <details> lays its children outside the flow, and they paint over whatever
   follows. */
.disclose:not([open]) > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--accent);
}
/* `.case-facts { display: flex }` is an author rule on a child of the
   <details>, which overrides the user agent's hiding of a closed panel.
   Hide it explicitly rather than trusting the UA. */
.disclose:not([open]) > *:not(summary) { display: none; }
.disclose > summary::-webkit-details-marker { display: none; }
.disclose > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.disclose > summary:hover { color: var(--accent-bright); }
.disclose .chev { width: 14px; height: 14px; flex-shrink: 0; transition: transform var(--dur) var(--ease-out); }
.disclose[open] .chev { transform: rotate(180deg); }
.evidence-detail { min-width: 0; }

/* A closed panel ALWAYS offers its control, at any width. Previously the
   desktop rule force-rendered the contents of a closed <details> instead —
   which paints them outside the parent's flow, straight over the Result block
   below (163px of text on text). Showing the summary cannot overlap anything,
   and a closed panel is never a dead end. */
/* Load-bearing: `.case-facts { display: flex }` is an author declaration on a
   direct child of the <details>, which overrides the user agent's hiding of a
   closed panel — the content then paints out of flow, on top of the Result
   block. Hide it explicitly instead of relying on the UA. */



.case-links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); margin-top: var(--space-5); }
.case-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--accent);
}
.case-link svg { width: 14px; height: 14px; }
.case-link:hover { color: var(--accent-bright); text-decoration: underline; text-underline-offset: 4px; }
.case-note { font-family: var(--font-mono); font-size: var(--fs-cap); color: var(--fg-subtle); }

.tags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-5); }
.tag {
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--fg-muted);
  white-space: nowrap;
}

/* State pills describe the state of the WORK, not live telemetry. */
.pill {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  border: 1px solid;
}
.pill--live  { color: var(--ok-bright); border-color: rgba(98, 187, 120, 0.45); background: rgba(98, 187, 120, 0.1); }
.pill--build { color: var(--warn);      border-color: rgba(232, 170, 78, 0.45); background: rgba(232, 170, 78, 0.1); }
.pill--res   { color: var(--accent);    border-color: rgba(75, 174, 237, 0.45);  background: rgba(75, 174, 237, 0.1); }
.pill--sev1  { color: var(--danger);    border-color: rgba(249, 120, 106, 0.45); background: rgba(249, 120, 106, 0.1); }
.pill--sev2  { color: var(--warn);      border-color: rgba(232, 170, 78, 0.45);  background: rgba(232, 170, 78, 0.1); }

/* ------------------------------------------------------ visual evidence -- */
/* A supporting figure should not outgrow the case it supports. Left
   uncapped, the screenshot reaches ~920px on a wide monitor and runs well
   past the text beside it; centred within its column, the leftover width
   reads as margin rather than a hole. */
/* width:100% is load-bearing: auto inline margins make a grid item
   shrink-to-fit, and the inner img sizes from its parent, so without an
   explicit width the whole figure collapses to a thumbnail. */
/* margin-block:0 matters — the UA gives <figure> a 1em block margin, which
   pushed this 16px below the figure beside it. */
.media-slot { width: 100%; max-width: 720px; margin: 0 auto; }
.media-slot--filled img {
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
}
.media-slot-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 190px;
  padding: var(--space-5) var(--space-4);
  text-align: center;
  background: repeating-linear-gradient(45deg, rgba(166, 171, 181, 0.03) 0 10px, transparent 10px 20px), var(--bg);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
}
.media-slot-inner svg { width: 24px; height: 24px; color: var(--fg-subtle); opacity: 0.6; }
.media-slot-hint { font-size: var(--fs-sm); color: var(--fg-muted); max-width: 32ch; }
/* 0.9em on an already-11px parent would fall under the readable floor. */
.media-slot-file { font-size: var(--fs-cap); }
.media-slot-file code { font-size: 1em; }
.media-slot figcaption, .timeline-fig figcaption {
  margin-top: var(--space-2);
  font-size: var(--fs-cap);
  color: var(--fg-subtle);
  text-align: center;
}
/* Two pieces of evidence stack on narrow screens and sit side by side once
   there is room — stacked, they run far past the text beside them and leave a
   hole in the card. */
.case-evidence {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  container-type: inline-size;
  container-name: evidence;
}
/* 1800px, not lower: below it the split leaves each figure around 220px —
   balanced, but too small to read, which trades one problem for another. */
/* Both evidence items take the FULL column width, one above the other, so
   their edges line up and the screenshot is never squeezed into a fraction of
   the column. Stacking normally costs height — the timeline turns horizontal
   below to pay that back. */



/* -------------------------------------------------- resource graph -------- */
/* An ArgoCD-style resource tree for one application, drawn as SVG so the edge
   geometry is exact rather than guessed from CSS boxes. It spans both columns
   at the foot of the card, which is where the space actually was. */
/* min-width:0 is load-bearing: a grid item defaults to a min-width of auto, so
   the SVG's 620px min-width propagated up and stretched the whole card to
   620px inside a 390px screen. */
.case > .case-figure--wide { grid-column: 1 / -1; margin: 0; min-width: 0; }

/* The drawing pans inside this box; it must never widen the page. */
.rg-scroll { overflow-x: auto; max-width: 100%; min-width: 0; }
/* Left-aligned, not centred: centring put the drawing 300px right of its own
   title and the card text, which read as a misalignment rather than a margin.
   The 1400px viewBox means filling a wide card lands near 1:1 scale instead of
   blowing the labels up; min-width makes it pan in .rg-scroll on narrow cards
   rather than shrinking the type to nothing. */
.rg {
  width: 100%;
  min-width: 1100px;
  height: auto;
  display: block;
}

.rg-node-box {
  fill: var(--bg);
  stroke: var(--border-strong);
  stroke-width: 1.25;
  rx: 6;
}
.rg-node-box--app { stroke: rgba(157, 134, 242, 0.65); fill: rgba(157, 134, 242, 0.1); }
.rg-node--app .rg-name { fill: var(--violet-bright); }
.rg-tier.rg-tier--declared { fill: var(--violet); }
.rg-node-box--pod { stroke: rgba(98, 187, 120, 0.5); fill: rgba(98, 187, 120, 0.07); }

.rg-kind {
  font-family: var(--font-mono);
  font-size: 13px;
  fill: var(--fg-subtle);
  letter-spacing: 0.06em;
}
.rg-name { font-family: var(--font-mono); font-size: 16px; fill: var(--fg); }
.rg-meta { font-family: var(--font-mono); font-size: 10px; fill: var(--fg-subtle); }
.rg-tier {
  font-family: var(--font-mono);
  font-size: 12px;
  fill: var(--fg-subtle);
  letter-spacing: 0.14em;
}

/* Marching ants along each edge — the same idiom ArgoCD uses to show which way
   the graph flows. It depicts wiring, not live traffic. */
.rg-edge {
  fill: none;
  stroke: rgba(157, 134, 242, 0.7);   /* declaration: repo → app → resources */
  stroke-width: 1.75;
  stroke-dasharray: 7 6;
  animation: rgFlow 1.4s linear infinite;
}
.rg-edge--live { stroke: rgba(98, 187, 120, 0.55); }
@keyframes rgFlow { to { stroke-dashoffset: -13; } }

/* Left-aligned to match the drawing and the title above it. */
.resource-graph figcaption {
  margin-top: var(--space-3);
  font-size: var(--fs-cap);
  color: var(--fg-subtle);
  max-width: 90ch;
}

/* -------------------------------------------------------------- diagram -- */
.diagram { margin: 0; }
.diagram-title { margin-bottom: var(--space-2); }

.dg-host {
  padding: var(--space-3);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.dg-host-label { margin-bottom: var(--space-3); color: var(--fg-muted); }

.dg-nodes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); }
.dg-node {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--space-2) var(--space-3);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
/* The control plane is the one node that differs, so it is the one that is marked. */
.dg-node--cp { border-color: rgba(75, 174, 237, 0.5); background: rgba(75, 174, 237, 0.08); }
.dg-node-name { font-family: var(--font-mono); font-size: var(--fs-cap); color: var(--fg); }
.dg-node--cp .dg-node-name { color: var(--accent); }
.dg-node-role { font-size: var(--fs-cap); color: var(--fg-subtle); }

.dg-aside {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--fs-cap);
  color: var(--fg-subtle);
}
.dg-aside span { font-family: var(--font-mono); color: var(--fg-muted); }

/* The rail joining host to platform services. */
.dg-link { width: 1px; height: var(--space-4); margin-inline: auto; background: var(--border-strong); }

.dg-services { display: flex; flex-direction: column; gap: var(--space-2); }
.dg-services li {
  padding: var(--space-2) var(--space-3);
  background: var(--bg);
  border: 1px solid var(--border);
  border-left: 2px solid var(--ok);
  border-radius: var(--radius-sm);
  font-size: var(--fs-cap);
  color: var(--fg-subtle);
}
.dg-services span { font-family: var(--font-mono); color: var(--fg); }

.diagram figcaption {
  margin-top: var(--space-3);
  font-size: var(--fs-cap);
  color: var(--fg-subtle);
  text-align: center;
}

/* -------------------------------------------------------------- timeline -- */
.timeline-fig { margin: 0; }
.timeline {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.timeline li { position: relative; display: flex; flex-direction: column; padding-left: var(--space-6); }
.timeline li::before {
  content: '';
  position: absolute;
  left: 0; top: 0.4em;
  width: 9px; height: 9px;
  border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  background: var(--panel);
}
.timeline li:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 4px;
  top: calc(0.4em + 11px);
  bottom: calc(var(--space-3) * -1);
  width: 1px;
  background: var(--border);
}
.timeline-done::before { border-color: var(--ok); background: var(--ok); }

/* Wide enough for the steps to sit across the card: the sequence reads
   left-to-right like the pipeline in Fig. 1, and the whole block costs ~110px
   of height instead of ~330px, which is what lets the screenshot below it run
   the full width of the column. */
/* A container query, not a viewport one: whether five steps fit across is a
   fact about THIS column's width, and the column varies with the card layout.
   At a 1280px viewport the viewport-based version squeezed each step to 69px
   and four of the five overflowed. */
@container evidence (min-width: 640px) {
  .case-evidence .timeline { flex-direction: row; align-items: stretch; }
  .case-evidence .timeline li { flex: 1 1 0; min-width: 0; padding-left: 0; padding-top: var(--space-5); }
  .case-evidence .timeline li::before { top: 0; left: 0; }
  .case-evidence .timeline li:not(:last-child)::after {
    top: 4px;
    left: 13px;
    right: calc(var(--space-3) * -1);
    bottom: auto;
    width: auto;
    height: 1px;
  }
  .case-evidence .timeline-label { font-size: var(--fs-sm); }
  .case-evidence .timeline-desc { font-size: var(--fs-cap); line-height: 1.45; }
}
.timeline-label { font-size: var(--fs-base); font-weight: 600; color: var(--fg); }
.timeline-desc { font-size: var(--fs-sm); color: var(--fg-subtle); }
.timeline-done .timeline-desc strong { color: var(--ok-bright); font-family: var(--font-mono); }

/* ------------------------------------------------------- smaller builds -- */
.rows { display: flex; flex-direction: column; }
.row {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 2.2fr) auto;
  gap: var(--space-3) var(--space-5);
  align-items: center;
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border-soft);
}
.rows > .row:first-child { border-top: 0; }
.row-name { font-family: var(--font-mono); font-size: var(--fs-base); color: var(--fg); font-weight: 500; }
.row-kind { margin-top: 2px; }
.row p { font-size: var(--fs-sm); color: var(--fg-muted); max-width: 90ch; }
.row-tags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }


/* The smaller-builds list collapses on phones; its summary still names all
   three, so nothing disappears from the page — it just stops costing 800px. */
.rows-detail > summary { padding: var(--space-4) var(--space-5); }
.rows-detail:not([open]) > summary { min-height: 56px; }

/* ------------------------------------------------------------- incidents -- */
.feed { display: flex; flex-direction: column; }
.feed-item { border-top: 1px solid var(--border-soft); }
.feed > .feed-item:first-child { border-top: 0; }

/* align-items:start matters — a pill is a grid item here, and the default
   stretch turns it into a tall oval as the row grows. */
.incident-lead {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--space-4);
  padding: var(--space-5);
}
.incident-lead h3 { font-size: var(--fs-h3); }

.incident-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-4); margin-top: var(--space-4); }
.incident-facts dt {
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--fg-subtle);
  margin-bottom: var(--space-1);
}
.incident-facts dd { font-size: var(--fs-base); color: var(--fg-muted); max-width: 70ch; }
.incident-facts .resolved { color: var(--fg); }

.feed-item summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4) var(--space-5);
  cursor: pointer;
  list-style: none;
}
.feed-item summary::-webkit-details-marker { display: none; }
.feed-item summary:hover { background: var(--panel-2); }
.feed-item summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.feed-title { font-size: var(--fs-base); font-weight: 600; color: var(--fg); }
.feed-item .chev { width: 15px; height: 15px; color: var(--fg-subtle); transition: transform var(--dur) var(--ease-out); }
.feed-item[open] .chev { transform: rotate(180deg); }
.feed-item[open] summary { background: var(--panel-2); }
.feed-item .incident-facts { margin-top: 0; padding: 0 var(--space-5) var(--space-5); }

.status-ok { font-family: var(--font-mono); font-size: var(--fs-cap); letter-spacing: 0.07em; text-transform: uppercase; color: var(--ok); }


/* ------------------------------------------------------------------ next -- */
/* Planned work, not built work. The dashed rule on the left is the tell — the
   same idiom the topology uses for the VM that sits outside the cluster. */
.next-list { display: flex; flex-direction: column; }
.next-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3) var(--space-5);
  padding: var(--space-5);
  border-top: 1px solid var(--border-soft);
}
.next-list > .next-item:first-child { border-top: 0; }
.next-index {
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  letter-spacing: 0.1em;
  color: var(--violet);
  padding-top: 2px;
}
.next-item h3 { font-size: var(--fs-base); margin-bottom: var(--space-2); }
.next-body p { font-size: var(--fs-sm); color: var(--fg-muted); max-width: 84ch; }
.next-body { border-left: 1px dashed var(--border-strong); padding-left: var(--space-5); }

@media (max-width: 720px) {
  .next-item { grid-template-columns: 1fr; gap: var(--space-2); padding: var(--space-4); }
  .next-body { border-left: 0; padding-left: 0; }
}

/* ---------------------------------------------------------------- stack -- */
.skill-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--space-4); }
.skill-group h3 {
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 500;
  margin-bottom: var(--space-3);
}
.skill-group ul { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* --------------------------------------------------------------- contact -- */
.contact-panel { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-5); padding: var(--space-8) var(--space-5); }
.contact-panel h2 { font-size: var(--fs-h2); max-width: 26ch; }
.contact-panel > div > p { margin-top: var(--space-3); color: var(--fg-muted); max-width: 52ch; font-size: var(--fs-base); }
.contact-links { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.contact-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--fg);
  font-size: var(--fs-base);
  transition: border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.contact-link svg { width: 16px; height: 16px; flex-shrink: 0; }
/* The assembled address is one unbreakable token — at 320px its min-content
   width (330px) was setting the floor for the whole page and pushing the grid
   track past the viewport. Let it break rather than dictate the layout. */
.contact-link { min-width: 0; }
#email-text { overflow-wrap: anywhere; }
.contact-link:hover { border-color: var(--accent); color: var(--accent); }
.contact-link--primary { background: var(--ok); border-color: var(--ok); color: var(--on-ok); font-weight: 600; }
.contact-link--primary:hover { background: var(--ok-bright); border-color: var(--ok-bright); color: var(--on-ok); }
.contact-fallback { margin-top: var(--space-4); font-size: var(--fs-sm); }

/* ---------------------------------------------------------------- footer -- */
.site-footer { padding: var(--space-5) var(--gutter) var(--space-8); border-top: 1px solid var(--border); margin-top: var(--view-y); }
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  color: var(--fg-subtle);
}

/* ------------------------------------------------------------ sequences -- */
/* Two one-shot animations, each depicting something that actually happens:
   a job moving through the provisioning pipeline, and a purchase becoming a
   running server. They fire once, when the element is first scrolled into
   view, and settle into the static design. Without the `is-running` class
   (no JS, reduced motion) both render in their finished state. */

/* 1 — a job travels the pipeline, stage by stage. */
@keyframes stageEnter {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes stageActive {
  0%, 100% { border-color: var(--border); background: var(--bg); }
  45%      { border-color: var(--accent); background: rgba(75, 174, 237, 0.12); }
}
/* The stage animations use fill-mode:both, so their last keyframe HOLDS the
   border colour and beats any static rule. A specially-coloured node therefore
   needs its own landing keyframes, exactly as the terminal node does. */
@keyframes stageDeclared {
  0%   { border-color: var(--border); background: var(--bg); }
  45%  { border-color: var(--violet-bright); background: rgba(157, 134, 242, 0.22); }
  100% { border-color: rgba(157, 134, 242, 0.5); background: rgba(157, 134, 242, 0.08); }
}
@keyframes stageLand {
  0%   { border-color: var(--border); background: var(--bg); }
  45%  { border-color: var(--ok-bright); background: rgba(98, 187, 120, 0.2); }
  100% { border-color: rgba(98, 187, 120, 0.45); background: rgba(98, 187, 120, 0.07); }
}
@keyframes arrowOn { from { opacity: 0; } to { opacity: 1; } }

.arch-flow.is-running .arch-node {
  animation: stageEnter 380ms var(--ease-out) both,
             stageActive 620ms var(--ease-out) both;
}
.arch-flow.is-running .arch-node--terminal {
  animation: stageEnter 380ms var(--ease-out) both,
             stageLand 720ms var(--ease-out) both;
}
.arch-flow.is-running .arch-node--gitops {
  animation: stageEnter 380ms var(--ease-out) both,
             stageDeclared 720ms var(--ease-out) both;
}
.arch-flow.is-running .arch-node + .arch-node::before { animation: arrowOn 300ms linear both; }

/* One 130ms step per stage — fast enough to read as a single pass. */
.arch-flow.is-running .arch-node:nth-child(1) { animation-delay: 0ms, 60ms; }
.arch-flow.is-running .arch-node:nth-child(2) { animation-delay: 130ms, 190ms; }
.arch-flow.is-running .arch-node:nth-child(3) { animation-delay: 260ms, 320ms; }
.arch-flow.is-running .arch-node:nth-child(4) { animation-delay: 390ms, 450ms; }
.arch-flow.is-running .arch-node:nth-child(5) { animation-delay: 520ms, 580ms; }
.arch-flow.is-running .arch-node:nth-child(2)::before { animation-delay: 110ms; }
.arch-flow.is-running .arch-node:nth-child(3)::before { animation-delay: 240ms; }
.arch-flow.is-running .arch-node:nth-child(4)::before { animation-delay: 370ms; }
.arch-flow.is-running .arch-node:nth-child(5)::before { animation-delay: 500ms; }

/* 2 — the 46 seconds, replayed: each step completes, the rail draws down,
   and the last dot lands green. */
@keyframes dotPending { from { opacity: 0; transform: scale(0.4); } to { opacity: 1; transform: none; } }
@keyframes railDraw   { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes stepIn     { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }
@keyframes dotLand {
  0%   { opacity: 0; transform: scale(0.4); box-shadow: 0 0 0 0 rgba(98, 187, 120, 0.55); }
  55%  { opacity: 1; transform: scale(1.25); }
  100% { opacity: 1; transform: none; box-shadow: 0 0 0 9px rgba(98, 187, 120, 0); }
}

.timeline.is-running li { animation: stepIn 320ms var(--ease-out) both; }
.timeline.is-running li::before { animation: dotPending 300ms var(--ease-out) both; }
.timeline.is-running li:not(:last-child)::after {
  transform-origin: top center;
  animation: railDraw 260ms linear both;
}
.timeline.is-running .timeline-done::before { animation: dotLand 620ms var(--ease-out) both; }

.timeline.is-running li:nth-child(1) { animation-delay: 0ms; }
.timeline.is-running li:nth-child(2) { animation-delay: 170ms; }
.timeline.is-running li:nth-child(3) { animation-delay: 340ms; }
.timeline.is-running li:nth-child(4) { animation-delay: 510ms; }
.timeline.is-running li:nth-child(5) { animation-delay: 680ms; }
.timeline.is-running li:nth-child(1)::before { animation-delay: 40ms; }
.timeline.is-running li:nth-child(2)::before { animation-delay: 210ms; }
.timeline.is-running li:nth-child(3)::before { animation-delay: 380ms; }
.timeline.is-running li:nth-child(4)::before { animation-delay: 550ms; }
.timeline.is-running li:nth-child(5)::before { animation-delay: 720ms; }
.timeline.is-running li:nth-child(1)::after { animation-delay: 90ms; }
.timeline.is-running li:nth-child(2)::after { animation-delay: 260ms; }
.timeline.is-running li:nth-child(3)::after { animation-delay: 430ms; }
.timeline.is-running li:nth-child(4)::after { animation-delay: 600ms; }

/* -------------------------------------------------------- scroll reveal -- */
[data-reveal-ready] .reveal { opacity: 0; transform: translateY(12px); }
[data-reveal-ready] .reveal.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 460ms var(--ease-out), transform 460ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}



/* ------------------------------------------------------ language switch -- */
.lang-switch {
  display: flex;
  gap: 2px;
  padding: 0 var(--space-5);
}
.lang-switch a {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-cap);
  color: var(--fg-muted);
  transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}
.lang-switch a:hover { color: var(--fg); border-color: var(--border-strong); }
.lang-switch a[aria-current='page'] {
  color: var(--accent);
  border-color: rgba(75, 174, 237, 0.5);
  background: rgba(75, 174, 237, 0.1);
}

/* ------------------------------------------------------------- Japanese -- */
/* Noto Sans JP carries the kana and kanji; the Latin faces do not. Japanese
   also needs looser leading and no negative tracking to stay readable. */
html[lang='ja'] {
  --font-sans: 'Noto Sans JP', 'Space Grotesk', system-ui, sans-serif;
}
html[lang='ja'] body { line-height: 1.85; }
html[lang='ja'] h1,
html[lang='ja'] h2,
html[lang='ja'] h3 { line-height: 1.45; letter-spacing: 0; }
/* Uppercase and wide tracking do nothing for kana but wreck its rhythm, and
   the mono face has no Japanese glyphs to render. */
html[lang='ja'] .cap,
html[lang='ja'] .case-facts dt,
html[lang='ja'] .incident-facts dt {
  font-family: var(--font-sans);
  letter-spacing: 0.04em;
  text-transform: none;
}
html[lang='ja'] .case-facts dd,
html[lang='ja'] .incident-facts dd,
html[lang='ja'] .row p { max-width: 46em; }
html[lang='ja'] .overview-lede { max-width: 40em; }
html[lang='ja'] .disclose > summary,
html[lang='ja'] .case-action,
html[lang='ja'] .case-note { font-family: var(--font-sans); }

/* ---------------------------------------------------------- mobile nav -- */
/* Hidden entirely on desktop, which also removes it from the tab order. */
.nav-toggle, .mobile-nav { display: none; }
.nav-icon { width: 18px; height: 18px; }

/* --------------------------------------------------------- case actions -- */
/* One evidence area per project. Only resources that exist get an action;
   everything else is a plain note, never a link that would 404. */
.case-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-5);
}
.case-action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 0 var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--accent);
  transition: border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.case-action svg { width: 14px; height: 14px; flex-shrink: 0; }
.case-action:hover { border-color: var(--accent); color: var(--accent-bright); }
.case-action--primary { border-color: rgba(98, 187, 120, 0.45); color: var(--ok-bright); }
.case-action--primary:hover { border-color: var(--ok-bright); color: var(--ok-bright); }

.case-purpose {
  margin-top: var(--space-3);
  margin-bottom: var(--space-4);
  font-size: var(--fs-base);
  color: var(--fg);
  max-width: 60ch;
}

/* A screenshot is worth opening full size; the link carries its own label. */
.media-link { display: block; border-radius: var(--radius-sm); }
.media-link:hover img { border-color: var(--accent); }
.media-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.media-slot--filled img { transition: border-color var(--dur) var(--ease-out); }

/* The availability boundary, stated plainly next to the topology. */
.dg-boundary {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-left: 2px solid var(--warn);
  border-radius: var(--radius-sm);
  background: var(--bg);
  font-size: var(--fs-cap);
  line-height: 1.5;
  color: var(--fg-muted);
}

/* ------------------------------------------------- narrow screens -------- */
@media (max-width: 1000px) {
  /* The rail becomes a sticky top bar with a real menu behind a button.
     --header-h feeds scroll-padding-top so deep links clear it. */
  :root { --header-h: 60px; }
  /* minmax(0, …) not 1fr: a bare 1fr track has an automatic minimum of
     min-content, so one long unbreakable string can push the whole column
     wider than the screen. */
  .shell { grid-template-columns: minmax(0, 1fr); }
  .rail {
    position: sticky;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--gutter);
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  /* One line, not two — a sticky bar taller than it needs to be costs the
     same screen space on every scroll. The name is what has to survive. */
  .brand { display: flex; align-items: center; min-height: 44px; padding: 0; margin-right: auto; }
  .brand .cap { display: none; }
  .rail-nav { display: none; }
  /* The CV lives inside the menu at this width, so the bar can fit 320px. */
  .rail-foot { display: none; }
  .mobile-nav .lang-switch { padding: 0; margin-top: var(--space-2); }
  .statusbar { position: static; }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
    padding: 0 var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--fg);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    cursor: pointer;
    transition: border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
  }
  .nav-toggle:hover { border-color: var(--accent); color: var(--accent); }
  .nav-toggle .nav-icon--close { display: none; }
  .nav-toggle[aria-expanded='true'] { border-color: var(--accent); color: var(--accent); }
  .nav-toggle[aria-expanded='true'] .nav-icon--open { display: none; }
  .nav-toggle[aria-expanded='true'] .nav-icon--close { display: block; }

  .mobile-nav {
    display: block;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 60;
    padding: var(--space-2);
    background: var(--rail);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 16px 32px -16px rgba(0, 0, 0, 0.9);
  }
  .mobile-nav[hidden] { display: none; }
  .mobile-nav ul { display: flex; flex-direction: column; }
  .mobile-nav a {
    display: flex;
    align-items: center;
    min-height: 48px;
    padding: 0 var(--space-3);
    border-radius: var(--radius);
    font-size: var(--fs-body);
    color: var(--fg-muted);
  }
  .mobile-nav a:hover { color: var(--fg); background: var(--panel); }
  .mobile-nav a[aria-current='true'] {
    color: var(--accent);
    background: rgba(75, 174, 237, 0.1);
    font-weight: 600;
  }
  .mobile-nav-cv { width: 100%; margin-top: var(--space-2); }
}

@media (max-width: 720px) {
  :root { --view-y: 1.5rem; }

  /* Keep the status line on one row; the dated note moves below it. */
  .statusbar { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
  .statusbar-main { font-size: var(--fs-sm); white-space: nowrap; }
  .statusbar-note { margin-left: 0; display: block; }

  /* Hero actions: full-width primary, the rest share a row rather than
     wrapping one-per-line at 320px. */
  .actions { gap: var(--space-2); }
  .actions .btn { flex: 1 1 auto; justify-content: center; }
  .actions .btn-primary { flex-basis: 100%; }

  .case-purpose { margin-bottom: var(--space-3); font-size: var(--fs-sm); }
  .case-actions { margin-top: var(--space-4); gap: var(--space-2); }
  .case-action { font-size: var(--fs-cap); }

  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); }
  .case { grid-template-columns: 1fr; gap: var(--space-4); }
  .panel-body { padding: var(--space-4); }
  .case-facts { gap: var(--space-3); }
  .case-facts dd, .incident-facts dd, .row p { line-height: 1.55; }
  .tags { margin-top: var(--space-4); }
  .case-links { margin-top: var(--space-3); gap: var(--space-1) var(--space-4); }

  .arch-flow { grid-template-columns: 1fr; gap: var(--space-4); }
  .arch-node { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 0 var(--space-2); }
  .arch-desc::before { content: '· '; }
  .arch-node + .arch-node::before {
    left: 50%;
    top: calc(var(--space-4) / -2 - 3px);
    transform: translateX(-50%) rotate(135deg);
  }

  .row { grid-template-columns: 1fr; padding: var(--space-4); }
  .incident-lead { grid-template-columns: 1fr; gap: var(--space-3); padding: var(--space-4); }
  .feed-item summary { grid-template-columns: auto minmax(0, 1fr) auto; padding: var(--space-3) var(--space-4); }
  .feed-item summary .status-ok { display: none; }
  .feed-item .incident-facts { padding: 0 var(--space-4) var(--space-4); }

  .media-slot-inner { min-height: 128px; padding: var(--space-4); }
  .media-slot figcaption { display: none; }
  .timeline { padding: var(--space-4); gap: var(--space-2); }

  .contact-panel { padding: var(--space-6) var(--space-4); }
  .statusbar-note { display: none; }

  /* On phones the control is always there, open or closed. */
  .disclose > summary { display: flex; }
  .disclose[open] > *:not(summary) { padding-top: var(--space-2); }
  .case-facts--result { margin-top: var(--space-3); }
}

/* ----------------------------------------------------- reduced motion --- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal-ready] .reveal { opacity: 1; transform: none; }
  [data-reveal-ready] .tile-bar i { transform: scaleX(1); }
  .rg-edge { animation: none !important; }
}

/* --------------------------------------------------------------- print -- */
@media print {
  .rail, .statusbar, .media-slot, .contact-panel { display: none; }
  body { background: #fff; color: #000; }
  .shell { grid-template-columns: 1fr; }
}
