/* Base and components. Every value comes from tokens.css.
   Components are built once here and on specimen.html; pages compose them unchanged. */

/* Base */

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

html {
  background-color: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-sans);
  font-weight: var(--font-weight);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
  text-rendering: optimizeLegibility;
}

body {
  margin: 0;
  font-size: var(--type-body-size);
  letter-spacing: var(--type-body-tracking);
  line-height: var(--type-body-leading);
}

h1, h2, h3, h4, p, figure, dl, dd, ul, ol {
  margin: 0;
  padding: 0;
}

h1, h2, h3, h4, th, strong, b {
  font-weight: var(--font-weight);
}

a {
  color: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: var(--link-underline-offset);
}

a:hover {
  color: var(--color-accent);
}

/* Webflow only: an HTML embed holding one element (a device, an image, a toggle) takes no box of its own,
   so the element sits in the layout as it does here. The build adds this class to those embeds. */
.cs-embed { display: contents; }
/* Code layer: Webflow's clearfix on embeds would add two empty flex or grid items */
.cs-embed::before,
.cs-embed::after { display: none; }

/* Static ground (DESIGN.md §4): the paper grain on one fixed layer, the first child of <body> on every
   page. It jumps between eight tile offsets every 0.1s, which reads as faint living film static; the
   animation is transform only, so it never repaints the page, and it holds still under reduced motion.
   Content sits above it; image panels and devices are opaque and cover it. Works on iOS Safari, which
   ignores fixed backgrounds. */
.static {
  position: fixed;
  inset: var(--static-inset);
  z-index: 0;
  pointer-events: none;
  background-color: var(--color-paper);
  background-image: var(--paper-grain);
  background-size: var(--paper-grain-size);
  animation: var(--static-animation);
}

@keyframes static {
  0% { transform: translate3d(var(--static-offset-1), 0); }
  12.5% { transform: translate3d(var(--static-offset-2), 0); }
  25% { transform: translate3d(var(--static-offset-3), 0); }
  37.5% { transform: translate3d(var(--static-offset-4), 0); }
  50% { transform: translate3d(var(--static-offset-5), 0); }
  62.5% { transform: translate3d(var(--static-offset-6), 0); }
  75% { transform: translate3d(var(--static-offset-7), 0); }
  87.5% { transform: translate3d(var(--static-offset-8), 0); }
}

/* Anything that isn't a page wrapper with its own rule (.case-page, .site-frame) and sits on the static */
.above-static {
  position: relative;
  z-index: 1;
}

/* Site header (DESIGN.md §8): wordmark, nav, where and when, contact. On the home page its items sit
   straight in the frame's grid (display: contents), above the stage's states (z-index 2); on a case study, .is-bar gives it its
   own grid in the same columns. At 991px and down (Webflow's tablet step), the location and contact move to the page's foot. */
.site-head { display: contents; }
.site-head.is-bar {
  display: grid;
  grid-template-columns: var(--frame-cols);
  column-gap: var(--frame-column-gap);
  align-items: start;
  padding: var(--frame-margin) var(--frame-margin) 0;
}
.site-logo {
  position: relative;
  z-index: 2;
  grid-column: var(--place-h-logo);
  grid-row: var(--row-h);
  align-self: start;
  justify-self: start;
  color: var(--cs-text);
  text-decoration-line: none;
  white-space: nowrap;
}
.site-nav {
  position: relative;
  z-index: 2;
  grid-column: var(--place-h-nav);
  grid-row: var(--row-h-nav);
  align-self: start;
  justify-self: var(--h-nav-align);
  display: flex;
  flex-direction: column;
  align-items: var(--h-nav-align);
}
.site-nav-link {
  color: var(--cs-muted);
  text-decoration-line: none;
  white-space: nowrap;
}
.site-nav-link.is-current { color: var(--cs-text); }
.site-where {
  position: relative;
  z-index: 2;
  grid-column: var(--place-h-where);
  grid-row: var(--row-h);
  order: var(--h-foot-order);
  align-self: start;
  margin: var(--h-foot-top) 0 0;
  color: var(--cs-text);
  white-space: nowrap;
}
.site-when { color: var(--cs-muted); }
.site-contact {
  position: relative;
  z-index: 2;
  grid-column: var(--place-h-contact);
  grid-row: var(--row-h);
  order: var(--h-foot-order);
  align-self: start;
  justify-self: end;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  margin: var(--h-foot-top) 0 0;
  white-space: nowrap;
}
.site-contact-link {
  display: inline;
  color: var(--cs-text);
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: var(--link-underline-offset);
}
.site-contact-link.is-muted { color: var(--cs-muted); }

/* Grid: full bleed, no container */

.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  column-gap: var(--grid-gutter);
  padding-inline: var(--grid-margin);
}

.rail { grid-column: var(--place-rail); margin-bottom: var(--rail-gap); }
.col-body { grid-column: var(--place-body); }
.col-head { grid-column: var(--place-head); }
.col-hero { grid-column: var(--place-hero); }
.col-figure { grid-column: var(--place-figure); }
.col-full { grid-column: var(--place-full); }

.section { margin-top: var(--space-section); }

/* The page grid's columns without its margins, for grids nested in a full-width item */
.grid-inner {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  column-gap: var(--grid-gutter);
}
.block { margin-top: var(--space-block); }

/* Type */

.t-hero {
  font-size: var(--type-hero-size);
  letter-spacing: var(--type-hero-tracking);
  line-height: var(--type-hero-leading);
}

.t-title {
  text-wrap: balance;
  font-size: var(--type-title-size);
  letter-spacing: var(--type-title-tracking);
  line-height: var(--type-title-leading);
}

.t-deck {
  font-size: var(--type-deck-size);
  letter-spacing: var(--type-deck-tracking);
  line-height: var(--type-deck-leading);
  color: var(--color-ink-soft);
}

.t-section {
  text-wrap: balance;
  font-size: var(--type-section-size);
  letter-spacing: var(--type-section-tracking);
  line-height: var(--type-section-leading);
}

.t-body {
  font-size: var(--type-body-size);
  letter-spacing: var(--type-body-tracking);
  line-height: var(--type-body-leading);
}

.t-soft { color: var(--color-ink-soft); }

.t-stat {
  font-size: var(--type-stat-size);
  letter-spacing: var(--type-stat-tracking);
  line-height: var(--type-stat-leading);
  font-variant-numeric: tabular-nums;
}

.t-label {
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  line-height: var(--type-label-leading);
  color: var(--color-ink-label);
}

.t-detail {
  font-size: var(--type-detail-size);
  letter-spacing: var(--type-detail-tracking);
  line-height: var(--type-detail-leading);
}

.t-table {
  font-size: var(--type-table-size);
  letter-spacing: var(--type-table-tracking);
  line-height: var(--type-table-leading);
  font-variant-numeric: tabular-nums;
}

/* Paragraph measure and rhythm */

.prose > p { max-width: var(--measure); }
.prose > p + p { margin-top: var(--space-paragraph); }

/* Signifier accent: inline only, x-height matched to its Söhne host */

.accent-face {
  font-family: var(--font-accent);
  font-style: italic;
  font-size: calc(1em * var(--font-accent-scale));
  letter-spacing: 0;
  line-height: 0;
}

/* Eyebrow: label with an accent plate numeral */

.plate-number { color: var(--color-accent); }

/* Project pattern: eyebrow → title → deck */

.project-head > * + * { margin-top: var(--space-24); }
.project-head .t-deck { max-width: var(--measure); }

/* Details: labelled facts, hairline above */

.details {
  display: grid;
  grid-template-columns: repeat(var(--detail-columns), minmax(0, 1fr));
  gap: var(--space-24) var(--grid-gutter);
  border-top: var(--hairline) solid var(--color-rule);
  padding-top: var(--space-16);
}

.details dd { margin-top: var(--space-8); }

/* Stats: numerals, hairlines above and below */

/* Figures sit side by side and wrap to a new row when a wide numeral can't share it */
.stats {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-40) var(--grid-gutter);
  border-block: var(--hairline) solid var(--color-rule);
  padding-block: var(--space-24);
}

.stats-item { flex: 1 0 auto; }

.stats-value {
  margin: 0;
  font-weight: var(--font-weight);
  white-space: nowrap;
}

.stats-label { margin: var(--space-8) 0 0; }
/* Outcome labels drop the label tracking (set in Webflow, 2026-10-10) */
.stats-label.t-label { letter-spacing: 0em; }

/* Roles table: CSS grid rows with fine hairlines */

.roles-head,
.roles-row {
  display: grid;
  grid-template-columns: var(--table-columns);
  column-gap: var(--grid-gutter);
}

.roles-head { display: var(--table-head-display); }

.roles-row {
  margin-top: var(--space-table-row);
  border-top: var(--hairline-fine) solid var(--color-rule);
  padding-top: var(--space-8);
}

.roles-role { grid-column: var(--table-role-place); }
.roles-size,
.roles-dates { color: var(--table-meta-color); }

/* Motion: fade and rise on entry. site.js adds .motion to <html>. */

