/* TheBrandSpur — only what Tailwind can't express. Tokens live in the tailwind
   config in index.html; this file holds the roll mechanics and the type scale. */

/* Nav height including its 2px top and 1px bottom borders — the hero
   subtracts this to fill the rest of the viewport, so it has to be exact
   or the hero overhangs by those few pixels. */
:root { --nav: 63px; }
@media (min-width: 1024px) { :root { --nav: 79px; } }

html { scroll-behavior: smooth; }
::selection { background: #ff751f; color: #0f0c40; }

:where(a, summary, button):focus-visible {
  outline: 2px solid #0f0c40;
  outline-offset: 3px;
}

.nav-link { position: relative; }
.nav-link::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: -4px;
  height: 2px; background: #ff751f; transition: right .25s ease;
}
.nav-link:hover::after { right: 0; }
/* the page you are already on keeps its rule drawn */
.nav-link.is-here { color: #ff751f; }
.nav-link.is-here::after { right: 0; }

/* ── team page ────────────────────────────────────────────────────────
   Own file, same system. The photo slot holds its aspect ratio whether or
   not a headshot has been dropped in yet, so the grid never reflows. */
.tm-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  /* auto-fit collapses empty tracks, so the cards can never make more columns
     than there are people — they sit in one row wherever they fit, then drop
     to the next line as the viewport narrows. */
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  /* Three people on a wide laptop would otherwise give ~560px headshots. Cap
     the grid and centre it so the cards stay portrait-sized. */
  max-width: 900px;
  margin-inline: auto;
  border-top: 1px solid rgba(15, 12, 64, .14);
  border-left: 1px solid rgba(15, 12, 64, .14);
}
/* Rules live on the cards, not as a gap over a coloured container: a part-full
   last row would otherwise show its empty cells as grey blocks. */
.tm-card {
  position: relative;
  background: #fffffd;
  padding: clamp(16px, 1.5vw, 26px);
  border-right: 1px solid rgba(15, 12, 64, .14);
  border-bottom: 1px solid rgba(15, 12, 64, .14);
}
/* Orange rule drawing across the top of the hovered card — the same accent the
   role label already uses, so the hover reads as part of the grid, not a chip
   floating over it. */
.tm-card::after {
  content: '';
  position: absolute; inset: -1px 0 auto 0; height: 2px;
  background: #ff751f;
  transform: scaleX(0); transform-origin: left;
  transition: transform .5s cubic-bezier(.22, .7, .2, 1);
}
.tm-card:hover::after, .tm-card:focus-within::after { transform: scaleX(1); }
.tm-photo {
  /* square, not portrait: at three columns a 4:5 slot ran ~460px tall */
  aspect-ratio: 1 / 1;
  margin-bottom: 20px;
  display: flex; align-items: center; justify-content: center;
  background: #faf8f3;
  border: 1px dashed rgba(15, 12, 64, .22);
  overflow: hidden;      /* the headshot pushes in on hover */
}
.tm-photo > span {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: rgba(15, 12, 64, .38);
}
/* Three headshots shot in three different rooms. Mono at rest makes them one
   set; colour arrives only on the card you are actually looking at. */
.tm-photo > img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(1) contrast(1.06);
  transform: scale(1.005);   /* hides the sub-pixel edge the scale-up rounds to */
  transition: filter .55s ease, transform .7s cubic-bezier(.22, .7, .2, 1);
}
.tm-card:hover .tm-photo > img,
.tm-card:focus-within .tm-photo > img {
  filter: grayscale(0) contrast(1);
  transform: scale(1.05);
}
.tm-role {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  color: #ff751f;
  margin: 0 0 10px;
  /* two lines reserved, so a long role title doesn't push its card's name
     out of line with the rest of the row */
  line-height: 1.45;
  min-height: 2.9em;
}
.tm-name {
  font-size: clamp(1.15rem, 1.5vw, 1.4rem);
  font-weight: 700;
  letter-spacing: -.025em;
  margin: 0 0 10px;
}
.tm-note {
  font-size: 14.5px;
  line-height: 1.55;
  color: rgba(15, 12, 64, .7);
  margin: 0;
}

/* ── services page ────────────────────────────────────────────────────
   A line-item schedule, not a card grid — the document form the subject
   already works in, and the same hairline system as the figures.

   Deliberately unnumbered. The landing page's six stages are a real
   sequence, so 01..06 carries meaning there; this is a menu you pick from,
   so the codes group by family (SC / PL / CM / CH) and say category rather
   than order. Numbering it too would also just read as that section again. */
