/* The left rail, on every page except the minimal home, which has its own bar, and
   the right ruler on the case studies and tool pages (DESIGN-SYSTEM.md §1b). A page opts
   in with body.has-rail, and body.case-page for the ruler.

   The page is four equal columns and the rail takes the first. Sections keep their
   own --edge padding on both sides, so the body starts one --edge short of the
   column and a section's text lands exactly on it. Full-bleed bands need no special
   case: they run under the rail and stop at the window. */
body.has-rail {
  /* --edge without the page-max cap: these pages run the full width of the screen.
     The others are set from it at :root, so they have to be set again here. */
  --edge: clamp(24px, 3.8vw, 72px);
  --page-max: 100%;   /* nothing centres itself in the column */
  --case-edge: var(--edge);
  --site-pad: var(--edge);
  --pad-x: var(--edge);
  --gap: var(--space-5);
  --col: calc((100% - 2 * var(--edge) - 3 * var(--gap)) / 4);
  --rail: calc(var(--edge) + var(--col) + var(--gap));
  /* A card has to read as a card on either theme. */
  --bt-surface: color-mix(in srgb, var(--ink) 9%, var(--paper));
  /* The hairline that holds an edge, even a black picture on a black page. */
  --bt-edge: color-mix(in srgb, var(--ink) 14%, transparent);
  /* Ask AI and Contact are one size of control. */
  --ctl-w: var(--space-9);
  --ctl-h: calc(var(--space-6) + var(--space-1));
  /* Their outline, which the theme toggle wears whole: line, knob and icon. */
  --ctl-line: color-mix(in srgb, var(--ink) 20%, transparent);
  /* Archive, the quietest of the rail's links. */
  --ink-quiet: color-mix(in srgb, var(--ink) 28%, transparent);
  padding-left: calc(var(--rail) - var(--edge));
}
/* No bar along the bottom. Archive is in the rail's links and the theme toggle sits beside
   Contact. */
body.has-rail { padding-bottom: 0; }
body.has-rail .shared-contact#contact { padding-bottom: var(--space-7) !important; }
/* Anubhuti and Clario carry their own 1600px box, written out in their sheets. */
body.has-rail.asset-clearance-case { --cl-edge: var(--edge); }
body.has-rail.portfolio-case { --an-pad: var(--edge); --gutter: var(--edge); }
body.has-rail :is(.chapter-inner, .portfolio-case .foundation-index) { max-width: none; margin-inline: 0; }
/* Anubhuti's dark chapters were full-width bands. In the column they stopped short with
   hard edges and read as a stray panel, so they sit on the page like every other section. */
body.has-rail .chapter.night { background: none; color: var(--ink); }
body.has-rail .night .chapter-inner > .chapter-copy { color: var(--ink-70); }

@media (min-width: 861px) {
  /* No bar across the top, so the page starts level with the name. A little higher
     than the rail's own top: a big title's capitals sit lower in their line, and
     it's the tops of the letters that should line up. */
  body.has-rail { padding-top: 0; --page-top: calc(var(--space-7) - var(--space-1)); }
  /* Solid: site-system.css gives the top bar an 82% background with !important, and at the
     side of the page anything scrolling underneath ghosted through it. */
  body.has-rail header.topbar.rail { background: var(--paper) !important; }
  /* The ruler shows how far through you are. */
  body.case-page .cs-progress { display: none; }
}

/* About's headline is four authored lines (user, 2026-11-08) at the page-title size. They
   hold wherever the copy column fits the longest, "A product designer building" (about
   12.8em, 716px at 56px); a narrower column wraps the sentence evenly instead. From 1700px
   the column is given that width, and the photos take the rest. */
@media (min-width: 861px) {
  .has-rail .about-statement { text-wrap: balance; }
  .has-rail .about-statement .stmt-line { display: inline; white-space: normal; }
  .has-rail .about-open { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: var(--space-7); }
}
@media (min-width: 1700px) {
  .has-rail .about-open { grid-template-columns: minmax(46rem, 1.4fr) minmax(0, 1fr); }
}
/* The greeting is always a line of its own, phones too; the sentence after it shares the rest. */
.has-rail .about-statement { text-wrap: balance; }
.has-rail .about-statement .stmt-line:first-child { display: block; text-wrap: wrap; }
@container (min-width: 46rem) {
  .has-rail .about-statement .stmt-line { display: block; white-space: nowrap; }
}
/* The photos end where the copy ends, at every width: the bento stretches to the copy
   column's height and its two photo rows share what the player row leaves. No tile keeps
   its own ratio here, or it would set the height again. Corners are the home bento's. */