.motion [data-reveal] {
  opacity: 0;
  transform: translateY(var(--motion-distance));
  transition:
    opacity var(--motion-duration) var(--motion-ease),
    transform var(--motion-duration) var(--motion-ease);
  transition-delay: calc(var(--reveal-index, 0) * var(--motion-stagger));
}

.motion [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* Grid overlay: toggled with the button or the G key */

.grid-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--layer-overlay);
  pointer-events: none;
}

.grid-overlay[hidden] { display: none; }

.grid-overlay > span {
  background: var(--color-rule);
  opacity: var(--overlay-opacity);
}

/* On the landing and Projects: the frame's columns, which have no gutters, drawn as edges */
.grid-overlay.is-frame {
  inset: var(--frame-margin);
  display: grid;
  grid-template-columns: var(--frame-cols);
  column-gap: var(--frame-column-gap);
}
.grid-overlay.is-frame[hidden] { display: none; }
.grid-overlay.is-frame > span {
  border-right: var(--hairline) solid var(--color-accent);
  background: none;
}

.grid-toggle {
  position: fixed;
  top: var(--grid-margin);
  right: var(--grid-margin);
  z-index: var(--layer-overlay);
  margin: 0;
  padding: var(--space-8);
  border: var(--hairline) solid var(--color-ink-label);
  border-radius: 0;
  background: var(--color-paper);
  font-family: inherit;
  cursor: pointer;
}

.grid-toggle:hover,
.grid-toggle[aria-pressed="true"] { color: var(--color-ink); }

/* Plate: edge to edge, one image recipe. site.js builds the duotone filters from tokens. */

.plate {
  grid-column: var(--place-full);
  margin-inline: calc(var(--grid-margin) * -1);
  aspect-ratio: var(--plate-aspect);
  background: var(--color-ink-soft);
}

.plate > img,
.plate > svg,
.state-panel > img,
.state-panel > svg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: url(#duotone);
}

.plate > svg,
.state-panel > svg { filter: none; }

.plate-caption {
  margin-top: var(--space-caption);
  row-gap: var(--space-8);
  color: var(--color-ink-label);
}

/* Project: eyebrow → title → deck → details → stats → impact → plate → link */

.project .t-title,
.project .t-deck { margin-top: var(--space-24); }
.project .stats { margin-top: var(--space-40); }
.project-link { margin-top: var(--space-40); }

/* Inverted section: contact. The only one on a page. */

.inverted {
  background: var(--color-ink);
  color: var(--color-paper);
  padding-block: var(--space-160);
}

.inverted .t-label { color: var(--color-paper); }
.inverted a:hover { color: inherit; }

.contact-line {
  font-size: var(--type-contact-size);
  letter-spacing: var(--type-contact-tracking);
  line-height: var(--type-contact-leading);
}

/* accent-inverted clears 3:1 on ink only at 24px and up: large links only */
.inverted a.contact-line:hover { color: var(--color-accent-inverted); }

.contact-links { margin-top: var(--space-24); }

/* Case study pages · DESIGN.md §10. Built once here and on specimen.html; the Willis eCOA
   page composes them unchanged. */

/* Focus: wherever the keyboard is, a ring in ink */
:focus-visible {
  outline: var(--focus-width) solid var(--color-ink);
  outline-offset: var(--focus-offset);
}