.sv-fam { border-top: 1px solid #0f0c40; }
.sv-fam + .sv-fam { margin-top: clamp(44px, 4.5vw, 76px); }

.sv-fam-head {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px 18px;
  padding: 16px 0 20px;
}
.sv-fam-code {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px; letter-spacing: .16em;
  color: #ff751f;
}
.sv-fam-name {
  font-size: clamp(1.3rem, 2vw, 1.85rem);
  font-weight: 800; letter-spacing: -.03em;
  margin: 0;
}
.sv-fam-count {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(15, 12, 64, .42);
  margin-left: auto;
}

/* Rail and panel, one open service per family. Grouping the switch per family
   rather than running one rail for all fifteen is what keeps every name on
   screen: the rail is never taller than its own family, so nothing is hidden
   behind a scroll — only the descriptions swap. */
.sv-switch { position: relative; }
/* Every rule in the rail belongs to the item below it, and selecting an item
   lights that rule. A closing rule under the last item would be the only one
   that belongs to nothing and can never light, which read as a separator that
   had failed to activate — so the rail simply ends. */
.sv-rail { list-style: none; margin: 0; padding: 0; }

/* A real button, so it is focusable and operable from the keyboard with no
   JS of ours; aria-expanded is the single source of truth for the lit state,
   which is why the styling hangs off the attribute and not a class. */
.sv-item {
  appearance: none; background: none; border: 0; font: inherit; color: inherit;
  position: relative;
  width: 100%; text-align: left; cursor: pointer;
  display: grid; grid-template-columns: 64px 1fr; gap: 16px;
  align-items: baseline;
  padding: 15px 0;
  border-top: 1px solid rgba(15, 12, 64, .14);
}
/* Same drawn rule as .nav-link and the lit stage row — the row lights along
   the hairline it already owns rather than lifting off the page as a card. */
.sv-item::after {
  content: ""; position: absolute; left: 0; right: 100%; top: -1px;
  height: 1px; background: #ff751f; transition: right .3s ease;
}
.sv-item:hover::after,
.sv-item[aria-expanded="true"]::after { right: 0; }
/* the chosen service holds a heavier rule than a passing hover does */
.sv-item[aria-expanded="true"]::after { height: 2px; }
/* the chosen service is marked on its code only — dimming the other fourteen
   names would read as unavailable, which is the wrong signal on a menu */
.sv-item[aria-expanded="true"] .sv-code { color: #ff751f; }

.sv-code {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px; letter-spacing: .1em;
  color: rgba(15, 12, 64, .42);
  transition: color .25s ease;
}
.sv-name {
  font-size: clamp(1.06rem, 1.5vw, 1.35rem);
  font-weight: 700; letter-spacing: -.025em;
  margin: 0;
}

/* Unhidden in the markup: with no JS every panel reads in flow under its own
   service, so the page is a complete list before js/services.js narrows it. */
.sv-panel { padding: 2px 0 20px 80px; }
.sv-panel > p {
  font-size: 15px; line-height: 1.6;
  color: rgba(15, 12, 64, .7);
  margin: 0 0 14px;
  max-width: 46ch;
}
/* What the service actually covers, as ruled line items. This is what fills
   the panel column — a lone paragraph left most of it empty — and it is the
   same hairline device as the rail, so the two read as one document. */
.sv-inc {
  list-style: none; margin: 0 0 15px; padding: 0;
  border-top: 1px solid rgba(15, 12, 64, .14);
}
.sv-inc > li {
  padding: 8px 0;
  border-bottom: 1px solid rgba(15, 12, 64, .14);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px; letter-spacing: .05em; line-height: 1.4;
  color: rgba(15, 12, 64, .6);
}
.sv-eng {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(15, 12, 64, .45);
}

@media (min-width: 1024px) {
  /* The panel is lifted out of the rail into its own column. min-height is the
     panel's own height, so a two- or three-service family (CH, CM) whose rail
     is shorter than the panel can't let it spill past the block. */
  .sv-switch { min-height: 252px; }
  .sv-rail { width: 56%; }
  /* Centred rather than top-aligned: the seven-service rail is ~200px taller
     than the panel, and pinning the panel to the top left that gap hanging
     under it. Centring is also stable — the panel does not jump to meet
     whichever service you picked. */
  .sv-panel {
    position: absolute; top: 50%; left: 60%; right: 0;
    transform: translateY(-50%);
    padding: 0 0 0 clamp(20px, 2vw, 34px);
    border-left: 1px solid rgba(15, 12, 64, .14);
  }
  .sv-panel > p { font-size: 15.5px; }
}

/* ── clients page: the review wall ────────────────────────────────────
   Same hairline cell system as .tm-grid on the team page — rules on the
   cards, paper ground, no radius and no shadow — so a part-full last row
   never shows empty cells as grey blocks. */
.cw-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
  border-top: 1px solid rgba(15, 12, 64, .14);
  border-left: 1px solid rgba(15, 12, 64, .14);
}
.cw-card {
  background: #fffffd;
  padding: clamp(20px, 2vw, 30px);
  border-right: 1px solid rgba(15, 12, 64, .14);
  border-bottom: 1px solid rgba(15, 12, 64, .14);
  display: flex; flex-direction: column; gap: 16px;
}

/* Wordmark slot. Holds its height whether or not a logo has been dropped
   in, so filtering the wall never reflows the rows. */
.cw-mark {
  height: 34px; display: flex; align-items: center;
}
.cw-mark > span {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: rgba(15, 12, 64, .3);
  border: 1px dashed rgba(15, 12, 64, .22);
  padding: 5px 10px;
}
.cw-mark > img { max-height: 100%; width: auto; }

/* One star path, repeated across the strip as a mask. The element's own
   background paints the empty stars and the child paints the filled ones —
   a mask applies to a pseudo/child too, so both are cut to the star shape
   and a half star costs nothing but a width. --r drives it from the markup. */
.cw-stars {
  --r: 5;
  --star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.5l1.9 4.1 4.5.5-3.35 3 .9 4.4L8 11.3l-3.95 2.2.9-4.4L1.6 6.1l4.5-.5z'/%3E%3C/svg%3E");
  position: relative;
  width: 85px; height: 15px; margin: 0;
  background: rgba(15, 12, 64, .19);
  -webkit-mask-image: var(--star); mask-image: var(--star);
  -webkit-mask-size: 17px 15px;   mask-size: 17px 15px;
  -webkit-mask-repeat: repeat-x;  mask-repeat: repeat-x;
}
.cw-stars > span {
  position: absolute; inset: 0 auto 0 0;
  width: calc(var(--r) / 5 * 100%);
  background: #ff751f;
}
.cw-rate {
  display: flex; align-items: center; gap: 10px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px; letter-spacing: .1em;
  color: rgba(15, 12, 64, .5);
}

/* The quote is the cell's subject, so it carries the weight rather than
   sitting under the brand name as small grey text. */
.cw-quote {
  font-size: clamp(1rem, 1.25vw, 1.16rem);
  font-weight: 600; line-height: 1.45; letter-spacing: -.02em;
  margin: 0; color: #0f0c40;
  flex: 1;
}
.cw-who { margin: 0; font-size: 14.5px; line-height: 1.5; }
.cw-who > b { font-weight: 700; }
.cw-who > span { color: rgba(15, 12, 64, .6); }

/* Just the service codes. The card bottom-aligns its content, so a meta line
   that wraps to two lines pushes that one card's quote and attribution a line
   above its neighbours' — keep it to about four codes and it stays on one. */
.cw-meta {
  display: flex; flex-direction: column; gap: 5px;
  padding-top: 14px;
  border-top: 1px solid rgba(15, 12, 64, .14);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px; letter-spacing: .13em; text-transform: uppercase;
  color: rgba(15, 12, 64, .42);
}
/* the service codes tie each client back to the schedule on services.html */
.cw-meta a { color: #ff751f; }
.cw-meta a:hover { text-decoration: underline; }

/* ── the statement roll ───────────────────────────────────────────────
   Every state occupies the same grid cell, so the h1 sizes to the tallest
   one and states stack without absolute positioning. .rline masks its span;
   an off-position span is simply clipped out of view. */
.roll {
  display: grid;
  font-size: clamp(2.15rem, 6.1vw, 6.25rem);
  font-weight: 800;
  line-height: .96;
  letter-spacing: -.035em;
  /* greedy wrapping on purpose — text-wrap:balance evens the lines out and
     breaks "We manage your / inventory." in the wrong place */
}
.roll-state { grid-area: 1 / 1; }
.rline {
  display: block;
  overflow: hidden;
  /* room for descenders inside the mask, pulled back out of the flow */
  padding-bottom: .14em;
  margin-bottom: -.14em;
}
.rline > span {
  display: block;
  will-change: transform;
}
/* pre-JS state: first claim visible, the rest parked below their masks */
.roll-state:not(:first-child) .rline > span { transform: translateY(110%); }

/* panel claim — one short statement, so it carries real size */
.panel-claim {
  font-size: clamp(1.6rem, 2.1vw, 2.15rem);
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -.025em;
  /* tabular figures keep the line from jittering while the count runs */
  font-variant-numeric: tabular-nums;
}
/* Counting figures are right-aligned in a box the script reserves at the
   final width, so the digits grow leftward and nothing beside them moves.
   tabular-nums keeps each digit the same width as it ticks. */
[data-count] {
  display: inline-block;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* inline-block opens a break opportunity, so keep the figure and its + together.
   The spur block marks the figure out as the claim's payload. */
.count-wrap {
  white-space: nowrap;
  background: #ff751f;
  color: #0f0c40;
  padding: 0 .11em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* A button, so picking a statement works from the keyboard as well as the
   mouse and the global :focus-visible rule already covers it. */
.tick {
  appearance: none; border: 0; padding: 0;
  position: relative;
  width: 22px; height: 3px;
  background: rgba(15, 12, 64, .18);
  cursor: pointer;
  transition: background-color .3s ease;
}
.tick.is-on { background: #ff751f; }
.tick:hover { background: rgba(15, 12, 64, .42); }
.tick.is-on:hover { background: #ff751f; }
/* The bar is 3px tall, which is nowhere near a usable tap target. This grows
   the hit area to 28x25 without moving the bar; -3px a side exactly meets the
   6px gap, so neighbouring targets touch but never overlap. */
.tick::after {
  content: "";
  position: absolute; left: -3px; right: -3px; top: -11px; bottom: -11px;
}

/* ── mood band ────────────────────────────────────────────────────────
   Values carried over from the design canvas: navy → violet → spur ramp,
   a fine dot screen and a 46px vertical rule grid over the top. */
.band-mood {
  height: clamp(300px, 42vw, 540px);
  background: linear-gradient(100deg, #0f0c40 0%, #2a1f6b 52%, #ff751f 100%);
}
/* The resting screen. js/band.js mirrors this pitch and colour on a canvas so
   the dots can be shoved around by the pointer; if it does, it sets .is-live
   and this background steps aside. No JS or reduced motion leaves it as-is. */
.band-dots {
  position: absolute; inset: 0;
  background-image: radial-gradient(rgba(255, 255, 253, .42) 1px, transparent 1.1px);
  background-size: 5px 5px;
  opacity: .5;
}
.band-dots.is-live { background-image: none; }
.band-dots > canvas { position: absolute; inset: 0; width: 100%; height: 100%; }

.band-rules {
  position: absolute; inset: 0;
  background: repeating-linear-gradient(90deg, rgba(15, 12, 64, .22) 0 1px, transparent 1px 46px);
}
.band-title {
  font-size: clamp(30px, 5vw, 82px);
  font-weight: 900;
  line-height: .94;
  letter-spacing: -.045em;
  color: #fffffd;
  margin: 0;
}
.band-ticks {
  position: absolute; left: 20px; top: 0; bottom: 0;
  width: 6px; padding: 24px 0;
  display: flex; flex-direction: column; justify-content: space-between;
}
.band-ticks > span { height: 1px; background: rgba(255, 255, 253, .5); }
.band-ticks > .is-dim { background: rgba(255, 255, 253, .3); }

/* reprise: the ramp and the rail mirror, so the pair reads as a matched set
   rather than the same band pasted twice */
.band-mood--flip {
  background: linear-gradient(100deg, #ff751f 0%, #2a1f6b 48%, #0f0c40 100%);
}
.band-ticks--right { left: auto; right: 20px; }

/* a photo, if one is dropped in, sits under the dot screen */
.band-photo {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  mix-blend-mode: overlay;
}

/* ── section headings ────────────────────────────────────────────────── */
.sec-title {
  font-size: clamp(1.9rem, 3.6vw, 3.6rem);
  font-weight: 800;
  line-height: .97;
  letter-spacing: -.035em;
  margin: 0;
  /* No text-wrap:balance here — it equalises line lengths, which breaks these
     headings mid-sentence. Where the break matters, mark it up with a <br>. */
}

/* ── FIG. 01 — the inventory loop ─────────────────────────────────────
   The figure keeps a fixed 1440 coordinate system. It scales with the
   viewport but never below 1240px, because past that the pill labels stop
   being readable — narrower than that it scrolls sideways instead. */
.fig-wrap { overflow-x: auto; }
.fig { display: block; width: 100%; min-width: 1240px; height: auto; }

.fig-hair { stroke: rgba(15, 12, 64, .12); stroke-width: 1; }
.fig-lbl {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px; letter-spacing: .15em;
  fill: rgba(15, 12, 64, .42);
}
.fig-num {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px; letter-spacing: .1em;
  fill: #ff751f;
}
.fig-pill { fill: #fffffd; stroke: rgba(15, 12, 64, .22); stroke-width: 1; }
.fig-name {
  font-family: Archivo, Helvetica, Arial, sans-serif;
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em;
  fill: #0f0c40;
}
.fig-sub {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 9.5px; letter-spacing: .1em;
  fill: rgba(15, 12, 64, .4);
}
.fig-forward, .fig-return {
  fill: none; stroke: #ff751f; stroke-width: 1.5;
}
.fig-mark { fill: #ff751f; }
/* the travelling pellet is one short dash chased along the return path */
.fig-pellet {
  fill: none; stroke: #ff751f; stroke-width: 6;
  stroke-linecap: round; opacity: 0;
}

/* ── the six stages ─────────────────────────────────────────────────
   Dark block on the palette's violet. One row lights at a time: white name,
   a nudge in scale, and a spur gradient rule that fades out to the right.
   js/stages.js decides which row that is. */
.pl-section { background: #2a1f6b; color: #fffffd; }
.pl-section .sec-title { color: #fffffd; }
/* Detail panels. Plain flow by default so all six read without JS; once
   js/stages.js marks the container live they stack into one cell and only the
   focused stage shows. Stacking means the column reserves the tallest panel,
   so switching stages never shifts the layout. */
.pl-details {
  align-self: start;
  display: grid;
  gap: 18px;
}
/* The lit row is whichever one is nearest the middle of the viewport, so the
   panel sticks there too — otherwise it scrolls out of the column and you
   can't read the detail for the stage that's currently highlighted.

   The old offset was half the height of a one-paragraph panel. A stage
   carrying pointers and a closing line is four times that, and starting it
   mid-viewport pushed the last lines below the fold.

   Centring it with translateY(-50%) is the obvious fix and the wrong one: on
   a 587px viewport a 437px panel then slides its first bullet under the
   sticky nav, and CSS cannot express "centre, but never above the nav"
   without knowing the panel's height. So it is anchored near the top and
   clamped — far enough down to keep some relationship with the lit row on a
   tall screen, never so far that a short one clips the closing line. */
@media (min-width: 1024px) {
  .pl-details {
    position: sticky;
    top: clamp(calc(var(--nav) + 18px), 18vh, 240px);
  }
}
.pl-detail {
  font-size: 15px;
  line-height: 1.6;
  color: rgba(255, 255, 253, .62);
  margin: 0;
}

/* A stage whose copy has been written out properly: the pointers, then one
   line on where the judgement sits. Same column, same scale as the paragraph
   it replaces — the panel just gets taller. */
/* A stage whose pointers split into groups. Deliberately unnumbered: two
   labels do not need counting, and the eye reads them as headings either way. */
.pl-group {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: rgba(255, 255, 253, .5);
  margin: 0 0 9px;
}
.pl-points + .pl-group { margin-top: clamp(14px, 1.4vw, 18px); }

/* A stage with two groups puts them side by side rather than stacked — nine
   pointers in one column is taller than a short window can show. The closing
   note spans both, since it is about the stage and not either group. */
@media (min-width: 1024px) {
  .pl-detail--split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 clamp(18px, 1.8vw, 30px);
    align-items: start;
  }
  .pl-detail--split > .pl-note { grid-column: 1 / -1; }
  /* the pair already carries its own bottom spacing via the note's rule */
  .pl-detail--split .pl-points { margin-bottom: 0; }
}

/* Leading and gaps are tighter than the paragraph they replace. Six pointers
   plus a closing note is a tall block, and the panel has to clear the sticky
   nav and the fold on a short laptop window — every line saved here is a line
   that doesn't fall off the bottom. */
.pl-points {
  list-style: none;
  margin: 0 0 clamp(13px, 1.3vw, 17px);
  padding: 0;
  display: grid;
  gap: 8px;
}
.pl-points > li {
  position: relative;
  padding-left: 23px;
  line-height: 1.45;
  color: rgba(255, 255, 253, .72);
}
/* a short spur rule for the marker, not a disc — the same hairline vocabulary
   the figures and the section eyebrows already use */
.pl-points > li::before {
  content: "";
  position: absolute; left: 0; top: .63em;
  width: 11px; height: 1px;
  background: #ff751f;
}
/* the human-judgement line: full ink and weight, so it reads as the point of
   the stage rather than a sixth bullet */
.pl-note {
  margin: 0;
  padding-top: clamp(13px, 1.3vw, 17px);
  border-top: 1px solid rgba(255, 255, 253, .16);
  font-weight: 600;
  color: #fffffd;
}
.pl-details.is-live { gap: 0; }
.pl-details.is-live .pl-detail {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity .3s ease;
}
.pl-details.is-live .pl-detail.is-on { opacity: 1; }

/* The bottom padding is structural, not decoration. The detail panel sticks
   inside this column, and sticky unpins as soon as the panel's bottom would
   pass the container's bottom — without this the last stage lights while the
   container has already run out, and its panel slides up under the nav. The
   padding buys enough container for the panel to stay pinned through row 07. */
.pl-list {
  list-style: none;
  margin: 0;
  padding: 0 0 clamp(185px, 28vh, 300px);
}
.pl-row {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0 clamp(12px, 2vw, 30px);
  /* Taller than the copy strictly needs. The detail panel sticks inside this
     column, and sticky can only travel the difference between the list height
     and the panel height — at the old padding that was ~110px, so the panel
     ran out of travel and slid under the nav on the last stages. */
  padding: clamp(13px, 2vw, 30px) 0;
  border-bottom: 1px solid rgba(255, 255, 253, .13);
}
.pl-num {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px; letter-spacing: .12em;
  color: #ff751f;
  opacity: .6;
}
.pl-name {
  /* sized so the longest stage name, Procurement/Logistics, still clears its
     own description on the same row — the narrower list column since the left
     copy column widened means this can't go back up */
  font-size: clamp(1.6rem, 2.6vw, 2.8rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.03em;
  color: rgba(255, 255, 253, .26);
  transform-origin: left center;
  transition: color .35s ease, transform .35s ease;
}
.pl-tag {
  font-size: 13px;
  /* holds one line at desktop; caps the column so a long line can't push
     against the stage name */
  max-width: 30ch;
  text-align: right;
  color: rgba(255, 255, 253, .28);
  transition: color .35s ease;
}
/* the lit rule sits on the row's own hairline */
.pl-row::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, #ff751f 0%, rgba(255, 117, 31, .4) 55%, transparent 100%);
  opacity: 0;
  transition: opacity .35s ease;
}
.pl-row.is-on .pl-name { color: #fffffd; transform: scale(1.045); }
.pl-row.is-on .pl-tag  { color: #fffffd; }
.pl-row.is-on::after   { opacity: 1; }

/* Side by side needs ~1600px now the left copy column takes 44% — at 1440 the
   list has ~690px and "Procurement/Logistics" alone eats most of it. Below the
   threshold the description drops under the name, which frees the whole row
   width and keeps the longest name from colliding with it. Raise this in step
   with the column split, or the two collide on a mid-size desktop. */
@media (max-width: 1599px) {
  .pl-row { grid-template-columns: auto 1fr; }
  .pl-tag { grid-column: 2; text-align: left; padding-top: 6px; max-width: none; }
}

/* ── FIG. 02 — the accountability path ────────────────────────────────
   Shares .fig-wrap / .fig / .fig-hair / .fig-lbl with FIG. 01. */
.tp-pill { fill: #fffffd; stroke: rgba(15, 12, 64, .22); stroke-width: 1; }
.tp-pill--on { fill: #ff751f; stroke: #ff751f; }
.tp-name {
  font-family: Archivo, Helvetica, Arial, sans-serif;
  font-size: 13px; font-weight: 600; letter-spacing: -.01em;
  fill: #0f0c40;
}
.tp-name--on { fill: #fffffd; }
/* SVG trims the whitespace ahead of a tspan, so inline sub-labels get their
   gap from dx in the markup rather than from a space character. */
.tp-sub {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 8.5px; font-weight: 400; letter-spacing: .1em;
  fill: #ff751f;
}
.tp-sub--on { fill: rgba(255, 255, 253, .85); }
.tp-link { fill: none; stroke: #ff751f; stroke-width: 1.5; }
/* one short round dash chased along a branch reads as a travelling pulse */
.tp-pulse {
  fill: none; stroke: #ff751f; stroke-width: 6;
  stroke-linecap: round; opacity: 0;
}

/* ── who we serve ─────────────────────────────────────────────────────
   The dotted ring is one circle with a near-zero dash and a round cap,
   rather than two dozen hand-placed dots. */
/* a centred statement, so it sits below the section-title scale and holds
   one line at desktop instead of orphaning the last word */
.cl-title { font-size: clamp(1.65rem, 3vw, 2.9rem); }

/* words start dim and are brought up to full ink as the block scrolls */
#clients-lede .w { color: rgba(15, 12, 64, .24); }

/* ── client marquee ──────────────────────────────────────────────────
   The track holds two identical sets, so translating it -50% lands
   exactly on the start of the second set and the loop is seamless. */
.marquee { overflow: hidden; }
.marquee-track {
  display: flex;
  width: max-content;
  animation: marquee 34s linear infinite;
}
.marquee:hover .marquee-track,
.marquee:focus-within .marquee-track { animation-play-state: paused; }
.mq-set { display: flex; }
@keyframes marquee { to { transform: translateX(-50%); } }

/* A cell carries the client's wordmark and their name. Categories used to sit
   here; they moved to the filter on clients.html, which is the page that can
   actually do something with them. */
.mq-cell {
  flex: 0 0 auto;
  width: clamp(160px, 15vw, 232px);
  height: clamp(92px, 9vw, 124px);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 9px;
  border-right: 1px solid rgba(15, 12, 64, .14);
  /* The playhead hands the highlight over rather than snapping it: one cell
     fades up as the one before it fades down. A cell is under the arrow for
     ~4.2s at the current track speed, so 0.4s reads as a hand-off without
     ever leaving two cells looking lit at once. */
  transition: background-color .4s ease, color .4s ease, border-right-color .4s ease;
}
/* Fixed height whatever lands in it, so swapping a logo can never change the
   track width and desync the -50% loop. */
.mq-mark {
  height: clamp(30px, 3vw, 42px);
  width: 100%;
  /* the 8:1 wordmarks reach this box's full width, so the gutter has to live
     here — without it they run flush into the cell's dividing rules */
  padding: 0 clamp(16px, 1.8vw, 26px);
  display: flex; align-items: center; justify-content: center;
}
.mq-mark > span {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 9px; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(15, 12, 64, .32);
  border: 1px dashed rgba(15, 12, 64, .22);
  padding: 3px 7px;
  /* matched to the cell, or the dashed slot snaps while the ground fades */
  transition: color .4s ease, border-color .4s ease;
}
/* Sources run from 1:1 badges to 8:1 wordmarks, so both axes are capped and
   object-fit does the letterboxing — height alone would push a wide mark
   straight through the side of its cell. */
.mq-mark > img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
}
/* A logo drawn in white is invisible on the paper cell. brightness(0) renders
   any colour as flat ink, which is the usual treatment for a logo wall anyway
   — mark the light ones and they fall in with the rest. Do NOT put this on a
   logo with a baked-in background: the background turns to ink as well. */
.mq-mark > img[data-ink="light"] { filter: brightness(0); opacity: .78; }
.mq-cell--on .mq-mark > img[data-ink="light"] { filter: brightness(0) invert(1); }
.mq-name {
  font-family: Archivo, Helvetica, Arial, sans-serif;
  font-size: clamp(13px, 1.05vw, 15.5px);
  font-weight: 700;
  letter-spacing: -.02em;
}

/* Applied by js/clients.js to whichever cell is passing under the arrow, so
   the arrow reads as a playhead rather than pointing at nothing. */
.mq-cell--on { background: #ff751f; color: #fffffd; border-right-color: #ff751f; }
.mq-cell--on .mq-mark > span {
  color: rgba(255, 255, 253, .8); border-color: rgba(255, 255, 253, .5);
}

/* ── footer ───────────────────────────────────────────────────────────
   Sits on the same gradient device as the two mood bands: a paper card
   floating on the ramp, then the mark oversized and cropped beneath it. */
.ft {
  padding: clamp(14px, 1.6vw, 26px);
  /* .band-mood pins a fixed band height; the footer sizes to its content */
  height: auto;
}
/* Spacing here is kept lean on purpose: the seven-item column drives the
   card height, and the whole footer has to clear a laptop viewport that has
   already lost ~100px to browser chrome. */
.ft-card {
  background: #fffffd;
  padding: clamp(24px, 2.6vw, 44px);
  border-radius: 2px;
}
.ft-head {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10.5px; font-weight: 400;
  letter-spacing: .15em; text-transform: uppercase;
  color: rgba(15, 12, 64, .45);
  margin: 0 0 14px;
}
.ft-list { list-style: none; margin: 0; padding: 0; }
.ft-list li + li { margin-top: 7px; }
.ft-list a {
  font-size: 14.5px;
  color: #0f0c40;
  text-decoration: none;
}
.ft-list a:hover { color: #ff751f; }

.ft-pixels {
  position: absolute;
  left: clamp(-6px, -0.4vw, 0px);
  bottom: 0;
  opacity: .9;
  pointer-events: none;
}
/* below desktop the columns reflow over that corner, so the motif goes */
@media (max-width: 1023px) { .ft-pixels { display: none; } }

/* The mark is sized in vw so it always spans the footer, and the wrapper is
   deliberately shorter than the glyphs — that shortfall is the crop. Both
   scale off the same vw basis, so the proportion of letter shown is fixed
   at every width. */
/* Scales off the parent's real width, not vw — vw includes the scrollbar and
   ignores the footer padding, which is what used to push the last letter off
   the right edge. The 711x65 viewBox crops the glyph feet. */
.ft-mark {
  display: block;
  width: 100%;
  height: auto;
  margin-top: clamp(14px, 2.4vw, 40px);
  /* .band-dots and .band-rules are absolutely positioned, so the mark has to
     be positioned too or the dot screen paints over the letterforms. */
  position: relative;
}
.ft-mark text {
  font-family: Archivo, Helvetica, Arial, sans-serif;
  font-size: 100px;
  font-weight: 900;
  letter-spacing: -.055em;
  fill: #0f0c40;
}

.ft-bar {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 26px;
  padding: 14px clamp(4px, 1vw, 14px) 4px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(255, 255, 253, .72);
}
.ft-bar a { color: inherit; text-decoration: none; }
.ft-bar a:hover { color: #fffffd; }

/* ── reduced motion: no pin, no roll, first claim only ───────────────── */
.no-motion .roll-state:not(:first-child) { display: none; }
.no-motion #ticks, .no-motion #tick-count { display: none; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition-duration: .01ms !important; }
  /* the marquee stops and becomes something you scroll yourself */
  .marquee-track { animation: none; }
  .marquee { overflow-x: auto; }
  #clients-lede .w { color: #0f0c40; }
}

/* ── audit form ───────────────────────────────────────────────────────
   Three panes in one <form>; js/audit.js shows one at a time. */
.au-card { background: #fffffd; border: 1px solid rgba(15,12,64,.14); border-radius: 20px; overflow: hidden; box-shadow: 0 20px 50px -25px rgba(15,12,64,.3); }
.au-head { background: #0f0c40; padding: 24px 28px 22px; }
.au-body, .au-done { padding: 24px 28px 28px; }
.au-head[hidden], .au-body[hidden] { display: none; }
.au-bar { display: flex; gap: 8px; flex: 1; }
.au-bar i { flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,253,.15); }
.au-bar i.on { background: #ff751f; }
.au-pane { border: 0; padding: 0; margin: 0; min-width: 0; }
.au-pane[hidden], .au-msg[hidden], .au-done[hidden], .au-back[hidden], .au-foot[hidden] { display: none; }
.au-label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 600; color: #0f0c40; padding: 0; }
.au-label b { color: #ff751f; }
.au-opts { display: grid; gap: 10px; }
.au-opts--chips { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 520px) { .au-opts--stack { grid-template-columns: 1fr; } }
.au-opts label { cursor: pointer; position: relative; }
.au-opts input { position: absolute; opacity: 0; pointer-events: none; }
.au-opts span { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border: 1px solid rgba(15,12,64,.14); border-radius: 12px; font-size: 15px; transition: border-color .15s, background .15s; }
.au-opts span::before { content: ""; width: 18px; height: 18px; flex: none; border: 2px solid rgba(15,12,64,.25); border-radius: 50%; }
.au-opts input[type=checkbox] + span::before { border-radius: 4px; }
.au-opts label:hover span { border-color: #ff751f; }
.au-opts input:checked + span { border-color: #ff751f; background: rgba(255,117,31,.08); }
.au-opts input:checked + span::before { border-color: #ff751f; background: #ff751f; box-shadow: inset 0 0 0 3px #fffffd; }
.au-opts input:focus-visible + span { outline: 2px solid #0f0c40; outline-offset: 2px; }
.au-field { display: block; }
.au-field input, .au-field select, .au-field textarea { width: 100%; padding: 13px 16px; border: 1px solid rgba(15,12,64,.14); border-radius: 12px; background: #faf8f3; font: inherit; font-size: 15px; color: #0f0c40; }
.au-field input:focus, .au-field select:focus, .au-field textarea:focus { outline: 2px solid #ff751f; outline-offset: 0; }
.au-next, .au-back { border-radius: 10px; padding: 15px 20px; font-weight: 700; font-size: 15px; transition: background .15s; }
.au-next { flex: 1; background: #ff751f; color: #0f0c40; }
.au-next:hover { background: #0f0c40; color: #fffffd; }
.au-next:disabled { opacity: .6; cursor: wait; }
.au-back { border: 1px solid rgba(15,12,64,.3); }
.au-msg { margin-top: 16px; font-size: 13px; color: #c0392b; }
.au-err { margin-top: 6px; font-size: 12.5px; color: #c0392b; }
.au-opts + .au-err { margin-top: 10px; }
.au-field.au-bad input, .au-field.au-bad select { border-color: #c0392b; }

/* ── audit form, step 4 (booking) ─────────────────────────────────── */
.au-actions[hidden] { display: none; }
.bk-back { margin-bottom: 14px; font-size: 13px; color: rgba(15,12,64,.6); }
.bk-back:hover { color: #0f0c40; }
.bk-load { padding: 28px 0; text-align: center; color: rgba(15,12,64,.6); }
.bk-retry { text-decoration: underline; color: #ff751f; font-weight: 600; }
.bk-month { display: flex; align-items: center; justify-content: space-between; margin: 0 auto 12px; max-width: 380px; }
.bk-prev, .bk-next { width: 36px; height: 36px; border-radius: 50%; font-size: 20px; line-height: 1; background: rgba(255,117,31,.1); color: #0f0c40; }
.bk-prev:disabled, .bk-next:disabled { opacity: .3; cursor: default; }
.bk-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; max-width: 380px; margin: 0 auto; text-align: center; }
.bk-wd { padding: 6px 0; font-size: 12px; font-weight: 600; color: rgba(15,12,64,.55); }
.bk-day { width: 100%; max-width: 40px; aspect-ratio: 1; margin: 0 auto; display: grid; place-items: center; border-radius: 50%; font-size: 14px; color: rgba(15,12,64,.28); }
.bk-day.on { background: rgba(255,117,31,.12); color: #0f0c40; font-weight: 600; cursor: pointer; transition: background .15s; }
.bk-day.on:hover { background: #ff751f; }
.bk-h { font-size: 20px; font-weight: 700; text-align: center; }
.bk-sub { margin: 2px 0 16px; text-align: center; font-size: 13px; color: rgba(15,12,64,.6); }
.bk-slots { display: grid; gap: 10px; }
.bk-slot, .bk-sure { width: 100%; padding: 13px; border: 1px solid #ff751f; border-radius: 10px; font-weight: 600; font-size: 14px; color: #0f0c40; transition: background .15s; }
.bk-slot:hover { background: rgba(255,117,31,.1); }
.bk-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.bk-slot.sel { background: #0f0c40; border-color: #0f0c40; color: #fffffd; }
.bk-sure { background: #ff751f; }
.bk-sum { padding: 16px; border: 1px solid rgba(15,12,64,.14); border-radius: 12px; background: #faf8f3; font-size: 14px; line-height: 1.6; }