@media (min-width: 981px) {
  body.has-rail .about-open { align-items: stretch !important; }
  .has-rail .about-bento { grid-template-rows: minmax(0, 1.2fr) minmax(0, 1fr) auto; }
  .has-rail .about-bento .bento { aspect-ratio: auto; }
}
.has-rail .about-bento .bento { border-radius: var(--radius-lg); }

/* A hairline between sections, to the page's edges: on the home, under the hero (where the
   brands strip used to run) and above About. */
.home-rule { height: 1px; margin: 0 var(--edge); border: 0; background: var(--rule); }
/* The brands strip lives on the Archive only, after the arc, and keeps to the page's edges. */
.has-rail .marquee { margin: var(--space-6) var(--edge) 0; }
@media (max-width: 860px) { .has-rail .marquee { margin-inline: 0; } }

/* What used to sit in a case study's bar: the links out, under the story. */
.cs-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-5); }

/* ---- The rail: who I am, the links, then Ask AI and Contact side by side.
   The long selectors are on purpose: site-system.css styles header.topbar with
   !important, and this has to win only on a page that opts in. */
body.has-rail header.topbar.rail {
  right: auto;
  bottom: auto;
  display: grid;
  /* Ask AI and Contact each take their own width (2026-11-10), then the rest. */
  grid-template-columns: auto auto minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto auto;
  gap: var(--space-6) var(--space-1);
  /* The rail's paper stops where the gap to the content begins, not at the content's edge:
     the gap stays clear, so a hover that reaches past its element (the buttons' selection
     box, 8px out) shows whole instead of slipping under the rail. The rail's own contents
     keep the same width. */
  width: calc(var(--rail) - var(--gap));
  height: 100svh !important;
  padding: var(--space-7) 0 var(--space-7) var(--edge) !important;
  background: var(--paper);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.rail-id { grid-column: 1 / -1; display: grid; }
.rail-id .brand { margin-bottom: var(--space-2); }
body.has-rail header.topbar.rail .nav { grid-column: 1 / -1; grid-row: 2; }
body.has-rail header.topbar.rail .ts-ask-launcher { grid-column: 1; grid-row: 4; align-self: end; }
body.has-rail header.topbar.rail .rail-contact { grid-column: 2; grid-row: 4; align-self: end; }

body.has-rail header.topbar.rail .brand,
body.has-rail header.topbar.rail .brand strong {
  font: 600 var(--type-card-title)/1.25 var(--face-sans, sans-serif) !important;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
}
/* Some case-study sheets dress the name up; in the rail it's plain on every page. */
body.has-rail header.topbar.rail .brand::before,
body.has-rail header.topbar.rail .brand::after,
body.has-rail header.topbar.rail .brand strong::before { content: none; display: none; }
body.has-rail header.topbar.rail .brand .ts-mark { display: none !important; }
body.has-rail header.topbar.rail .rail-line {
  margin: 0;
  font: 400 var(--type-lead)/1.55 var(--face-sans, sans-serif);
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-40);
}
/* Where I am, as a pin. It comes from one mask, so it's the line's own colour in either theme. */
body.has-rail header.topbar.rail .rail-place { display: flex; align-items: center; gap: var(--space-1); font-size: var(--type-small); }
.rail-place::before {
  content: '';
  flex: none;
  width: .72em;
  height: 1em;
  background: currentColor;
  --pin: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='3 1 10 14'%3E%3Cpath fill-rule='evenodd' d='M8 1a5 5 0 0 0-5 5c0 4.1 5 9 5 9s5-4.900 5-9a5 5 0 0 0-5-5Zm0 6.800a1.800 1.800 0 1 1 0-3.600 1.800 1.800 0 0 1 0 3.600Z'/%3E%3C/svg%3E");
  -webkit-mask: var(--pin) center / contain no-repeat;
  mask: var(--pin) center / contain no-repeat;
}
body.has-rail header.topbar.rail .nav {
  display: grid;
  gap: var(--space-4);
  justify-items: start;
  margin: 0 !important;
}
body.has-rail header.topbar.rail .nav > a {
  padding: 0;
  font: 600 var(--type-body)/1.3 var(--face-sans, sans-serif) !important;
  letter-spacing: 0;
  text-transform: none;
  text-decoration: none;
  color: var(--ink);
}
body.has-rail header.topbar.rail .nav > a::after,
body.has-rail header.topbar.rail .nav > a::before { content: none; }
body.has-rail header.topbar.rail .nav > a[aria-current] { color: var(--accent-text) !important; }
body.has-rail header.topbar.rail .nav > a:hover { color: var(--accent-text); }
/* Craft (craft.html, renamed from Archive and moved under Work, 2026-11-09) is a
   full link now, set like the others. */