/* Read by assistive technology, not drawn */
.visually-hidden {
  position: absolute;
  width: var(--hairline);
  height: var(--hairline);
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Page head: the wordmark, back to the home page */
.page-head { padding-top: var(--grid-margin); }

/* Case study header: eyebrow, title, summary (the project head) */
.case-head { padding-top: var(--space-160); }

/* A case study's sections, 160 apart (the site already has its own .section class) */
.case-section { margin-top: var(--space-section); }

/* Section text: 40 below its heading */
.section-text { margin-top: var(--space-40); }

/* Caption line: one sentence in caption style under a block (a definition, the sourcing) */
.caption-line {
  margin: var(--space-caption) 0 0;
  max-width: var(--measure);
  color: var(--color-ink-label);
}

/* Details table: label and value on one row, a fine hairline above each */
.fact-table { margin: 0; }

.fact-row {
  display: grid;
  grid-template-columns: var(--fact-columns);
  gap: var(--space-8) var(--grid-gutter);
  margin-top: var(--space-table-row);
  border-top: var(--hairline-fine) solid var(--color-rule);
  padding-top: var(--space-8);
}

.fact-label {
  margin: 0;
  font-weight: var(--font-weight);
}

.fact-value {
  margin: 0;
  max-width: var(--measure);
}

/* Beat: a paragraph whose lead sits in the rail, on the paragraph's first baseline */
.beat {
  align-items: baseline;
  margin-top: var(--space-paragraph);
}

.beat-lead { color: var(--color-ink); }
.beat.is-first { margin-top: 0; }

/* Plate row: caption in the rail, top-aligned to the plate */
/* Margins and padding a site-wide figure style may add (the block or section spacing above stays) */
.plate-row {
  align-items: start;
  margin-right: 0;
  margin-bottom: 0;
  margin-left: 0;
  padding-bottom: 0;
}

/* A plate's caption in the rail (undoes a site-wide figcaption style; the rail's own gap stays) */
.fig-caption {
  margin-top: 0;
  text-align: left;
  opacity: 1;
}
.plate-no { display: block; }

/* Plate frame: holds a screenshot at its ratio, untreated (DESIGN.md §10); until the file
   lands, a placeholder in panel with the expected file name. Edge to edge on phones. */
.plate-frame {
  grid-column: var(--place-figure);
  display: grid;
  place-items: center;
  aspect-ratio: var(--plate-screen-aspect);
  margin-inline: calc(var(--plate-bleed) * -1);
  background: var(--color-panel);
}

.plate-frame > img,
.plate-frame > picture,
.plate-frame > picture > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.plate-label {
  padding: var(--space-16);
  text-align: center;
}

/* Process plate: Alex's own schematics, line work on paper, apart from the screenshots */
.plate-frame.is-process {
  aspect-ratio: var(--plate-process-aspect);
  margin-inline: 0;
  border: var(--diagram-stroke) solid var(--color-rule-strong);
  background: none;
}

.process-plate { grid-column: var(--place-figure); }

/* Timeline (Process plate A): the same marks and labels lay out left to right, or top to bottom
   when --timeline-vertical is 1 (phones). Each mark and label carries its date as --at, in months
   from the first month shown, and its row as --row; --span and --rows sit on the timeline. */
.timeline {
  --v: var(--timeline-vertical);
  position: relative;
  height: calc((1 - var(--v)) * var(--rows) * var(--timeline-row) + var(--v) * (var(--timeline-head) + var(--span) * var(--timeline-month)));
}

.timeline-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.tl-month-line {
  x: calc((1 - var(--v)) * var(--at) / var(--span) * 100%);
  y: calc(var(--v) * (var(--timeline-head) + var(--at) * var(--timeline-month)));
  width: calc((1 - var(--v)) * var(--diagram-stroke-fine) + var(--v) * 100%);
  height: calc((1 - var(--v)) * 100% + var(--v) * var(--diagram-stroke-fine));
  fill: var(--color-rule-strong);
}

.is-planned { --lane: var(--timeline-lane-planned); --w: var(--diagram-stroke); }
.is-actual { --lane: var(--timeline-lane-actual); --w: var(--diagram-stroke-bold); }

/* A milestone: a tick across its row, or a mark at the lane's edge */
.tl-mark {
  x: calc((1 - var(--v)) * (var(--at) / var(--span) * 100% - var(--w) / 2) + var(--v) * var(--lane));
  y: calc((1 - var(--v)) * (var(--row) + 0.25) * var(--timeline-row) + var(--v) * (var(--timeline-head) + var(--at) * var(--timeline-month) - var(--w) / 2));
  width: calc((1 - var(--v)) * var(--w) + var(--v) * var(--timeline-tick));
  height: calc((1 - var(--v)) * 0.5 * var(--timeline-row) + var(--v) * var(--w));
  fill: var(--color-ink);
}

/* A planned stretch of time, from --at to --to */
.tl-span {
  x: calc((1 - var(--v)) * var(--at) / var(--span) * 100% + var(--v) * var(--lane));
  y: calc((1 - var(--v)) * ((var(--row) + 0.5) * var(--timeline-row) - var(--w) / 2) + var(--v) * (var(--timeline-head) + var(--at) * var(--timeline-month)));
  width: calc((1 - var(--v)) * (var(--to) - var(--at)) / var(--span) * 100% + var(--v) * var(--w));
  height: calc((1 - var(--v)) * var(--w) + var(--v) * (var(--to) - var(--at)) * var(--timeline-month));
  fill: var(--color-ink);
}

/* The gap between a planned milestone and when it happened, drawn as an L: from the planned
   mark (--at, --row) across the lanes, then along time to the actual mark (--to, --row-to) */
.tl-gap-cross {
  x: calc((1 - var(--v)) * (var(--at) / var(--span) * 100% - var(--diagram-stroke) / 2) + var(--v) * var(--timeline-lane-planned));
  y: calc((1 - var(--v)) * (var(--row) + 0.5) * var(--timeline-row) + var(--v) * (var(--timeline-head) + var(--at) * var(--timeline-month) - var(--diagram-stroke) / 2));
  width: calc((1 - var(--v)) * var(--diagram-stroke) + var(--v) * (var(--timeline-lane-actual) - var(--timeline-lane-planned)));
  height: calc((1 - var(--v)) * (var(--row-to) - var(--row)) * var(--timeline-row) + var(--v) * var(--diagram-stroke));
  fill: var(--color-ink);
}

.tl-gap-along {
  x: calc((1 - var(--v)) * var(--at) / var(--span) * 100% + var(--v) * (var(--timeline-lane-actual) - var(--diagram-stroke) / 2));
  y: calc((1 - var(--v)) * ((var(--row-to) + 0.5) * var(--timeline-row) - var(--diagram-stroke) / 2) + var(--v) * (var(--timeline-head) + var(--at) * var(--timeline-month)));
  width: calc((1 - var(--v)) * (var(--to) - var(--at)) / var(--span) * 100% + var(--v) * var(--diagram-stroke));
  height: calc((1 - var(--v)) * var(--diagram-stroke) + var(--v) * (var(--to) - var(--at)) * var(--timeline-month));
  fill: var(--color-ink);
}

/* Labels: HTML over the drawing, so type stays at its true size. Paper behind them keeps
   the lines from running through the words. Left to right, a label follows its mark
   (or, with .is-late, ends at it); top to bottom, it sits right of its lane's mark. */
.tl-label {
  --late: 0;
  position: absolute;
  left: calc((1 - var(--v)) * (var(--at) / var(--span) * 100% + var(--timeline-gap) * (1 - 2 * var(--late))) + var(--v) * (var(--lane) + var(--timeline-tick) + var(--timeline-gap)));
  top: calc((1 - var(--v)) * var(--row) * var(--timeline-row) + var(--v) * (var(--timeline-head) + var(--vat, var(--at)) * var(--timeline-month) - var(--type-label-size) * var(--type-label-leading) / 2));
  display: flex;
  align-items: center;
  min-height: calc((1 - var(--v)) * var(--timeline-row));
  width: max-content; /* its own width, even where the timeline's edge is near */
  max-width: calc((1 - var(--v)) * var(--timeline-label-width) + var(--v) * var(--lane-room));
  transform: translateX(calc(-100% * var(--late) * (1 - var(--v))));
  background: var(--color-paper);
}

.tl-label.is-planned { --lane-room: calc(var(--timeline-lane-actual) - var(--timeline-lane-planned) - var(--timeline-tick) - 2 * var(--timeline-gap)); }
.tl-label.is-actual { --lane-room: calc(100% - var(--timeline-lane-actual) - var(--timeline-tick) - var(--timeline-gap)); }
.tl-label,
.tl-lane { color: var(--color-ink); }

.tl-label.is-late { --late: 1; text-align: var(--timeline-late-align); }

/* Lane names: at the start of their rows, or above their lanes */
.tl-lane {
  position: absolute;
  left: calc(var(--v) * var(--lane));
  top: calc((1 - var(--v)) * var(--row) * var(--timeline-row));
  display: flex;
  align-items: center;
  min-height: calc((1 - var(--v)) * var(--timeline-row));
  max-width: calc((1 - var(--v)) * 100% + var(--v) * var(--lane-room));
  background: var(--color-paper);
}

.tl-lane.is-planned { --lane-room: calc(var(--timeline-lane-actual) - var(--timeline-lane-planned) - var(--timeline-gap)); }
.tl-lane.is-actual { --lane-room: calc(100% - var(--timeline-lane-actual)); }

/* Month names: along the top, or down the left edge */
.tl-month {
  position: absolute;
  display: flex;
  align-items: center;
  min-height: calc((1 - var(--v)) * var(--timeline-row));
  left: calc((1 - var(--v)) * (var(--at) / var(--span) * 100% + var(--timeline-gap) / 2));
  top: calc(var(--v) * (var(--timeline-head) + var(--at) * var(--timeline-month) + var(--timeline-gap) / 2));
  max-width: calc((1 - var(--v)) * 100% + var(--v) * (var(--timeline-lane-planned) - var(--timeline-gap)));
  color: var(--color-ink-label);
}

/* Case study page: the type and ground every case study sits on */
.case-page {
  position: relative;
  z-index: 1;
  color: var(--color-ink);
  font-family: var(--font-sans);
  font-weight: var(--font-weight);
  font-size: var(--type-body-size);
  letter-spacing: var(--type-body-tracking);
  line-height: var(--type-body-leading);
}

/* The Willis eCOA page's wrapper: .case-page is shared with the v3 and v5 Webflow drafts, which still
   carry the old fixed grain on it, so this combo clears it there (shared classes only take additions) */
.case-page.case-willis {
  position: relative;
  z-index: 1;
  background-color: transparent;
  background-image: none;
  color: var(--color-ink);
  font-size: var(--type-body-size);
  letter-spacing: var(--type-body-tracking);
  line-height: var(--type-body-leading);
}

/* First screen: eyebrow, problem line (the h1), ownership line and tags, with the hero plate
   beside them; stacked on phones. The text uses the project head's spacing. */
.case-intro {
  align-items: start;
  row-gap: var(--space-40);
  padding-top: var(--case-head-top);
}

.case-intro-text { grid-column: var(--place-case-text); }
.case-eyebrow { margin: 0; }
.case-problem { margin: var(--space-24) 0 0; }
.case-owner { margin: var(--space-24) 0 0; }
.case-tags { margin: var(--space-24) 0 0; }

/* A decision block's heading, its paragraph, and the closing line after the blocks */
.decision-heading { margin: 0; }

.case-copy {
  margin: 0;
  max-width: var(--measure);
}

/* A paragraph that follows another in the same column (a combo class, not p + p, so it maps to Webflow) */
.case-copy.is-continued { margin-top: var(--space-paragraph); }

/* Section lead: a small label as the section's heading, then the section's point in large type for
   readers who skim, then the body text below */
.case-kicker { margin: 0; }

/* About the project: a text button in the first screen opens a panel holding the case study's full
   text and its credits (site.js). It reads as a link; + while closed, × while open. Without
   JavaScript the button stays hidden and the panel open. */
.about-row { margin: var(--space-24) 0 0; }
.about-toggle {
  margin: 0;
  padding: 0;
  border-style: none;
  border-color: currentColor;  /* not the browser's button border, which dark mode turns white */
  background-color: transparent;
  color: var(--color-ink);
  font-family: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: var(--link-underline-offset);
  cursor: pointer;
}
.about-toggle:hover { color: var(--color-accent); }
.about-label { margin: 0; }
.about-sign { margin-left: var(--space-8); }

/* Credits: label and value pairs in two columns, Pentagram-style; values one per line */
.credits {
  display: grid;
  grid-template-columns: var(--credits-columns);
  column-gap: var(--grid-gutter);
}
.credits-col { margin: 0; }
.credits-item { margin: 0 0 var(--space-24); }
.credits-label {
  margin: 0;
  color: var(--color-ink-label);
}
.credits-value { margin: 0; }

/* ---- Willis eCOA page (DESIGN.md §10, Figma 2026-10-22) ----
   This page's own system, from the Figma file: smaller, flatter type (the cs- tokens), two equal
   halves, 32px between every block, panels that keep their Figma proportions as the page scales.
   The rest of the site keeps its scale and grid. */

/* Type: Söhne Buch at every size, tracking from Figma's percent (-1 = -0.01em) */
.t-cs-title { font-size: var(--cs-type-title-size); line-height: var(--cs-type-title-leading); letter-spacing: var(--cs-type-title-tracking); }
.t-cs-lead { font-size: var(--cs-type-lead-size); line-height: var(--cs-type-lead-leading); letter-spacing: var(--cs-type-lead-tracking); }
.t-cs-body { font-size: var(--cs-type-body-size); line-height: var(--cs-type-body-leading); letter-spacing: var(--cs-type-body-tracking); }
.t-cs-small { font-size: var(--cs-type-small-size); line-height: var(--cs-type-small-leading); letter-spacing: var(--cs-type-small-tracking); }
.t-cs-caption { font-size: var(--cs-type-caption-size); line-height: var(--cs-type-caption-leading); letter-spacing: var(--cs-type-caption-tracking); }
.t-cs-button { font-size: var(--cs-type-button-size); line-height: var(--cs-type-button-leading); letter-spacing: var(--cs-type-button-tracking); }
.t-cs-logo { font-size: var(--cs-type-logo-size); line-height: var(--cs-type-logo-leading); letter-spacing: var(--cs-type-logo-tracking); }

/* Frame: the wordmark 24px from the top, then one column of blocks, 32px apart */
.cs-header { padding: var(--grid-margin) var(--grid-margin) 0; }
.cs-logo {
  display: block;
  width: fit-content;
  color: var(--cs-text);
  text-decoration-line: none;
}
.cs-logo:hover { color: var(--color-accent); }
.cs-main {
  display: flex;
  flex-direction: column;
  gap: var(--cs-gap);
  margin-top: var(--cs-header-gap);
  padding: var(--cs-body-top) var(--grid-margin) 0;
  color: var(--cs-text);
}
.cs-foot { padding: var(--cs-footnote-top) var(--grid-margin) var(--space-96); }
.cs-footnote {
  margin: 0;
  max-width: var(--cs-side-max);
  color: var(--cs-muted);
}

/* Sticky header (Willis specs §3a, reworked by Alex on 2026-10-22). Nothing shows until the page head's
   title row has scrolled under the top of the screen (site.js adds is-scrolled). Then, on the way down,
   only the reading-mode toggle floats in the top right corner, no strip behind it. Any scroll up brings
   the whole bar back (site.js adds is-full): the paper strip slides down from above and the wordmark and
   project title fade in beside the toggle, which never moves; any scroll down takes them away again.
   cs-header-h tall (70px; 68px on phones, where it's just the wordmark, a link home, and the toggle). While only the
   toggle shows, the rest of the bar lets clicks through to the page. The site header itself scrolls away
   with the page. Under reduced motion the states switch at once. */
.cs-bar {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cs-bar-gap);
  height: var(--cs-header-h);
  padding: var(--cs-bar-pad);
  visibility: hidden;
  pointer-events: none;
}
.cs-bar.is-scrolled { visibility: visible; }
.cs-bar.is-full { pointer-events: auto; }
.cs-bar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--color-paper);
  background-image: var(--paper-grain);
  background-size: var(--paper-grain-size);
  transform: translateY(-100%);
  transition: transform var(--head-slide-duration) var(--head-slide-ease);
}
.cs-bar.is-full::before { transform: none; }
.cs-bar-logo {
  display: var(--cs-bar-logo-display);
  flex: none;
  align-self: stretch;
  align-items: center;  /* the link fills the bar's height, so it's an easy tap home */
  color: var(--cs-text);
  text-decoration-line: none;
}
.cs-bar-title {
  display: var(--cs-bar-title-display);
  flex: 1 1 0%;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  color: var(--cs-text);
  font-size: var(--cs-bar-title-size);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cs-bar-logo,
.cs-bar-title {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--head-slide-duration) var(--head-slide-ease), visibility 0s linear var(--head-slide-duration);
}
.cs-bar.is-full .cs-bar-logo,
.cs-bar.is-full .cs-bar-title {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--head-slide-duration) var(--head-slide-ease), visibility 0s;
}

/* Page head: the title with the toggle top right; 64px below, the summary and four facts in halves */
.case-mast {
  display: flex;
  flex-direction: column;
  gap: var(--cs-mast-gap);
}
.cs-mast-row {
  display: flex;
  flex-direction: var(--cs-mast-direction);
  align-items: flex-start;
  gap: var(--cs-gap);
}
.case-title {
  flex: 1 1 0%;
  margin: 0;
  color: var(--cs-text);
}
.cs-mast-info {
  display: grid;
  grid-template-columns: var(--cs-mast-columns);
  gap: var(--cs-gap);
}
.case-summary { margin: 0; }
.case-meta {
  display: grid;
  grid-template-columns: var(--cs-meta-columns);
  gap: var(--cs-gap);
  margin: 0;
}
.cs-fact { margin: 0; }
.cs-fact-label {
  margin: 0;
  color: var(--cs-muted);
}
.cs-fact-value { margin: 0; }

/* Reading mode: a pill track with two buttons, The TL;DR and The weeds, TL;DR pressed by default.
   site.js keeps aria-pressed and is-on in step and marks the page is-tldr, which hides every is-detailed
   block. The pressed fill is the thumb behind the buttons, which site.js sizes to the pressed button and
   slides across on a press (Alex, Oct 25). Under it, the note says what the press changed, then leaves. */
.mode-toggle {
  position: relative;
  display: flex;
  flex: none;
  box-sizing: border-box;
  height: var(--cs-toggle-height);
  padding: var(--cs-toggle-pad);
  border-radius: var(--cs-pill);
  background-color: var(--cs-surface);
  pointer-events: auto;
}
.mode-btn {
  box-sizing: border-box;
  height: var(--cs-button-height);
  margin: 0;
  padding: var(--space-8) var(--cs-button-pad-x);
  border-style: none;
  border-color: currentColor;  /* not the browser's button border, which dark mode turns white */
  border-radius: var(--cs-pill);
  background-color: transparent;
  color: var(--cs-text);
  font-family: inherit;
  cursor: pointer;
  position: relative;  /* over the thumb */
}
.mode-thumb {
  position: absolute;
  top: var(--cs-toggle-pad);
  left: 0;
  height: var(--cs-button-height);
  border-radius: var(--cs-pill);
  background-color: var(--cs-pressed);
  pointer-events: none;
  transition: transform var(--motion-duration-exit) var(--motion-ease), width var(--motion-duration-exit) var(--motion-ease);
}
.mode-note {
  position: absolute;
  top: calc(100% + var(--space-8));
  right: 0;
  margin: 0;
  padding: var(--space-8) var(--cs-button-pad-x);
  border-radius: var(--cs-pill);
  background-color: var(--cs-surface);
  color: var(--cs-text);
  white-space: nowrap;
  visibility: hidden;
  pointer-events: none;
}
.mode-note.is-shown { visibility: visible; }
.mode-note.is-start { right: auto; left: 0; }

/* Timeline section: two equal halves, 64px padding top and bottom (is-spaced; 32px on the v3 and v5
   drafts). The side column holds the date and headline at the top and the captions for the panels
   above, pushed to its bottom. */
.story {
  display: grid;
  grid-template-columns: var(--cs-halves);
  gap: var(--cs-col-gap);
  padding: var(--cs-gap) 0;
}
.story.is-spaced { padding: var(--cs-story-pad) 0; }  /* the final page: twice the room between text and graphics */
.story-side {
  display: flex;
  flex-direction: column;
  gap: var(--cs-side-gap);
}
.story-top { max-width: var(--cs-side-max); }
.story-date {
  margin: 0;
  color: var(--cs-muted);
}
.story-head {
  margin: 0;
  color: var(--cs-text);
}
.story-captions {
  margin-top: var(--cs-caption-top);
  max-width: var(--cs-side-max);
}
.story-captions.is-intro { max-width: var(--cs-intro-caption-max); }
.story-caption { margin: 0; }
.story-caption.is-next { margin-top: var(--cs-caption-gap); }
.caption-where {
  margin: 0;
  color: var(--cs-muted);
}
.caption-text {
  margin: 0;
  color: var(--cs-text);
}
.cs-stats {
  display: flex;
  flex-wrap: var(--cs-stats-wrap);
  gap: var(--cs-gap);
  margin: 0;
}
.cs-stat { flex: 0 1 auto; min-width: 0; }
.cs-stat-value {
  margin: 0;
  white-space: nowrap;
}
.cs-stat-label { margin: 0; }
.story-body { min-width: 0; }
.story-lead {
  margin: 0;
  color: var(--cs-text);
}
.story-lead.is-continued { margin-top: var(--cs-lead-gap); }
.story-more { margin-top: var(--cs-gap); }
.story-text {
  margin: 0;
  color: var(--cs-text);
}
.story-text.is-continued { margin-top: var(--cs-body-gap); }
.story-sublabel { color: var(--cs-muted); }
.story-close { margin-top: var(--cs-gap); }
/* Intro footnotes (Alex, 2026-10-23): the abbreviations the page leans on, at caption size under the
   intro in both modes, numbered in order of first mention; each number and term muted in its own narrow
   column with the definition beside it. The first mention in the text carries the number, small and
   muted, raised without opening up the line. */
.cs-terms {
  display: grid;
  grid-template-columns: var(--cs-term-w) minmax(0, 1fr);
  gap: var(--space-4) var(--space-24);
  margin: var(--cs-gap) 0 0;
}
.cs-term-name {
  margin: 0;
  color: var(--cs-muted);
}
.cs-term-text {
  margin: 0;
  color: var(--cs-text);
}
.cs-fn-num {
  display: inline-block;
  min-width: var(--cs-fn-num-w);
}
.cs-fn-ref {
  color: var(--cs-muted);
  font-size: var(--cs-fn-ref-size);
  line-height: 0;
  vertical-align: super;
}
.story-links {
  margin: var(--cs-gap) 0 0;
  padding: 0;
  list-style-type: none;
}
.story-link-item { margin: 0; }
.story-link-item.is-continued { margin-top: var(--cs-body-gap); }
/* display: inline because the site's tag styles make every link a block */
.cs-link {
  display: inline;
  color: var(--cs-text);
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: var(--link-underline-offset);
}
.cs-link:hover { color: var(--color-accent); }

/* Panels: full width, square, no border. Their ground is a lichen tint over the page's fixed static
   (specs §6, Oct 22), so the grain runs on across the panel's edges. overflow: clip keeps the growth
   stage's sticky working and lets a panel grow when its content needs more height. */