/* The theme toggle, after the user's own button: a 1px outline pill holding a hollow knob and
   the current mode's icon. Night puts the knob left and the moon right; day puts the sun
   left and the knob right. The knob slides across and the new icon draws itself in. script.js
   drives it like any .theme-switch. */
body.has-rail header.topbar.rail .rail-theme {
  --rt-h: var(--ctl-h);
  /* Narrower since 2026-11-09, so all four controls can share one line. */
  --rt-w: calc(var(--rt-h) * 1.6);
  --rt-s: calc(var(--rt-h) * .56);
  --rt-in: calc((var(--rt-h) - var(--rt-s)) / 2);
  position: relative;
  /* Under Contact since 2026-11-09, as wide as it, with Reduce motion beside it
     under Ask AI: two rows of two, every control one size. */
  grid-column: 2;
  grid-row: 5;
  align-self: start;
  justify-self: start;
  margin-top: calc(var(--space-2) - var(--space-6)) !important;
  display: block;
  flex: none;
  box-sizing: border-box;
  width: var(--rt-w);
  height: var(--rt-h);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  /* Dressed like Contact: its outline for the line, its word's colour for the knob and icon.
     No hover state. */
  color: var(--ink);
  font: inherit;
  box-shadow: inset 0 0 0 1px var(--ctl-line);
  cursor: pointer;
  /* The motion starts the moment it's pressed: the knob leaves at speed and lands softly, the
     new icon draws in as soon as the knob is off its spot, the old one is gone before the knob
     arrives. */
  --rt-move: 600ms var(--ease);
  --rt-draw: stroke-dashoffset 640ms var(--ease) 80ms, opacity 240ms ease 80ms;
  --rt-hide: stroke-dashoffset 200ms ease, opacity 160ms ease;
}
body.has-rail header.topbar.rail .rail-theme:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 3px; }
.rail-theme .rt-knob, .rail-theme .rt-slot { position: absolute; top: 50%; translate: 0 -50%; width: var(--rt-s); height: var(--rt-s); }
/* The knob glides on translate, which the compositor runs. Night: left. Day: across by the
   pill's width less a knob and two insets. */
.rail-theme .rt-knob { left: var(--rt-in); translate: calc(var(--rt-w) - var(--rt-h)) -50%; border-radius: 50%; box-shadow: inset 0 0 0 1px currentColor; transition: translate var(--rt-move); }
:root.theme-dark .rail-theme .rt-knob { translate: 0 -50%; }
.rail-theme .rt-sun { left: var(--rt-in); }
.rail-theme .rt-moon { right: var(--rt-in); }
.rail-theme svg { display: block; width: 100%; height: 100%; overflow: visible; fill: none; stroke: currentColor; stroke-width: 1.15; stroke-linecap: round; stroke-linejoin: round; }
.rail-theme svg :is(path, circle) { stroke-dasharray: 1; stroke-dashoffset: 0; transition: var(--rt-draw); }
:root.theme-dark .rail-theme .rt-sun :is(path, circle),
:root:not(.theme-dark) .rail-theme .rt-moon path { stroke-dashoffset: 1; opacity: 0; transition: var(--rt-hide); }
/* A flip is instant: styles.css (html.theme-shifting) turns every transition off while the new
   colours land. Only the toggle's own motion is put back. */
html.theme-shifting body.has-rail .rail-theme .rt-knob { transition: translate var(--rt-move) !important; }
html.theme-shifting body.has-rail .rail-theme svg :is(path, circle) { transition: var(--rt-draw) !important; }
html.theme-shifting.theme-dark body.has-rail .rail-theme .rt-sun :is(path, circle),
html.theme-shifting:not(.theme-dark) body.has-rail .rail-theme .rt-moon path { transition: var(--rt-hide) !important; }
@media (prefers-reduced-motion: reduce) {
  html body.has-rail header.topbar.rail .rail-theme :is(.rt-knob, path, circle), body.has-rail header.topbar.rail .rail-theme { transition: none !important; }
}
/* Reduce motion (script.js builds it, rethought 2026-11-09): a round icon button the
   height of the others, outlined like them, showing pause while things move and play
   once they are still. */