.cs-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: clip;
  margin: 0;
  background-color: var(--cs-panel-tint);
}
.cs-panel.is-device {
  align-items: center;
  padding: var(--cs-panel-pad);
}
.cs-panel.is-coa {
  justify-content: flex-start;
  align-items: center;
  padding: var(--cs-coa-top) var(--cs-coa-side);
}
.cs-pair {
  display: grid;
  grid-template-columns: var(--cs-pair-columns);
  gap: var(--cs-pair-gap);
  aspect-ratio: var(--cs-aspect-pair);
}
.cs-pair.is-joined { margin-top: var(--cs-pair-join); }  /* right under another panel: the pair gap above it too */
.cs-panel.is-strip {
  aspect-ratio: var(--cs-aspect-strip-half);
  padding: var(--cs-strip-pad);
}
.cs-panel.is-half {
  align-items: center;
  padding: var(--cs-half-pad);
  container-type: var(--cs-half-container);
}
.cs-panel.is-diagram {
  aspect-ratio: var(--cs-aspect-device);
  padding: var(--cs-panel-pad);
}
.cs-panel.is-paper { aspect-ratio: var(--cs-aspect-paper); }
/* The fitted diagrams take their content's height, never more than a screen (diagram-fit brief) */
.cs-panel.is-tree,
.cs-panel.is-flow { aspect-ratio: auto; }

/* Devices (specs §5a, §5b; image-crispness brief): the bare screen inside the coded frame (.cs-frame).
   The device's width is capped so the whole screen fits below the sticky header (cs-fit-h × its own
   ratio), rounded down to an even pixel so it sits on whole pixels and centres without a half pixel.
   It drifts with the parallax, frame and shadow with it; the panel doesn't. On phones it's the whole
   screen at the panel's width, the frame scaled down, with an enlarge badge. */
.cs-device {
  --device-ratio: var(--cs-ratio-wide);
  display: block;
  width: min(100%, calc(var(--cs-fit-h) * var(--device-ratio)));
  width: round(down, min(100%, calc(var(--cs-fit-h) * var(--device-ratio))), 2px);
  aspect-ratio: var(--device-ratio);
  cursor: zoom-in;
}
.cs-device.is-assessments { --device-ratio: var(--cs-ratio-assessments); }
.cs-device.is-participants { --device-ratio: var(--cs-ratio-participants); }
.cs-device.is-coa {
  --device-ratio: var(--cs-ratio-coa);
  width: min(var(--cs-coa-width), calc(var(--cs-fit-h) * var(--device-ratio)));
  width: round(down, min(var(--cs-coa-width), calc(var(--cs-fit-h) * var(--device-ratio))), 2px);
}
.cs-device.is-half { /* the half's own width or height, whichever is tighter (on phones it isn't a container: 100% wins) */
  width: min(100%, 100cqw, calc(100cqh * var(--device-ratio)));
  width: round(down, min(100%, 100cqw, calc(100cqh * var(--device-ratio))), 2px);
}
.cs-frame.is-coa { border-radius: var(--cs-device-radius-coa); }
.cs-frame.cs-strip-frame { /* the strip overrides the shared frame: pinned bottom right, only the top-left corner rounded */
  position: var(--cs-strip-position);
  right: 0;
  bottom: 0;
  width: var(--cs-strip-width);
  height: var(--cs-strip-height);
  border-right-width: var(--cs-strip-edge);
  border-bottom-width: var(--cs-strip-edge);
  border-radius: var(--cs-strip-radius);
  cursor: zoom-in;
}
.cs-frame-img.is-strip {
  width: var(--cs-strip-screen);
  max-width: none;
  height: auto;
}

/* Recording ring (specs §5a): the session is recording. Drawn just inside the frame's edge so it
   follows the corners, 3px at the screen's own width and scaling with the device. */
.cs-device-ring {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: calc(var(--cs-device-radius-coa) - var(--hairline));
  box-shadow: inset 0 0 0 var(--cs-ring-width) var(--cs-recording);
  pointer-events: none;
}

/* Enlarge badge (phones): says the device opens full size; the whole device is the link */
.cs-enlarge {
  position: absolute;
  right: var(--cs-enlarge-inset);
  bottom: var(--cs-enlarge-inset);
  z-index: 3;
  display: var(--cs-enlarge-display);
  align-items: center;
  justify-content: center;
  width: var(--cs-enlarge-size);
  height: var(--cs-enlarge-size);
  border: var(--hairline) solid var(--cs-border);
  border-radius: 50%;
  background-color: var(--cs-enlarge-fill);
  color: var(--cs-text);
}
.cs-enlarge-icon {
  width: var(--space-16);
  height: var(--space-16);
  fill: currentColor;
}

/* Image rows: the December mid-fi images and the recreated slider and comparison, on tinted panels
   (the slider sits on the page). On phones they stack, each image full width. */
.cs-row {
  display: grid;
  align-items: center;
  margin: 0;
}
.cs-row.is-midfi {
  grid-template-columns: var(--cs-midfi-columns);
  gap: var(--cs-row-gap);
  aspect-ratio: var(--cs-aspect-midfi);
}
.cs-row.is-slider {
  grid-template-columns: var(--cs-slider-columns);
  aspect-ratio: var(--cs-aspect-slider);
}
.cs-flat {
  position: relative;
  margin: 0;
  background-color: var(--cs-panel-tint);
}
.cs-flat.is-cards { padding: var(--cs-midfi-left-pad) 0; }
.cs-flat.is-clip {
  align-self: stretch;
  overflow: clip;
  padding: var(--cs-flat-pad);
}
.cs-plain {
  margin: 0;
  padding: var(--cs-slider-left-pad) 0;
}
/* The images: each is exported as exactly the part its panel shows. The cards and the slider fill their
   width; the participant table and the comparison sit at their panel's bottom right, running off its
   right and bottom edges as in Figma. A tap opens the full image in the viewer. */
.cs-shot-link {
  display: block;
  cursor: zoom-in;
}
.cs-shot {
  display: block;
  width: 100%;
  height: auto;
}
.cs-shot-link.is-clipped {
  position: var(--cs-shot-position);
  right: 0;
  bottom: 0;
}
.cs-shot-link.is-participants { width: var(--cs-midfi-shot-width); }
.cs-shot-link.is-comparison { width: var(--cs-slider-shot-width); }

/* Image viewer: the full image at its true size, pannable; Esc, the close button or a tap outside it
   closes it. A native modal dialog holds focus inside. */
.viewer {
  width: 100vw;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border-style: none;
  background-color: var(--color-paper);
  color: var(--cs-text);
  overflow: auto;
}
.viewer-bar {
  position: sticky;
  top: 0;
  left: 0;
  z-index: 1;
  display: flex;
  justify-content: flex-end;
  padding: var(--space-16) var(--grid-margin);
  background-color: var(--color-paper);
}
.viewer-close {
  margin: 0;
  padding: 0;
  border-style: none;
  border-color: currentColor;  /* not the browser's button border, which dark mode turns white */
  background-color: transparent;
  color: var(--cs-text);
  font-family: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: var(--link-underline-offset);
  cursor: pointer;
}
.viewer-close:hover { color: var(--color-accent); }
.viewer-img {
  display: block;
  width: var(--viewer-image-width);
  max-width: none;
  height: auto;
  margin: 0 auto var(--grid-margin);
}

/* ---- Willis eCOA diagrams (in Webflow, HTML embeds carrying these rules) ----
   Built by scripts/build_diagrams.py from tokens, in Söhne at 16px (the page's body size). Text never
   scales: the wide forms show from 1440px, and below that each turns into its compact form. The
   permission matrix and paper table follow the Figma design; the trees, flow and growth map take its
   type and colors. Cut, deferred and tossed items are in cut. */
.diagram-panel {
  position: relative;
  z-index: 1;
  width: 100%;
  margin: 0;
  padding: 0;
}

/* Permission matrix: header row, then each group's heading on the panel and its rows in a framed table */
.perm-switch { display: var(--perm-switch-display); flex-wrap: wrap; gap: var(--space-8); margin: 0 0 var(--space-16); }
.perm-tab {
  margin: 0;
  padding: var(--space-8) var(--cs-button-pad-x);
  border-style: none;
  border-color: currentColor;  /* not the browser's button border, which dark mode turns white */
  border-radius: var(--cs-pill);
  background-color: var(--cs-surface);
  color: var(--cs-text);
  font-family: inherit;
  cursor: pointer;
}
.perm-tab.is-on { background-color: var(--cs-pressed); }
.perm-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.perm-h {
  padding: var(--diagram-pad);
  border-bottom: var(--hairline) solid var(--cs-border);
  color: var(--cs-text);
  font-weight: inherit;
  text-align: center;
}
.perm-h.is-value { width: var(--perm-value-width); }
.perm-h.is-persona { width: var(--perm-persona-width); text-align: left; }
.perm-c { display: var(--perm-col-display); }
.perm-table.is-scores .c-scores,
.perm-table.is-phi .c-phi,
.perm-table.is-sites .c-sites,
.perm-table.is-staff .c-staff,
.perm-table.is-certs .c-certs { display: table-cell; }
.perm-gh { padding: var(--cs-perm-pad-y) var(--diagram-pad); font-weight: inherit; text-align: left; }
.perm-gname { display: block; color: var(--cs-text); }
.perm-gnote { display: block; color: var(--cs-muted); }
.perm-rows { border: var(--hairline) solid var(--cs-border); }
.perm-row { background-color: var(--cs-cell); }
.perm-persona,
.perm-cell {
  height: var(--cs-perm-row);
  padding: var(--cs-perm-pad-y) var(--diagram-pad);
  border-bottom: var(--hairline) solid var(--cs-border);
  font-weight: inherit;
  vertical-align: middle;
}
.perm-row.is-last .perm-persona,
.perm-row.is-last .perm-cell { border-bottom-style: none; }
.perm-persona { text-align: left; }
.perm-cell { color: var(--cs-text); text-align: center; }
.perm-name { display: var(--perm-name-display); margin-right: var(--space-8); color: var(--cs-text); }
.perm-priority { display: var(--perm-priority-display); color: var(--cs-muted); }
.perm-row.is-open .perm-priority { display: block; }
.perm-more {
  display: var(--perm-more-display);
  margin: 0;
  padding: 0;
  border-style: none;
  border-color: currentColor;  /* not the browser's button border, which dark mode turns white */
  background-color: transparent;
  color: var(--cs-muted);
  font-family: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: var(--link-underline-offset);
  cursor: pointer;
}
.perm-dot {
  display: inline-block;
  width: var(--cs-dot);
  height: var(--cs-dot);
  border-radius: 50%;
  background-color: var(--cs-text);
  vertical-align: middle;
}
.perm-none { color: var(--cs-muted); opacity: var(--cs-dash-opacity); }
.perm-chip {
  display: inline-block;
  padding: 0 var(--space-8);
  border-radius: var(--cs-pill);
  background-color: var(--cs-chip);
  color: var(--cs-text);
}

/* Paper to platform: a three-line head, column heads, then a framed table: at site, arrow, in Willis */
.paper-head { margin-bottom: var(--space-16); }
.paper-eyebrow { margin: 0; color: var(--cs-muted); }
.paper-name { margin: 0; color: var(--cs-text); }
.paper-lede { margin: 0; color: var(--cs-text); }
.paper-cols {
  display: var(--paper-cols-display);
  grid-template-columns: var(--paper-row-columns);
  align-items: center;
  height: var(--cs-paper-head-row);
  color: var(--cs-text);
}
.paper-col { padding: 0 var(--diagram-pad); }
.paper-col.is-to { grid-column: 3; }
.paper-rows {
  margin: 0;
  padding: 0;
  list-style-type: none;
  border: var(--hairline) solid var(--cs-border);
  background-color: var(--cs-cell);
}
.paper-row {
  display: grid;
  grid-template-columns: var(--paper-row-columns);
  align-items: center;
  min-height: var(--cs-paper-row);
  border-bottom: var(--hairline) solid var(--cs-border-soft);
  box-sizing: border-box;
}
.paper-row.is-last { border-bottom-style: none; }
.paper-from,
.paper-to { display: flex; flex-direction: column; padding: var(--diagram-pad); }
.paper-to { padding-top: var(--cs-paper-to-top); }
.paper-where { display: var(--paper-where-display); margin-bottom: var(--diagram-stack); color: var(--cs-muted); }
.paper-title { color: var(--cs-text); }
.paper-sub { color: var(--cs-muted); }
.paper-arrow {
  display: var(--arrow-wide-display);
  justify-self: center;
  width: var(--cs-arrow);
  height: var(--cs-arrow);
  fill: var(--cs-text);
}
.paper-arrow-tall { display: var(--arrow-tall-display); }

/* Trees: the January scoping map and the growth map, laid out compactly once and scaled to fit the
   screen below the sticky header (viewBox and meet), so the whole picture is always in view. Labels
   never render under 11px: site.js measures the fitted scale and, below it, shows the outline
   (the figure gets is-compact). Outlines also show below 1280px wide or 700px tall. */
.tree-wide {
  display: var(--wide-display);
  width: 100%;
  max-height: min(var(--natural-h), calc(100svh - var(--cs-header-h) - 2 * var(--cs-panel-pad)));
  margin: 0 auto;
}
.tree-tall,
.g-tall { display: var(--tall-display); }
.tree-svg,
.growth-svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.node-box { fill: var(--cs-cell); stroke: var(--cs-border); stroke-width: var(--hairline); }
.node-text {
  fill: var(--cs-text);
  font-family: inherit;
  font-size: var(--cs-diagram-text-size);
  text-anchor: middle;
  dominant-baseline: central;
}
.node.is-root .node-box,
.g-node.is-root .node-box { fill: var(--cs-text); stroke: var(--cs-text); }
.node.is-root .node-text,
.g-node.is-root .node-text { fill: var(--color-paper); }
.edge,
.g-edge { fill: none; stroke: var(--color-ink-soft); stroke-width: var(--hairline); }
.node.st-m .node-box { stroke: var(--cs-text); stroke-dasharray: var(--dash-mid-fi); }
.node.st-p .node-box { stroke: var(--color-ink-soft); stroke-dasharray: var(--dash-slated); }
.node.st-p .node-text { fill: var(--cs-muted); }
.edge.st-p { stroke-dasharray: var(--dash-slated); }
.node.st-d .node-box { stroke: var(--color-cut); stroke-dasharray: var(--dash-mid-fi); }
.node.st-d .node-text { fill: var(--color-cut); }
.node-strike { stroke: var(--color-cut); stroke-width: var(--hairline); }
.edge.st-d { stroke: var(--color-cut); stroke-dasharray: var(--dash-mid-fi); }
.node-tag { fill: var(--cs-muted); font-family: inherit; font-size: var(--cs-diagram-text-size); dominant-baseline: central; }
.node-tag.is-cut { fill: var(--color-cut); }

/* Outlines (the trees below 1440px): indented, branches collapsible, first level open */
.out-list { margin: 0; padding: 0 0 0 var(--space-16); list-style-type: none; border-left: var(--hairline) solid var(--cs-border); }
.out-list.is-top { padding-left: 0; border-left-style: none; }
.out-item { margin: var(--space-8) 0 0; color: var(--cs-text); }
.out-summary { cursor: pointer; }
.out-item.st-p > .out-label,
.out-item.st-p > .out-branch > .out-summary > .out-label { color: var(--cs-muted); }
.out-item.st-d > .out-label,
.out-item.st-d > .out-branch > .out-summary > .out-label { color: var(--color-cut); text-decoration-line: line-through; }
.out-tag { color: var(--cs-muted); font-size: var(--cs-type-body-size); }
.out-tag.is-cut { color: var(--color-cut); }

/* Invite to first sign-in: laid out at true size, then scaled as a whole to fit the screen below the
   sticky header (site.js sets --flow-scale and the box's size). Titles never under 12px: below that
   the vertical form shows. */
.flow-scroll { display: var(--wide-display); }
.flow-fit {
  width: var(--flow-fit-w, auto);
  height: var(--flow-fit-h, auto);
  margin: 0 auto;
}
.flow-fit .flow-wide {
  transform: scale(var(--flow-scale, 1));
  transform-origin: 0 0;
}
.flow-compact { display: var(--tall-display); }
.flow-wide { position: relative; }
.flow-lines { position: absolute; top: 0; left: 0; overflow: visible; }
.flow-line { fill: none; stroke: var(--color-ink-soft); stroke-width: var(--hairline); }
.flow-line.is-tossed { stroke: var(--color-cut); stroke-dasharray: var(--dash-tossed); }
.flow-head { fill: var(--color-ink-soft); }
.flow-head.is-tossed { fill: var(--color-cut); }
.flow-step {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: var(--diagram-stack);
  padding: var(--diagram-pad);
  border: var(--hairline) solid var(--cs-border);
  background-color: var(--cs-cell);
  box-sizing: border-box;
}
.flow-who,
.flow-note { color: var(--cs-muted); }
.flow-title { color: var(--cs-text); }
.flow-step.is-key { border-color: var(--cs-text); background-color: var(--cs-text); }
.flow-step.is-key .flow-who,
.flow-step.is-key .flow-title,
.flow-step.is-key .flow-note { color: var(--color-paper); }
.flow-step.is-done { border-color: var(--cs-text); }
.flow-step.is-tossed { border: var(--hairline) dashed var(--color-cut); background-color: transparent; }
.flow-step.is-tossed .flow-who,
.flow-step.is-tossed .flow-title,
.flow-step.is-tossed .flow-note { color: var(--color-cut); }
.flow-step.is-tossed .flow-title { text-decoration-line: line-through; }
.flow-step.is-tabled { border: var(--hairline) dashed var(--color-ink-soft); background-color: transparent; }
.flow-step.is-tabled .flow-title { color: var(--cs-muted); }
.flow-lane { position: absolute; color: var(--color-cut); }
.flow-line.is-tabled-line { stroke-dasharray: var(--dash-tabled); }
.flow-loop { position: absolute; width: var(--flow-loop-w); color: var(--cs-muted); text-align: center; }
.flow-tall,
.flow-branch,
.flow-strip-list { margin: 0; padding: 0; list-style-type: none; }
.flow-tall .flow-step,
.flow-tossed-strip .flow-step,
.flow-pin-tall .flow-step { position: static; }
.flow-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-8); }
.flow-down { display: block; padding: var(--diagram-stack) 0; color: var(--cs-muted); text-align: center; }
.flow-tossed-strip { margin-top: var(--space-24); }
.flow-lane-tall { margin: 0 0 var(--space-8); color: var(--color-cut); }
.flow-strip-list { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-8); }
.flow-pin-tall { margin-top: var(--space-24); }
/* April visit flow, compact: the scale's two paths stacked inside a dashed "For each scale" group */
.flow-each { padding: var(--space-16); border: var(--hairline) dashed var(--cs-border); }
.flow-each-label { margin: 0 0 var(--space-8); color: var(--cs-muted); }
.flow-each-label.is-end { margin: var(--space-16) 0 0; }
.flow-path-label { margin: var(--space-16) 0 var(--space-8); color: var(--cs-text); }
.flow-path-label.is-first { margin-top: 0; }