body.has-rail header.topbar.rail .rail-motion {
  grid-column: 1; grid-row: 5; align-self: start; justify-self: start;
  margin-top: calc(var(--space-2) - var(--space-6));
  display: grid; place-items: center;
  box-sizing: border-box; width: var(--ctl-h); height: var(--ctl-h);
  margin-inline: 0; padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--ink); box-shadow: inset 0 0 0 1px var(--ctl-line); cursor: pointer;
}
body.has-rail header.topbar.rail .rail-motion:hover { color: var(--accent-text); }
body.has-rail header.topbar.rail .rail-motion:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 3px; }
.rail-motion svg { grid-area: 1 / 1; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.rail-motion .rm-play { display: none; }
:root.motion-off .rail-motion .rm-pause { display: none; }
:root.motion-off .rail-motion .rm-play { display: block; }
/* One line, two groups (user, 2026-11-10): Ask AI and Contact, a clear gap, then
   Reduce motion and Day/Night, all on the rail's last row and centred on one line.
   The second pair sits in the third column, so it follows Contact at a fixed
   distance. Below 1360px the rail is too narrow for four, so the second pair drops
   to its own row under the first, a clear gap above it. */
@media (min-width: 861px) and (max-width: 1359px) {
  body.has-rail header.topbar.rail .rail-motion { grid-column: 1 / span 2; margin-top: calc(var(--space-4) - var(--space-6)); }
  body.has-rail header.topbar.rail .rail-theme:not(.rail-motion) { grid-column: 1 / span 2; justify-self: start; margin-left: calc(var(--ctl-h) + var(--space-1)) !important; margin-top: calc(var(--space-4) - var(--space-6)) !important; }
}
@media (min-width: 1360px) {
  body.has-rail header.topbar.rail { grid-template-rows: auto auto 1fr auto; }
  body.has-rail header.topbar.rail .rail-motion { grid-column: 3; grid-row: 4; align-self: center; justify-self: start; margin: 0 0 0 var(--space-2); }
  body.has-rail header.topbar.rail .rail-theme:not(.rail-motion) { grid-column: 3; grid-row: 4; align-self: center; justify-self: start; margin: 0 0 0 calc(var(--space-2) + var(--ctl-h) + var(--space-1)) !important; }
  body.has-rail header.topbar.rail :is(.ts-ask-launcher, .rail-contact) { align-self: center; }
}

/* Ask AI and Contact: one size, side by side at the foot of the rail. */
body.has-rail header.topbar.rail .ts-ask-launcher,
body.has-rail header.topbar.rail .rail-contact {
  position: static;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  box-sizing: border-box;
  /* Sized by their words with the same padding (2026-11-10): Contact was stretched
     to Ask AI's width. */
  width: auto;
  min-width: 0;
  height: var(--ctl-h);
  min-height: 0;
  margin: 0;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  box-shadow: inset 0 0 0 1px var(--ctl-line);
  text-decoration: none;
  color: var(--ink);
}
body.has-rail header.topbar.rail .ts-ask-launcher,
body.has-rail header.topbar.rail .ts-ask-launcher-label,
body.has-rail header.topbar.rail .rail-contact {
  font: 600 var(--type-small)/1 var(--face-sans, sans-serif) !important;
  letter-spacing: 0;
  text-transform: none;
}
body.has-rail header.topbar.rail .ts-ask-launcher::before,
body.has-rail header.topbar.rail .ts-ask-launcher::after { content: none; }
body.has-rail header.topbar.rail :is(.ts-ask-launcher, .rail-contact):hover { color: var(--accent-text); }
body.has-rail header.topbar.rail .ts-ask-face { width: var(--space-5); height: var(--space-5); }

/* ---- The ruler (scrubber.js): where you are in the page, in the right margin.
   Ticks are built by the script; it sets each one's --reach and opacity. */
.scrub {
  --scrub-step: min(var(--space-3), calc((100svh - 2 * var(--bar-h) - var(--space-9)) / 33));
  position: fixed;
  z-index: 300;
  right: 0;
  top: 50%;
  translate: 0 -50%;
  box-sizing: border-box;
  width: var(--edge);
  padding-block: var(--space-5);
  cursor: ns-resize;
  touch-action: none;
  user-select: none;
  outline: 0;
}
.scrub[hidden] { display: none; }
.scrub-ticks { position: relative; display: grid; justify-items: end; row-gap: calc(var(--scrub-step) - 1px); padding-right: var(--space-3); }
.scrub-ticks i { display: block; width: var(--reach, var(--space-2)); height: 1px; background: var(--ink); opacity: .18; transition: width var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease); }
.scrub:focus-visible .scrub-ticks { outline: 2px solid var(--accent-text); outline-offset: var(--space-1); border-radius: var(--radius-sm); }
/* The word beside the swell. It sits over the content, so it only shows while the
   page is moving or the ruler is in use, and it wears the page colour to stay legible. */