/* Product growth: a pinned stage exactly one screen tall below the sticky header. The map (fitted,
   meet) on the left; the users and the release rail stacked in a column on the right. The track is one
   half-screen of scroll per release: the stage pins as its top meets the header, each half screen of
   scroll shows the next release, and it unpins after the last has had its full step. Reduced motion:
   no pinning, the final release, the rail as plain buttons. The ambient ground is on the stage only. */
.g-scroll {
  display: var(--wide-display);
  height: calc(var(--releases) * var(--g-step) + 100svh - var(--cs-header-h)); /* --releases is set on the figure */
}
.g-stage {
  position: var(--g-stage-position);
  top: var(--cs-header-h);
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--g-side-w);
  gap: var(--cs-gap);
  height: calc(100svh - var(--cs-header-h));
  padding: var(--g-stage-pad);
  overflow: clip;
  background-color: var(--cs-panel-tint);
}
.g-map {
  position: relative;
  z-index: 1;
  min-width: 0;
  min-height: 0;
}
.g-side {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--cs-gap);
  min-width: 0;
}
.g-users-label { margin: 0 0 var(--space-8); color: var(--cs-muted); }
.g-chips { display: flex; flex-wrap: wrap; gap: var(--space-8); margin: 0; padding: 0; list-style-type: none; }
.g-user { padding: 0 var(--space-8); border: var(--hairline) solid var(--cs-border); color: var(--cs-text); }
.g-user.is-off { visibility: hidden; } /* keeps its place, so nothing in the side column moves between releases */
.g-user { transition: background-color var(--motion-duration) var(--motion-ease), border-color var(--motion-duration) var(--motion-ease); }
.g-user.is-new { border-color: var(--cs-new-stroke); background-color: var(--cs-new-fill); outline: calc(var(--stroke-new) - var(--hairline)) solid var(--cs-new-stroke); }
.g-rail { display: flex; flex-direction: column; align-items: flex-start; gap: 0; margin: 0; padding: 0; list-style-type: none; }
.g-step {
  margin: 0;
  padding: var(--space-4) var(--space-8);
  border: var(--hairline) solid transparent;
  background-color: transparent;
  color: var(--cs-muted);
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.g-step.is-past { color: var(--cs-text); }
.g-step.is-on { border-color: var(--cs-text); color: var(--cs-text); }
.g-tall {
  padding: var(--cs-panel-pad);
  background-color: var(--cs-panel-tint);
}
.g-tall .g-users { margin-bottom: var(--space-16); }
.g-stepper { display: flex; align-items: center; justify-content: space-between; gap: var(--space-16); margin-bottom: var(--space-16); }
.g-prev,
.g-next {
  margin: 0;
  padding: var(--space-8) var(--space-16);
  border: var(--hairline) solid var(--cs-border);
  background-color: var(--cs-cell);
  color: var(--cs-text);
  font-family: inherit;
  cursor: pointer;
}
.g-prev:disabled,
.g-next:disabled { color: var(--cs-muted); cursor: default; }
.g-current { margin: 0; text-align: center; }
.g-node { transition: opacity var(--motion-duration) var(--motion-ease); }
.g-edge { transition: opacity var(--motion-duration) var(--motion-ease), stroke var(--motion-duration) var(--motion-ease); }
.g-node .node-box { transition: fill var(--motion-duration) var(--motion-ease), stroke var(--motion-duration) var(--motion-ease); }
.g-node.is-off,
.g-edge.is-off { opacity: 0; }
.g-node.is-new .node-box { fill: var(--cs-new-fill); stroke: var(--cs-new-stroke); stroke-width: var(--stroke-new); }
.g-edge.is-new { stroke: var(--cs-new-stroke); stroke-width: var(--stroke-new); }
.g-tall .out-item.is-off { display: none; }
.g-tall .out-item.is-new > .out-label,
.g-tall .out-item.is-new > .out-branch > .out-summary > .out-label { background-color: var(--cs-new-fill); }

/* A figure whose fitted labels would go under 11px (or titles under 12px) shows its compact form */
.diagram-panel.is-compact .tree-wide,
.diagram-panel.is-compact .g-scroll,
.diagram-panel.is-compact .flow-scroll { display: none; }
.diagram-panel.is-compact .tree-tall,
.diagram-panel.is-compact .g-tall,
.diagram-panel.is-compact .flow-compact { display: block; }

/* The January tree grows in once, column by column, when its panel is mostly in view (site.js adds
   grow-ready, then is-grown); its deferred areas are struck last. Without JavaScript, or under
   reduced motion, it's simply there. */
.tree.grow-ready .node,
.tree.grow-ready .edge { opacity: 0; }
.tree.grow-ready.is-grown .node,
.tree.grow-ready.is-grown .edge {
  opacity: 1;
  transition: opacity var(--motion-duration) var(--motion-ease) calc(var(--col) * var(--motion-stagger));
}
.tree.grow-ready .node-strike { opacity: 0; }
.tree.grow-ready.is-grown .node-strike {
  opacity: 1;
  transition: opacity var(--motion-duration-exit) var(--motion-ease) calc(9 * var(--motion-stagger));
}

/* List items in the diagrams carry their own margin, so Webflow's site-wide li margin never reaches them */
.paper-row,
.flow-item,
.g-user,
.g-step-item { margin: 0; }

/* Code layer (Webflow page code): TL;DR mode hides the detailed blocks */
.case-page.is-tldr .is-detailed { display: none; }
/* Code layer: the device frame's inner shadow, over the screen */
.cs-frame::after {
  content: "";
  position: absolute;
  inset: calc(var(--hairline) * -1);
  border-radius: inherit;
  z-index: 2;
  box-shadow: var(--cs-device-inset);
  pointer-events: none;
}
/* Code layer: the home stage, pinned (site.js sets data-mode). The frame sticks; every state takes
   the whole frame and only the shown one is visible. The page scrolls natively and snaps to each
   state, so a fast flick can't skip one; while pinned, no scrollbar eats into the right margin. */
.home[data-mode="pinned"] {
  position: relative;
  height: calc((var(--states) + var(--state-lead)) * var(--state-scroll));
}
.home[data-mode="pinned"] .home-frame {
  position: sticky;
  top: var(--frame-margin);
  grid-template-rows: var(--frame-rows);
  height: var(--frame-h);
}
.home[data-mode="pinned"] .home-state { grid-row: 1 / -1; }
.home[data-mode="pinned"] .home-state:not(.is-shown) { visibility: hidden; }
html:has(.home[data-mode="pinned"]) {
  scrollbar-width: none;
  scroll-snap-type: y mandatory;
}
html:has(.home[data-mode="pinned"])::-webkit-scrollbar { display: none; }
.home-mark {
  position: absolute;
  top: calc(var(--i) * var(--state-scroll));
  height: var(--state-scroll);
  scroll-snap-align: start;
  scroll-snap-stop: always;
  pointer-events: none;
}
/* Code layer: stacked, the run of paper after Projects that leads into the project (site.js adds it) */
.home-next {
  display: block;
  height: var(--home-next-run);
}

/* Code layer: the toggle stays hidden until site.js can make it work */
.mode-toggle[hidden] { display: none; }

.case-lead {
  margin: var(--space-16) 0 0;
  max-width: var(--measure);
}

.case-closing { margin: 0; }
.plate-frame.is-hero { grid-column: var(--place-case-hero); }

/* Decision block: heading, one visual, a caption saying what changed, then the decision
   notes as details-table rows. The centerpiece gets more room, nothing louder. */
.decision.is-centerpiece { padding-block: var(--centerpiece-room); }

/* Before-and-after strip: two labeled frames side by side (stacked on phones), with an
   optional large stat above */
.strip-stat { margin-bottom: var(--space-24); }

.strip {
  display: grid;
  grid-template-columns: var(--strip-columns);
  gap: var(--space-24) var(--grid-gutter);
}

.strip-label { margin-bottom: var(--space-8); }

/* Image pair: two plates in a strip, each its own figure with the caption beneath
   (overrides any site-wide figure and figcaption styles) */
.pair-item {
  margin: 0;
  padding: 0;
}

.pair-caption {
  margin: var(--space-caption) 0 0;
  max-width: var(--measure);
  text-align: left;
  opacity: 1;
  color: var(--color-ink-label);
}

/* Overlap diagram: two groups drawn as frames that step apart and overlap; names sit in the
   three regions (first group only, both, second group only). Side by side, or stacked when
   --overlap-vertical is 1. */
.overlap {
  --v: var(--overlap-vertical);
  position: relative;
  height: calc((1 - var(--v)) * var(--overlap-height) + var(--v) * var(--overlap-stack-height));
}

.overlap-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.ov-set {
  fill: none;
  stroke: var(--color-ink);
  stroke-width: var(--diagram-stroke);
  width: calc((1 - var(--v)) * var(--overlap-share) + var(--v) * (100% - var(--overlap-offset)));
  height: calc((1 - var(--v)) * (100% - var(--overlap-offset)) + var(--v) * var(--overlap-share));
}

.ov-set.is-first { x: 0; y: 0; }

.ov-set.is-second {
  x: calc((1 - var(--v)) * (100% - var(--overlap-share)) + var(--v) * var(--overlap-offset));
  y: calc((1 - var(--v)) * var(--overlap-offset) + var(--v) * (100% - var(--overlap-share)));
}

.overlap-regions {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: var(--overlap-columns);
  grid-template-rows: var(--overlap-rows);
}

.ov-region { padding: var(--space-16); }
.ov-region.is-first { align-self: start; }
.ov-region.is-both { align-self: center; justify-self: center; }

.ov-region.is-second {
  align-self: end;
  justify-self: var(--overlap-last-justify);
}

.ov-group { color: var(--color-ink-label); }

/* Timeline with one lane over a span of years: shorter months when it runs top to bottom */
.timeline.is-single {
  --timeline-head: var(--space-24);
  --timeline-month: var(--timeline-month-long);
}

.timeline.is-single .is-actual { --lane: var(--timeline-lane-planned); }
.timeline.is-single .tl-label.is-actual { --lane-room: calc(100% - var(--timeline-lane-planned) - var(--timeline-tick) - var(--timeline-gap)); }

/* Footnote: the reference in the text, the note, and the way back */
.fn-ref {
  color: inherit;
  text-decoration-line: none;
}

.fn-ref:hover { color: var(--color-accent); }

.footnote {
  margin-top: var(--space-24);
  max-width: var(--measure);
  color: var(--color-ink-label);
}

.fn-back {
  margin-left: var(--space-8);
  text-decoration-line: none;
}

/* Up/next: where this page sits, and where to go after it */
.page-nav {
  margin-top: var(--space-section);
  padding-bottom: var(--space-96);
}

.page-nav-item {
  align-items: baseline;
  margin-top: var(--space-24);
}

.page-nav-link {
  margin: 0;
  color: var(--color-ink);
}

.svg-defs {
  position: absolute;
  width: 0;
  height: 0;
}

/* Underlined text link (up/next, credits, specimen) */
.text-link {
  display: inline;
  color: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: var(--link-underline-offset);
}

.text-link:hover { color: var(--color-accent); }

/* ---- Landing (About) and Projects (DESIGN.md §8, Figma 2026-10-22) ----
   One frame, 12 columns by 24 rows inside the margins, that keeps the 1680 × 1069 design's shape.
   The header's items sit in its grid (.site-head is display: contents). Blocks are placed at their
   Figma row and flow down from there. At 991px and down the page runs as one column of 4. */
.site-frame {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: var(--frame-cols);
  grid-template-rows: var(--frame-rows);
  column-gap: var(--frame-column-gap);
  width: var(--frame-w);
  height: var(--frame-h);
  margin: var(--frame-margin);
  color: var(--cs-text);
}
.t-display {
  font-size: var(--type-display-size);
  line-height: var(--type-display-leading);
  letter-spacing: var(--type-display-tracking);
}

/* Home stage (DESIGN.md §8): About, then each project, as states over one frame. Each state is its
   own 12 × 24 grid the size of the frame, so its blocks sit at the same rows as on their own page; the
   header stays in the frame's grid above them. By default, and without JavaScript, the states stack
   as plain screens (the first shares row 1 with the header). site.js pins the frame and swaps the
   states in place as you scroll (the pinned rules are in the code layer). */
/* The stage holds its frame's margins, so they don't collapse away under Webflow's html height rule */
.home { display: flow-root; }
.home-frame { grid-template-rows: none; height: auto; } /* .site-frame is position: relative, so blocks measure from it */
.home-state {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: var(--frame-cols);
  grid-template-rows: var(--frame-rows);
  column-gap: var(--frame-column-gap);
  height: var(--frame-h);
}
.home-state.is-first { grid-row: var(--home-first-row); }

/* Landing: headline and paragraph, the portrait, three lists */
.l-copy {
  grid-column: var(--place-l-copy);
  grid-row: var(--row-l-copy);
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: var(--l-copy-gap);
  margin-top: var(--l-copy-top);
}
.l-headline {
  max-width: var(--l-headline-measure);
  margin: 0;
}
.l-text {
  margin: 0;
  max-width: var(--l-copy-measure);
}
.l-portrait {
  grid-column: var(--place-l-portrait);
  grid-row: var(--row-l-portrait);
  align-self: start;
  display: block;
  width: var(--l-portrait-width);
  height: auto;
  aspect-ratio: var(--l-portrait-aspect);
  margin-top: var(--l-portrait-top);
  object-fit: cover;
}
.l-list {
  grid-row: var(--row-l-lists);
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: var(--l-list-gap);
  margin-top: var(--l-list-top);
}
.l-list.is-disciplines { grid-column: var(--place-l-disciplines); }
.l-list.is-industries { grid-column: var(--place-l-industries); }
.l-list.is-other { grid-column: var(--place-l-other); }
.l-list-label {
  margin: 0;
  color: var(--cs-muted);
}
.l-list-items {
  margin: 0;
  padding: 0;
  list-style-type: none;
}
.l-list-item { margin: 0; }

/* Projects (landing brief §4, Oct 22): one screen per project. The title (36px); About in a box at
   least four rows tall, so the two fact groups land on row 15 (longer text pushes them down, never over
   it); View project at row 17; the image panel from row 3 to the foot. All small type below the title. */
.t-project {
  font-size: var(--type-project-size);
  line-height: var(--type-project-leading);
  letter-spacing: var(--type-display-tracking);
}
.p-title {
  grid-column: var(--place-p-title);
  grid-row: var(--row-p-title);
  align-self: start;
  margin: var(--p-title-top) 0 0;
}
.p-title-link {
  display: inline;
  color: inherit;
  text-decoration-line: none;
}
.p-body {
  grid-column: var(--place-p-body);
  grid-row: var(--row-p-body);
  order: var(--p-body-order);
  align-self: start;
  display: grid;
  grid-template-columns: var(--p-body-columns);
  align-content: start;
  margin-top: var(--p-body-top);
}
.p-about-label {
  grid-column: 1;
  margin: 0;
  color: var(--cs-muted);
}
.p-about {
  grid-column: var(--place-p-about);
  min-height: var(--p-about-min);
  margin: 0;
  padding-bottom: var(--p-about-pad);
}
.p-facts {
  display: grid;
  grid-template-columns: var(--p-facts-columns);
  align-content: start;
  margin: 0;
}
.p-facts.is-a { grid-column: var(--place-p-facts-a); }
.p-facts.is-b {
  grid-column: var(--place-p-facts-b);
  margin-top: var(--p-facts-b-top);
}
.p-fact-label {
  margin: 0;
  color: var(--cs-muted);
}
.p-fact-value { margin: 0; }
.p-view {
  grid-column: var(--place-p-view);
  justify-self: start;
  margin-top: var(--p-view-top);
  color: var(--cs-text);
  text-decoration-line: none;
}
.p-view:hover { color: var(--color-accent); }
.p-panel {
  grid-column: var(--place-p-panel);
  grid-row: var(--row-p-panel);
  order: var(--p-panel-order);
  position: relative;
  display: grid;
  place-items: center;
  overflow: clip;
  aspect-ratio: var(--p-panel-aspect);
  margin: var(--p-panel-top) calc(var(--p-panel-bleed) * -1) 0;
  padding: var(--p-panel-pad);
  background-color: var(--cs-panel-tint);
  text-decoration-line: none;
  container-type: size; /* the device sizes to the panel's width or height, whichever is tighter */
}
.p-device {
  width: var(--p-device-width);
  height: var(--p-device-height);
  aspect-ratio: var(--p-device-aspect);
}

/* Device frame (Willis eCOA specs §5a): drawn in CSS around the bare screen, never baked into the
   image: the leaf/200 edge, rounded corners that clip the screen, two drop shadows and an inner
   shadow over the screen. Pills and shadows are allowed here only (DESIGN.md §10). */
.cs-frame {
  position: relative;
  z-index: 1;
  overflow: hidden;
  container-type: inline-size;
  border: var(--hairline) solid var(--cs-device-edge);
  border-radius: var(--cs-device-radius);
  box-shadow: var(--cs-device-shadow);
}
.cs-frame.is-coa { border-radius: var(--cs-device-radius-coa); }
.cs-frame-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