.scrub-label {
  position: absolute;
  right: calc(var(--space-3) + var(--scrub-peak, var(--space-7)) + var(--space-2));
  translate: 0 -50%;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-tag);
  background: var(--paper);
  box-shadow: 0 0 0 1px var(--bt-edge);
  font: 400 var(--type-small)/1.2 var(--face-sans, sans-serif);
  white-space: nowrap;
  color: var(--ink-70);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.scrub.is-awake .scrub-label { opacity: 1; }
@media (max-width: 860px), (hover: none) { .scrub { display: none; } }
@media (prefers-reduced-motion: reduce) { .scrub-ticks i, .scrub-label { transition: none; } }
@media print { .scrub { display: none; } }

/* ---- Case studies and tool pages: four columns. The rail, two for the story, and one for the
   sections, so the story has room on both sides. The names can stay on screen because
   nothing else lives in that column. Under 1280px there's no room for a column to sit
   empty, so the page takes three and the ruler keeps its floating word. */
.scrub-names { display: none; }
@media (min-width: 1280px) {
  /* The ruler lives in the left rail (scrubber.js), so these pages keep the landing
     page's exact layout: the same rail, the same content width, nothing reserved on
     the right (user, 2026-11-09). Only ?ruler=right, the old layout kept for
     comparison, reserves a right-hand column: as wide as the widest name
     (scrubber.js), with the rail cut to match and a tighter edge. */
  body.has-rail.ruler-right:is(.case-page, .has-ruler) {
    --edge: clamp(24px, 3.4vw, 64px);
    --ruler-w: calc(var(--ctl-w) * 2 + var(--space-1));
    --rail: calc(var(--edge) * 2 + var(--ruler-w));
    padding-right: calc(var(--scrub-names-w, var(--space-9)) + var(--space-6) + var(--space-3) + var(--edge));
  }
  body.ruler-right:is(.case-page, .has-ruler) .scrub { width: calc(var(--space-8) + var(--edge)); }
  body:is(.case-page, .has-ruler) .scrub-ticks { padding-right: var(--edge); }
  body:is(.case-page, .has-ruler) .scrub-label { display: none; }

  /* Old right-hand ruler only (?ruler=right): it squeezed the story into two
     columns, so what sat side by side stacked and four across became two. With the
     ruler in the left rail the pages keep their full-width layout. */
  /* The top of a case study: a small picture beside the headline and the explanation,
     then the numbers and details across both columns. */
  body.case-page.ruler-right .cs-top { grid-template-columns: minmax(0, 1fr) minmax(0, 34%); gap: 0 var(--space-6); }
  body.case-page.ruler-right .cs-top > div { display: contents; }
  body.case-page.ruler-right .cs-top > div > * { grid-column: 1; }
  body.case-page.ruler-right .cs-top > div > :is(.cs-metrics, .cs-meta, .cs-actions) { grid-column: 1 / -1; }
  body.case-page.ruler-right .cs-top > .cs-top-media { position: static; grid-column: 2; grid-row: 1 / span 3; align-self: start; margin: 0; }
  body.case-page.ruler-right .cs-top-media img { border-radius: var(--radius-md); box-shadow: 0 0 0 1px var(--bt-edge); }
  body.case-page.ruler-right :is(.section-tight, .chapter-inner):has(h2) { grid-template-columns: minmax(0, 1fr); }
  body.case-page.ruler-right :is(.section-tight, .chapter-inner):has(h2) > * { grid-column: 1 / -1 !important; }
  body.case-page.ruler-right .chapter-copy.is-pair { grid-template-columns: minmax(0, 1fr); }
  body.case-page.ruler-right :is(.clario-catch-list, .clario-verdicts).fl-four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  body.case-page.ruler-right .credits { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  body.case-page.ruler-right .shared-contact#contact .contact-one-line { grid-column: 1 / -1 !important; }
  body.case-page.ruler-right .shared-contact#contact .contact-hero-aside { grid-column: 1 / -1 !important; grid-row: 2 !important; justify-content: flex-start; margin: var(--space-5) 0 0 !important; }
  body:is(.case-page, .has-ruler) .scrub-names {
    position: fixed;
    z-index: 301;
    display: block;
    top: 50%;
    right: calc(var(--edge) + var(--scrub-peak, var(--space-7)) + var(--space-3));
    translate: 0 -50%;
    height: var(--scrub-h, 0);
    pointer-events: none;
  }
}
/* Anubhuti's boards were drawn for the full page. Two columns on a laptop is narrow
   for them; on a wide screen they have the room and stay as they are. */
@media (min-width: 1280px) and (max-width: 1800px) {
  body.case-page.ruler-right .an-scale-row { grid-template-columns: minmax(0, 1fr) 13ch; }
  body.case-page.ruler-right .an-scale-row > :nth-child(2),
  body.case-page.ruler-right .an-scale-row > :nth-child(4) { display: none; }
  body.case-page.ruler-right .an-scale-sample { overflow-wrap: normal; }
  body.case-page.ruler-right .an-faces { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-5); }
  body.case-page.ruler-right .an-face { min-width: 0; }
  body.case-page.ruler-right .an-face-set { overflow-wrap: anywhere; }
  body.case-page.ruler-right .an-pipe { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  body.case-page.ruler-right .an-trace { gap: var(--space-4); }
}
@media (min-width: 1280px) and (max-width: 1400px) {
  body.case-page.ruler-right .an-icons { grid-template-columns: minmax(0, 1fr); }
  body.case-page.ruler-right .an-pipe { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Current is orange, the way the rail marks the page you're on. A hover lifts the name
   and reaches its tick out to it, so the word and the mark read as one thing. */
.scrub-names a {
  position: absolute;
  right: 0;
  translate: 0 -50%;
  padding: var(--space-1) 0 var(--space-1) var(--space-3);
  font: 400 var(--type-small)/1.2 var(--face-sans, sans-serif);
  white-space: nowrap;
  text-decoration: none;
  color: var(--ink-40);
  pointer-events: auto;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), translate var(--dur-fast) var(--ease);
}
.scrub-names a.is-near, .scrub-names a:hover { color: var(--ink); translate: calc(var(--space-1) * -1) -50%; }
.scrub-names a.is-current { color: var(--accent-text); font-weight: 600; }
.scrub-names a:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; border-radius: var(--radius-sm); }
@media (prefers-reduced-motion: reduce) { .scrub-names a { transition: none; } }
@media print { .scrub-names { display: none !important; } }

/* ---- Phones and small tablets: the rail folds into a bar across the top. */
@media (max-width: 860px) {
  body.has-rail { padding-left: 0; }
  body.has-rail header.topbar.rail { right: 0; display: flex; width: auto; height: var(--bar-h) !important; align-items: center; padding: 0 var(--pad-x) !important; gap: var(--space-3); }
  body.has-rail header.topbar.rail .rail-line { display: none; }
  body.has-rail header.topbar.rail .brand, body.has-rail header.topbar.rail .brand strong { font-size: var(--type-small) !important; }
  body.has-rail header.topbar.rail .nav { display: flex; gap: var(--space-3); }
  body.has-rail header.topbar.rail .nav > a { font-size: var(--type-small) !important; }
  body.has-rail header.topbar.rail .rail-contact { width: auto; height: auto; margin-left: auto; align-self: center; border-radius: 0; box-shadow: none; }
  body.has-rail header.topbar.rail .ts-ask-launcher { width: auto; height: auto; align-self: center; border-radius: 0; box-shadow: none; }
  body.has-rail header.topbar.rail .rail-theme { --rt-h: calc(var(--space-6) - var(--space-1)); width: calc(var(--rt-h) * 1.9); margin-top: 0 !important; align-self: center; }
  body.has-rail header.topbar.rail .rail-theme .rt-knob { translate: calc(var(--rt-h) * .9) -50%; }
  :root.theme-dark body.has-rail header.topbar.rail .rail-theme .rt-knob { translate: 0 -50%; }
  body.has-rail header.topbar.rail .rail-motion { display: none; }
  /* Craft stays in the top bar on phones (2026-11-09): it's the way to the campaign
     work. On the narrowest phones the bar can't hold it and Contact both, so Contact
     steps out there; every page ends in the full contact section anyway. */
}

/* ---- The Archive with the ruler (2026-11-09) ---------------------------------
   Where the ruler's names show (a wide screen with a pointer), they are the
   section headings: the gallery titles leave the page, a closed gallery takes no
   room, and the ruler opens one at a time (scrubber.js). Anywhere the ruler
   doesn't show, the headings and their accordions stay on the page as before. */
@media (min-width: 1280px) and (hover: hover) {
  body.has-ruler .craft-accordion > summary { display: none; }
  body.has-ruler .craft-accordion { border-bottom: 0; }
  body.has-ruler .craft-accordion:not([open]) { display: block; height: 0; overflow: hidden; }
  body.has-ruler .craft-accordion[open] > .craft-accordion-content { padding-top: var(--space-6); }
  /* The ruler's column takes a quarter of the width, so the galleries hold fewer,
     larger tiles: three across, two for the wide banners. */
  body.has-ruler .sheet[data-cols] { --cols: 3; }
  body.has-ruler .sheet--banners[data-cols] { --cols: 2; }
}
@media (min-width: 2000px) and (hover: hover) {
  body.has-ruler .sheet[data-cols] { --cols: 4; }
  body.has-ruler .sheet--banners[data-cols] { --cols: 3; }
}

/* ---- The Archive's ruler is narrow (2026-11-09) ------------------------------
   The names are short, so the ruler keeps a fixed 192px instead of a quarter of the
   page. The brand strip runs under it at full width, so the ruler fades out while
   the opening is on screen (scrubber.js, .is-away). */
.scrub, .scrub-names { transition: opacity var(--dur) var(--ease); }
:is(.scrub, .scrub-names).is-away { opacity: 0; pointer-events: none; }
:is(.scrub, .scrub-names).is-away a { pointer-events: none !important; }
@media (min-width: 1280px) {
  /* The arc keeps to the content's width like the sections below, holding seven
     larger cards instead of nine
     (script.js), so its cards stay the same size. Seven bigger cards need a
     slightly deeper band. */
  body.has-ruler .cascade { aspect-ratio: 1 / 0.38; margin-inline: var(--case-edge); }
}
/* With the ruler in the left rail the arc runs the content's full width, a
   the same nine cards drawn larger (script.js), so the band is deeper:
   0.896 x the half-span of 0.5 (2026-11-10). */
@media (min-width: 760px) {
  body.has-ruler.ruler-left .cascade { aspect-ratio: 1 / 0.45; }
}

/* The brand strip spans exactly the hairline under it (the section dividers sit
   at the content's edges), at every width (user, 2026-11-09). */
.archive-page main#main > .marquee { margin-inline: var(--case-edge); }

/* ---- The ruler in the left rail (the default since 2026-11-09, scrubber.js) ----
   Under the links, in the rail's free row. The ticks grow to the right from the
   rail's edge, and a single label beside the swell names the section you're in,
   or the one under the pointer. The list of names and the right-hand column go,
   so the content keeps one --edge clear of the window on the right as well. */
/* Every section's name, beside its mark, while the pointer is on the ruler: the
   current one at full strength, the others dimmed. At rest only the one label shows. */
body.has-rail.ruler-left .scrub .scrub-names {
  display: block !important;
  position: absolute;
  z-index: 1;
  top: 0;
  right: auto;
  left: calc(var(--scrub-peak, var(--space-6)) + var(--space-3));
  height: var(--scrub-h, 0);
  translate: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
body.ruler-left .scrub:is(:hover, :focus-visible) .scrub-names { opacity: 1; }
body.ruler-left .scrub:is(:hover, :focus-visible) .scrub-label { opacity: 0 !important; }
body.ruler-left .scrub .scrub-names a { left: 0; right: auto; padding: var(--space-1) 0; color: var(--ink); opacity: .4; pointer-events: none; translate: 0 -50%; }
body.ruler-left .scrub .scrub-names a.is-near { opacity: 1; translate: var(--space-1) -50%; }
body.ruler-left .scrub .scrub-names a.is-current { opacity: 1; color: var(--accent-text); }
body.has-rail.ruler-left header.topbar.rail .scrub {
  position: relative;
  z-index: auto;
  grid-column: 1 / -1;
  grid-row: 3;
  /* Centred in the rail's free space, between About and Ask AI (user, 2026-11-09). */
  align-self: center;
  right: auto;
  top: auto;
  translate: none;
  width: 100%;
  margin-top: 0;
  padding-block: 0;
}
body.ruler-left .scrub-ticks { justify-items: start; padding-right: 0; }
/* Sentence case, like the rail's links: the top bar's label styling stops here. */
body.ruler-left .scrub :is(.scrub-label, .scrub-names a) { text-transform: none; letter-spacing: 0; font-family: var(--face-sans, sans-serif); }
body.ruler-left .scrub-label {
  right: auto;
  left: calc(var(--scrub-peak, var(--space-6)) + var(--space-3));
  padding: 0;
  background: none;
  box-shadow: none;
  color: var(--ink);
  font-weight: 600;
}
@media (min-width: 1280px) {
  body.ruler-left:is(.case-page, .has-ruler) .scrub-label { display: block; }
}

@media (max-width: 420px) {
  body.has-rail header.topbar.rail .rail-contact { display: none; }
}

/* ---- Case-study tops with the rail (audit, 2026-11-09) -------------------------
   The picture beside the headline is sticky, and its offset was written for the old
   top bar and case bar (about 144px). With the rail there is no bar above it, so it
   started that far down and never lined up. It now sticks just under the window's
   top, and starts level with the headline, below the back link. */
@media (min-width: 901px) {
  body.has-rail .cs-top > .cs-top-media { top: var(--space-5); }
  body.has-rail .cs-top:has(.cs-back) > .cs-top-media { margin-top: calc(var(--type-label) * 1.4 + var(--space-5)); }
}

/* ---- Phones and touch screens (user, 2026-11-10) ---------------------------------
   The top bar: the rail's name block is empty here, but as a flex item it still
   took a gap, so the links started 12px in from the page's edge. */
@media (max-width: 860px) {
  body.has-rail header.topbar.rail .rail-id { display: none; }
  body.has-rail header.topbar.rail .nav { padding-left: 0; }
}
/* Craft wherever the ruler doesn't show: the page's own style, not the old
   accordion. Plain sentence-case headings between hairlines, no side bar, no
   letter-by-letter fill; each gallery a simple grid. */
@media not all and (min-width: 1280px) and (hover: hover) {
  body.has-ruler .craft-accordion { border-bottom: 1px solid var(--rule); }
  body.has-ruler .craft-accordion summary { padding: var(--space-5) 0; border-left: 0; }
  body.has-ruler .craft-accordion summary .sec-head { padding-right: var(--space-7); }
  body.has-ruler .craft-accordion .sec-title { font: 600 var(--type-lead)/1.3 var(--face-sans, sans-serif) !important; letter-spacing: 0; text-transform: none; color: var(--ink); }
  body.has-ruler .craft-accordion .craft-chevron { right: 0; }
  body.has-ruler .craft-accordion-content { padding: 0 0 var(--space-6); }
}
@media (max-width: 640px) {
  body.has-ruler .sheet--banners[data-cols] { --cols: 1; }
}
/* No hover on touch: each piece's name and one-liner sit under it. */
@media (hover: none) {
  body.has-ruler .sheet .tile.ct .ct-info { display: none; }
  body.has-ruler .sheet figcaption.craft-cap { display: flex; gap: 2px; padding-top: var(--space-2); }
  body.has-ruler .sheet figcaption.craft-cap .cc-meta { display: none; }
  body.has-ruler .sheet figcaption.craft-cap .cc-title { font-weight: 600; }
}

/* About with the ruler (user, 2026-11-10): where the ruler shows, its names are the
   section headings, so Experience, Tech stack and Education leave the page, as the
   gallery titles do on Craft. Phones keep them. */
@media (min-width: 1280px) and (hover: hover) {
  body.page-about.has-ruler section[data-scrub] > .sec-head { display: none; }
}

/* The left ruler stays hidden until the page has been scrolled (scrubber.js sets
   body.scrub-seen past the first 40px), then fades in (user, 2026-11-10). */
body.ruler-left .scrub { transition: opacity var(--dur) var(--ease); }
body.ruler-left:not(.scrub-seen) .scrub { opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { body.ruler-left .scrub { transition: none; } }
html.motion-off body.ruler-left .scrub { transition: none; }
/* A pointer over the rail brings it back, even at the top of the page (user, 2026-11-10). */
body.ruler-left:not(.scrub-seen) header.rail:hover .scrub { opacity: 1; pointer-events: auto; }

/* The next-section pill at the end of each Craft gallery (scrubber.js, 2026-11-10). */
.craft-next { display: flex; justify-content: center; padding-top: var(--space-7); }

/* Phones: the top bar is solid too (2026-11-10). At 82% the card titles scrolling
   under it showed through the links. */
@media (max-width: 860px) {
  body.has-rail header.topbar.rail { background: var(--paper) !important; }
}
/* Wherever the ruler doesn't show (phones, tablets), the accordion headings already
   say what comes next, so the next-section pills stay off (user, 2026-11-10). */
@media not all and (min-width: 1280px) and (hover: hover) { .craft-next { display: none; } }
