/* Reusable component styles, built up one component at a time. */

/* Placeholder block — design.md: Components > Placeholder block. Remove once real content replaces it. */

.placeholder {
  background: var(--color-bg-placeholder);
  /* Tokenised rather than a literal #000: under global dark the block itself goes near-black, and
     a hardcoded black label vanished into it. */
  color: var(--color-placeholder-text);
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 120px;
}

/* Section divider + eyebrow — design.md: Layout */

.section-divider {
  height: 1px;
  border: 0;
  margin: 0;
  background: var(--color-border);
}

.section-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
  margin-top: var(--space-xl);
}

.section-title {
  font-family: var(--font-sans);
  font-size: var(--text-h2);
  font-weight: 500;
  color: var(--color-text-primary);
}

/* Case study section body */

.case-study-section {
  padding-top: var(--space-2xl);
  scroll-margin-top: var(--space-2xl);
}

.case-study-section__intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  margin-top: var(--space-xl);
}

.case-study-section__blocks {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
  margin-top: var(--space-xl);
}

/* Case study 2 runs one 48px rhythm through 01/ CONTEXT, matching 02/ THE PROBLEM: the gap into
   the first question and every heading/paragraph gap inside a Q&A block all sit at --space-2xl.
   Case study 1 keeps its 32px intervals. */
.case-study-section__blocks--loose {
  margin-top: var(--space-2xl);
}

.case-study-section__blocks--loose .qa-row__text {
  gap: var(--space-2xl);
}

.skip-link {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  font-weight: 500;
  color: var(--color-text-primary);
}

.skip-link:hover {
  text-decoration: underline;
}

/* Two-column Q&A block — design.md: Layout > Two-column split (520px text + visual) */

.qa-row {
  display: flex;
  align-items: center;
  gap: var(--space-column-gap);
}

/* Holds 520px wherever there's room; min-width:0 lets both columns shrink instead of
   overflowing the page once the viewport can't fit 520 + gap + the visual's min-content. */
.qa-row__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  flex: 0 1 520px;
  min-width: 0;
}

/* A Q&A row followed by a full-width element that belongs to it (case study 3: the DLP flow
   diagram under "What's Data Loss Prevention?"). Tighter than the 48px between separate rows. */
.qa-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.qa-row__visual {
  flex: 1;
  min-width: 0;
  max-width: 100%;
}

.qa-row__visual--stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
  min-width: 0;
  max-width: 100%;
}

/* Caps running text at a readable measure. See design.md > Layout > Text measure. */
.measure {
  max-width: var(--measure);
}

.qa-heading {
  font-family: var(--font-sans);
  font-size: var(--text-h3);
  font-weight: 500;
  color: var(--color-text-primary);
}

.qa-body {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

.qa-body strong {
  font-weight: 600;
}

.qa-body ul {
  list-style: disc;
  padding-left: var(--space-lg);
}

/* Box analogy images + captions. Figma spaces the three images 88px apart, which is off the
   scale — distributed evenly across the column instead. */

.boxes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--space-lg);
  row-gap: var(--space-sm);
  align-items: center;
}

.boxes__caption {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* 02/ THE PROBLEM — section stack */

.problem__stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  margin-top: var(--space-xl);
}

/* Case study 2 runs one 48px rhythm through 02/ THE PROBLEM, matching .problem__rows below it,
   so every text chunk in the section is evenly spaced. Case study 1 keeps its 32px stack. */
.problem__stack--loose {
  gap: var(--space-2xl);
}

.problem__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
}

/* Figma constrains this headline to 955px so it breaks across two lines. */
.problem__title {
  max-width: 955px;
}

.problem__subline {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

.problem__subline strong {
  font-weight: 600;
}

/* Onboarding-timing comparison. Figma's icon gaps (19/21/13px) and line gaps (12px) are off
   the scale and inconsistent between columns — normalized to --space-md and --space-sm. */

.compare {
  display: grid;
  /* minmax(0,...) lets tracks shrink below their content's min-width instead of overflowing. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-column-gap);
}

.compare__item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
}

.compare__icon {
  flex-shrink: 0;
}

.compare__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.compare__label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
}

.compare__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

.compare__body ul {
  list-style: disc;
  padding-left: var(--space-lg);
}

/* The concerns / The Challenge */

.concern {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

.concern__lead {
  font-weight: 600;
}

.challenge-lines {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

.challenge-lines strong {
  font-weight: 600;
}

/* Scenario cards — case study 3, 02/ THE PROBLEM. Three banded cards, each setting a scenario against
   its manual consequence, in mono. Figma hand-places the text inside each card (top insets 32/27/26,
   paragraph gaps 48/27/48 — the middle card squeezed to fit its longer copy); normalized to one rhythm
   so all three share it and still land on Figma's 316px card height. */
.scenario-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-lg);
}

.scenario-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-height: 316px;
  padding: var(--space-xl) var(--space-md);
  background: var(--color-bg-band);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-secondary);
  overflow-wrap: break-word;
}

/* After the base rules so the cascade holds: cards stack, and the fixed height is dropped. */
@media (max-width: 767px) {
  .scenario-cards {
    grid-template-columns: 1fr;
  }

  .scenario-card {
    min-height: 0;
  }
}

/* Quote / testimonial block — design.md: Components > Quote/testimonial block */

.quote {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  background: var(--color-bg-band);
  padding: var(--space-2xl) var(--space-lg);
}

.quote__mark {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: var(--text-quote-mark);
  font-weight: 400;
  line-height: 1;
  color: var(--color-text-secondary);
}

.quote__mark--close {
  align-self: flex-end;
}

.quote__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  flex: 1;
  min-width: 0;
  overflow-wrap: break-word;
}

.quote__divider {
  height: 1px;
  border: 0;
  margin: 0;
  background: var(--color-border);
}

/* Several quotes, each with its own pair of marks, set back to back as one continuous band
   (case study 3, 04/ IMPACT). The second panel drops its top padding, so quotes sit --space-2xl
   apart instead of doubling up to 96px. */
.quote-stack > .quote + .quote {
  padding-top: 0;
}

.quote__text {
  font-family: var(--font-mono);
  font-size: var(--text-caption-quote);
  font-style: italic;
  font-weight: 400;
  color: var(--color-text-secondary);
}

.quote__attribution {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  font-style: normal;
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* A quote set at 16px, quote and attribution alike (case study 3, block 04: the THIP quote beside
   CONSTRAINTS). The base stays at --text-caption-quote for block 01's EY quote. */
.quote__text--lg,
.quote__text--lg .quote__attribution {
  font-size: var(--text-label);
}

/* .accent-highlight: a reusable "recolour this text to the case study's own accent" marker,
   requested piecemeal per case study for specific quotes/captions/labels rather than as a
   systematic pass — so it's opt-in per element (applied to the containing <figure>/<div>/<p>/
   <blockquote>) rather than a blanket rule. Scoped per case study via the existing body class
   (.cs--on-demand / .cs--ai-powered / …) so the SAME class name resolves to the right accent on
   whichever page it's used, instead of proliferating hex-named or page-named classes. Dark mode
   only in both cases so far — each one's own accent measured well under AA against the light-mode
   background it would actually sit on (On-Demand: 1.37:1 against the band; AI-Powered: 1.47:1
   against the plain page bg), so dark-only isn't a stylistic choice, it's the only version that's
   actually readable. Descendant-combined selectors win on specificity alone against each class's
   own site-wide color rule, no !important needed. */
[data-theme="dark"] .cs--on-demand .accent-highlight,
[data-theme="dark"] .cs--on-demand .accent-highlight .quote__text,
[data-theme="dark"] .cs--on-demand .accent-highlight .quote__attribution,
[data-theme="dark"] .cs--on-demand .accent-highlight .quote__mark,
[data-theme="dark"] .cs--on-demand .accent-highlight .exploration__label,
[data-theme="dark"] .cs--on-demand .accent-highlight .exploration__notes {
  /* On-Demand's own accent-text colour — 12.77:1 against the dark-mode band (#232220). Corrected
     from an initial #ecc9bd (AI-Powered's accent, given in error for this page). */
  color: #F0E9A0;
}

[data-theme="dark"] .cs--ai-powered .accent-highlight,
[data-theme="dark"] .cs--ai-powered .accent-highlight .split-panel__text,
[data-theme="dark"] .cs--ai-powered .accent-highlight .split-panel__list,
[data-theme="dark"] .cs--ai-powered .accent-highlight .grading__title,
[data-theme="dark"] .cs--ai-powered .accent-highlight .grading__caption {
  /* AI-Powered's own accent-text colour — 11.32:1 against the dark-mode page bg (#1b1a17).
     .grading__wires (the connector SVG) sets its own explicit color, so applying .accent-highlight
     to the whole .grading diagram wrapper (GRADING PROCESS, 03/05 THE PROCESS) recolours the text
     labels only — the arrows keep --color-annotation, unaffected by this. */
  color: #ecc9bd;
}

[data-theme="dark"] .cs--reusable-lists .accent-highlight,
[data-theme="dark"] .cs--reusable-lists .accent-highlight .scenario-card,
[data-theme="dark"] .cs--reusable-lists .accent-highlight .field__label,
[data-theme="dark"] .cs--reusable-lists .accent-highlight .testimonial,
[data-theme="dark"] .cs--reusable-lists .accent-highlight .quote__text,
[data-theme="dark"] .cs--reusable-lists .accent-highlight .quote__attribution,
[data-theme="dark"] .cs--reusable-lists .accent-highlight .quote__mark {
  /* Reusable Lists' own accent-text colour — 7.90:1 against the dark-mode band (#232220). */
  color: #E4AC71;
}

/* 03/ THE PROCESS — intro + index list */

.process__intro {
  display: flex;
  align-items: flex-start;
  gap: var(--space-column-gap);
  margin-top: var(--space-xl);
}

.process__intro-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  flex: 1;
  min-width: 0;
}

.process__lede {
  max-width: 520px;
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

/* Figma stacks these 18px apart; normalized to --space-md, as with the stats band. */
.index-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  flex: 0 1 410px;
  min-width: 0;
}

.index-list__item {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
}

.index-list__rule {
  height: 1px;
  border: 0;
  margin: 0;
  background: var(--color-border);
}

/* Index items that jump to their decision block (case study 3). They keep the item's look and
   underline on hover or keyboard focus, like .skip-link. */
.index-list__link:hover,
.index-list__link:focus-visible {
  text-decoration: underline;
}

/* Clears the numeral from the viewport edge when a block is reached by its anchor, matching
   .case-study-section. */
.decision[id] {
  scroll-margin-top: var(--space-2xl);
}

/* Numbered decision block — design.md: Components > Numbered decision block */

.decision {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* No line-height override here: Figma's numeral box is 96 x 1.3125 = 126px, and design.md
   specifies --leading-normal for all text. Forcing line-height:1 shortened the box by 30px and
   pulled every field in the block up with it. */
.decision__numeral {
  font-family: var(--font-mono);
  font-size: var(--text-numeral);
  font-weight: 500;
  color: var(--color-text-secondary);
}

.decision__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.decision__heading {
  font-family: var(--font-sans);
  font-size: var(--text-h3);
  font-weight: 500;
  color: var(--color-text-primary);
}

/* The decision a block lands on, stated in one line under its fields (case study 3). */
.decision__statement {
  font-family: var(--font-sans);
  font-size: var(--text-stat-label);
  font-weight: 500;
  color: var(--color-text-primary);
}

.decision__fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

/* The decision body stacks at 24px. Case study 2's block 01 is a sequence of full-width rows
   that Figma separates by 48px and 72px instead; these add to the stack gap rather than
   requiring a second wrapper. 72px is the same interval design.md documents before a numbered
   block, and it recurs between that block's own rows. */
.decision__gap-48 {
  margin-top: var(--space-lg);
}

/* 64px between one decision block and the next. .decision__body contributes 24 of it, so this
   adds the remaining 40. 64 is not on the documented spacing scale, which jumps 48 -> 72. */
.decision__gap-64 {
  margin-top: calc(64px - var(--space-lg));
}

.decision__gap-72 {
  margin-top: var(--space-2xl);
}

/* 16px body set in Medium — Figma's --text-body-emphasis, used for a lead line above a row. */
.field__lead {
  font-weight: 500;
}

/* 36px mono label introducing a decision's outcome ("DECISION 1"). Figma gaps it 7px from its
   list; normalized to --space-sm. */
.field--loose {
  gap: var(--space-sm);
}

.decision__callout {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Every decision group in case study 2 runs the 48px section rhythm. The base .decision__callout
   keeps its tight 8px for block 01, where the label sits directly on its bullet list. */
.decision__callout--lg {
  gap: var(--space-2xl);
}

/* The DECISION label sits closer to the line it introduces than the fields are to each other:
   24px rather than 48. Pulling the following element up by the difference leaves every later gap
   in the group untouched. */
.decision__callout--lg > .decision__callout-label + * {
  margin-top: calc(var(--space-lg) - var(--space-2xl));
}

/* Right column of a decision row, dropped so it starts level with the left column's
   subheading rather than its label. Figma measures 68px and 112.5px in the two rows of
   block 02; regularized to one interval. */
/* Right column of a decision row, dropped so it lines up with something in the left column. The
   two columns are independent flows with different content, so there is no shared baseline —
   each interval is measured and set per instance through --visual-offset:
     block 02, DECISION 2 — 104px, card bottom level with the quote's attribution line
     block 02, DECISION 3 —  94px, quote bottom level with the last list item
     block 03, DECISION 4 — 163px, insight bottom level with the left column's last line
   Re-measure whenever the copy in either column changes. */
.decision-row__visual--offset {
  margin-top: var(--visual-offset, var(--space-3xl));
}

/* Bare pull quote under a label. Unlike .quote it has no band, quote mark or divider, and
   Figma sets it at --text-label rather than --text-caption-quote. */
.testimonial {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-style: italic;
  font-weight: 400;
  color: var(--color-text-secondary);
}

.decision__callout-label {
  font-family: var(--font-mono);
  font-size: var(--text-h2);
  font-weight: 500;
  color: var(--color-text-secondary);
}

/* THE IDEA field above its supporting row, in case study 2's 03/ THE PROCESS opener. */
.process__block {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
  margin-top: var(--space-xl);
}

/* 72px before every numbered decision block — measured from the Figma frames (intro row bottom
   302 -> numeral 374, and exploration band bottom 2307 -> numeral 2379). */
.numbered-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xl);
  margin-top: var(--space-3xl);
  margin-bottom: var(--space-xl);
}

.case-study-section--numbered {
  padding-top: var(--space-3xl);
  /* 48px between the comparison image's bottom and the gallery band. With the image pushed
     down to meet PROCESS, the comparison is now the section's lowest element, so this padding
     measures from it directly. */
  padding-bottom: var(--space-2xl);
}

/* Decision block whose visual aligns to the fields, not to the whole block: the caption sits
   beside the heading, and the image spans exactly from the first field label's top edge to the
   last field's final line. */

/* Caption is pinned directly above the image so its gap is set here rather than inherited from
   the body's 24px stack gap. Figma uses 12px, which is not on the scale — see design.md. */
.shot__caption {
  position: absolute;
  bottom: 100%;
  left: 0;
  margin: 0 0 var(--space-sm);
}

.decision__split {
  display: flex;
  align-items: stretch;
  gap: var(--space-column-gap);
}

.decision__split .decision__fields {
  flex: 0 1 520px;
  min-width: 0;
}

.decision__visual {
  position: relative;
  flex: 1;
  min-width: 0;
  max-width: 100%;
  background: var(--color-bg-band);
}

/* Absolutely positioned so the image contributes no intrinsic height — the row's height comes
   from the fields column, and the shot stretches to match it exactly. Omitting `right` lets the
   box shrink-to-fit the image's width, so the percentage-based dot and line map to the image. */
.shot {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  margin: 0;
}

.shot .band-panel__shot {
  display: block;
  height: 100%;
  width: auto;
  max-width: 100%;
  /* Guard: if the column ever constrains the width, letterbox rather than distort. */
  object-fit: contain;
}

/* Pointer line + dot — design.md: Components > Pointer line + dot (annotation connector).
   One SVG so the dot and line share a coordinate space and cannot drift apart. No viewBox, so
   user units are CSS pixels and the round dash caps never distort; the percentage coordinates
   are measured from the screenshot's own pixels, so the anchor holds at any rendered size. */
.shot__pointer {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  /* On request: nudges the dot + line down 4px from their originally-measured anchor, both
     themes. A fixed px transform rather than adjusting the SVG's own percentage coordinates, so
     the shift stays exactly 4px regardless of the screenshot's rendered size. */
  transform: translateY(4px);
}

.shot__callout {
  position: absolute;
  /* Sits flush to the content's right edge: the visual column is 632px, the callout 296px. */
  left: 68.15%;
  top: 83.18%;
  transform: translateY(-50%);
  width: 296px;
  max-width: none;
}

/* On request: dark mode only — the callout box for this one annotation becomes On-Demand's own
   dark band shade with its accent text, instead of the shared --color-accent-bg/--color-text-
   secondary every other callout on the site still uses. Specificity (attribute + class) beats
   .callout's own base rule without needing !important. */
[data-theme="dark"] .shot__callout {
  background: #36352A;
  color: #F0E9A0;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.field__label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
}

.field__body {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

/* A lead line above a supporting row, inside a single field. .field__body is a plain block, so
   this spacing has to be declared — a bare <p> followed by a <div> would otherwise sit flush. */
.field__body--lead-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
}

/* Bulleted list with breathing room between items; lists otherwise run flush. Uses li + li so
   the last item carries no trailing space, and avoids flex, which drops the markers. */
.field__body--spaced li + li {
  margin-top: var(--space-sm);
}

.field__body ul {
  list-style: disc;
  padding-left: var(--space-lg);
}

.field__body ol {
  list-style: decimal;
  padding-left: var(--space-lg);
}

/* Figma sets the ANALYSIS list items at --text-stat-label. */
.field__body--emphasis li {
  font-size: var(--text-stat-label);
  font-weight: 500;
}

/* Decision block paired with a banded visual on the right */

.decision-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-column-gap);
}

.decision-row--tight {
  gap: var(--space-lg);
}

.decision-row__text {
  flex: 0 1 var(--decision-text-width, 520px);
  min-width: 0;
}

.decision-row__visual {
  flex: 1;
  min-width: 0;
}

.annotated__stage {
  min-width: 0;
}

.band-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  background: var(--color-bg-band);
  padding: var(--space-2xl) var(--space-lg);
}

/* A note panel: label, running text and a quote, set on even --space-2xl padding (case study 3,
   block 01). Figma hand-places it at 53 / 45 / 51 / 63 with a 21px gap; normalized, it keeps the same
   line breaks and lands within a few px of Figma's 391px height. */
.band-panel--note {
  padding: var(--space-2xl);
  gap: var(--space-lg);
}

/* A quote panel beside a decision's content (case study 3, block 04: the user-testing insights on
   the A/B options, and the THIP quote beside CONSTRAINTS). Figma pads it 28-36px -> --space-xl, and
   sets 50px from the label to the first quote and 63px between quotes -> 48 and 64 (48 + 16). */
.band-panel--quote {
  padding: var(--space-xl);
  gap: var(--space-2xl);
}

.band-panel--quote .testimonial + .testimonial {
  margin-top: var(--space-md);
}

.band-panel__caption {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* A figure caption that carries its step number as a real list marker (case study 3, block 03:
   "1. Banner…", "2. Badges…", "3. Adding…"), so a caption that wraps hangs under its own text. */
.numbered-caption {
  list-style: decimal;
  padding-left: var(--space-lg);
}

.band-panel__shot {
  border: 1px solid var(--color-border);
  max-width: 100%;
}

/* Animated Copilot flair — case study 2, block 01.
   The InlineOutputCard is a flat export with Copilot's #464feb border already baked in, so this
   overlays a ring that sits exactly on top of it and sweeps a highlight around. The insets are
   percentages of the image box, measured off the PNG (border at x 7..1263, y 4..355 of 1271x367,
   the extra at the bottom being its drop shadow), so they hold at any rendered width.
   Registering the angle lets it animate; where @property is unsupported the fallback keeps a
   static gradient ring and the baked border still shows underneath. */

@property --flair-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.ai-flair {
  position: relative;
  display: block;
}

.ai-flair img {
  display: block;
  width: 100%;
}

.ai-flair__ring {
  position: absolute;
  inset: 1.09% 0.55% 3% 0.55%;
  border-radius: var(--radius-sm);
  padding: 2px;
  pointer-events: none;
  background: conic-gradient(
    from var(--flair-angle, 0deg),
    #464feb 0deg,
    #464feb 190deg,
    #7b6cf6 250deg,
    #3ec9f0 295deg,
    #7b6cf6 330deg,
    #464feb 360deg
  );
  /* Paint only the 2px band: the content box is masked out of the padding box. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: ai-flair-sweep 4s linear infinite;
}

@keyframes ai-flair-sweep {
  to {
    --flair-angle: 360deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ai-flair__ring,
  .steps__frame::before {
    animation: none;
  }
}

/* DLP flow diagram — case study 3, 01/ CONTEXT. Built like .grading: a fixed 1198 x 224 design box
   (Figma group 232:173810, including the card's shadow), connectors as one inline SVG on a matching
   viewBox layered under positioned HTML, wrapped in .flow-scroll so it scrolls inside its column on
   narrow viewports. Every offset below is Figma geometry relative to the group's top-left.
   The "Sharing blocked" card is a mock Outlook notification, so it keeps Outlook's own styling — 10px
   radius, Material elevation shadow, #0075b9 link — scoped here rather than as portfolio tokens. */

.dlp-flow {
  position: relative;
  width: 1198px;
  height: 224px;
  flex-shrink: 0;
}

.dlp-flow__wires {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
}

.dlp-flow__wire {
  fill: none;
  stroke-width: 1;
  stroke-linecap: round;
}

.dlp-flow__wire--allowed {
  stroke: var(--color-flow-allowed);
}

.dlp-flow__wire--blocked {
  stroke: var(--color-flow-blocked);
}

.dlp-flow__icon,
.dlp-flow__band,
.dlp-flow__card {
  position: absolute;
  z-index: 1;
}

.dlp-flow__band {
  width: 193px;
  height: 46px;
  background: var(--color-bg-band);
}

.dlp-flow__card {
  width: 288px;
  height: 125px;
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
}

.dlp-flow__icon--on-top,
.dlp-flow__label,
.dlp-flow__card-title,
.dlp-flow__card-text {
  position: absolute;
  z-index: 2;
}

.dlp-flow__label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-primary);
  white-space: nowrap;
}

.dlp-flow__label--muted {
  color: var(--color-text-secondary);
}

.dlp-flow__label--strong {
  font-weight: 500;
  color: var(--color-text-secondary);
}

.dlp-flow__card-title {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 500;
  color: var(--color-text-primary);
  white-space: nowrap;
}

.dlp-flow__card-text {
  width: 206px;
  font-family: var(--font-sans);
  font-size: var(--text-caption);
  font-weight: 400;
  color: var(--color-text-primary);
}

.dlp-flow__card-text--link {
  color: #0075b9;
}

/* Grading process diagram — design.md: Components > Grading process diagram.
   Fixed-coordinate geometry on a 1281 x 363 design box, the same approach .flow uses: the
   connectors are one inline SVG on that viewBox, and every box, icon and caption is positioned
   HTML above it so the text stays selectable and legible. Wrap it in .flow-scroll so it scrolls
   inside its column on narrow viewports instead of stretching the page.
   Figma's right column ends at 1281, one pixel past the 1280 content column; kept at 1280 here so
   the diagram fits exactly at full width instead of scrolling by a pixel. */

.grading {
  position: relative;
  width: 1280px;
  height: 363px;
  flex-shrink: 0;
}

/* Three explicit layers. Without them the bands, which come later in the DOM, paint over the
   connectors running inside them. */
.grading__wires {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  /* Set on the <svg> rather than the <g>: the arrowhead marker lives in <defs>, so its
     currentColor inherits from here, not from the group that draws the lines. */
  color: var(--color-annotation);
}

.grading__band {
  position: absolute;
  z-index: 0;
  background: var(--color-bg-band);
}

.grading__box {
  position: absolute;
  z-index: 0;
  border: 2px solid var(--color-border);
}

.grading__box--pass {
  border-color: var(--color-status-pass);
}

.grading__box--fail {
  border-color: var(--color-status-fail);
}

.grading__icon {
  position: absolute;
  z-index: 2;
}

.grading__caption {
  position: absolute;
  z-index: 2;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-secondary);
}

.grading__caption--medium {
  font-weight: 500;
}

/* Figma sets the threshold clause in DM Mono Medium Italic. */
.grading__caption em {
  font-style: italic;
  font-weight: 500;
}

.grading__title {
  position: absolute;
  z-index: 2;
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

/* Horizontal step strip — icons with mono captions across the content width, with a labelled
   frame bracketing a subset of the steps. The frame is positioned as a percentage of the 1280px
   grid, the same technique .flow uses, so it tracks the columns it brackets as the page scales.
   Figma's gutters (42/37/37/42/35) average 41.6px across 1280, which space-between reproduces. */

.steps {
  position: relative;
  padding-top: var(--space-3xl);
  padding-bottom: var(--space-2xl);
}

.steps__frame {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 16.09%;  /* (286 - 80) / 1280 */
  width: 50.86%; /* 651 / 1280 */
  border: 5px solid var(--color-bg-band);
  padding: var(--space-md) var(--space-lg);
}

/* A brighter segment travelling around the frame. Same technique as .ai-flair__ring: a conic
   gradient masked down to the 5px band, rotated by the registered --flair-angle. inset:-5px
   reaches back out over the border (insets resolve against the padding box), and padding:5px
   makes the visible ring exactly as thick as the border it sits on, so the solid border below
   stays as the fallback wherever masks or @property are unsupported. Slower than the card's
   sweep — it is a much longer perimeter and should stay in the background. */
.steps__frame::before {
  content: "";
  position: absolute;
  inset: -5px;
  padding: 5px;
  pointer-events: none;
  background: conic-gradient(
    from var(--flair-angle, 0deg),
    var(--color-bg-band) 0deg,
    var(--color-bg-band) 210deg,
    #e6e4db 255deg,
    #ffffff 285deg,
    #e6e4db 315deg,
    var(--color-bg-band) 360deg
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: ai-flair-sweep 6s linear infinite;
}

.steps__frame-label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
}

.steps__row {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-xl);
}

.steps__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  flex: 0 1 185px;
  min-width: 0;
}

.steps__icon {
  flex-shrink: 0;
}

.steps__caption {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* Split comparison panel — two banded columns divided by a vertical rule, each an icon above a
   heading, subline and bullets. All text is mono/secondary. Figma hand-places the two columns
   with 5-6px of drift between them; regularized to one rhythm here. */

.split-panel {
  display: flex;
  align-items: stretch;
  gap: var(--space-lg);
  background: var(--color-bg-band);
  padding: var(--space-lg);
}

.split-panel__col {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  flex: 1 1 0;
  min-width: 0;
}

.split-panel__rule {
  flex-shrink: 0;
  width: 1px;
  background: var(--color-border);
}

/* icon -> heading is 24px; the stack gap supplies 16 of it. */
.split-panel__icon {
  margin-bottom: var(--space-sm);
}

.split-panel__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.split-panel__text,
.split-panel__list {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
}

.split-panel__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  list-style: disc;
  padding-left: var(--space-lg);
}

/* Captioned visual sitting in normal flow (video placeholder, side-by-side drawers). */

.figure-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Block 01's guidance card is top-aligned with the DECISION 1 block in the left column. The two
   columns are independent flows with different content, so there is no shared grid line to align
   to — the interval is measured: 65px below the split panel puts the card's top edge level with
   the DECISION 1 label. Re-measure if the copy in either column changes. */
.figure-stack--align-decision {
  gap: 65px;
}

.figure-stack__pair {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
}

.figure-stack__pair img {
  min-width: 0;
  max-width: 100%;
}

/* SIT classifier cards — design.md: Card information architecture — interactive cards.
   Real Fluent-style components (Figma 667:57877), not the 3 static product screenshots they
   replaced: white/light always, independent of this page's own light/dark theme, the same way
   the case study's embedded product videos don't reskin either — these represent Purview's own
   UI, not this portfolio's. */
.sit-cards {
  align-items: stretch;
}

.sit-card {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border-radius: 16px;
  background: #ffffff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12);
  font-family: "Segoe UI", var(--font-sans);
  transition: box-shadow 0.15s ease-in-out;
}

.sit-card:hover {
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.18), 0 0 2px rgba(0, 0, 0, 0.12);
}

/* The gradient border is a second, slightly larger rounded rect behind the card, showing only
   through the padding this adds — border-image banded badly around a 16px radius, this doesn't. */
.sit-card--agentic {
  margin: 2px;
  background:
    linear-gradient(#ffffff, #ffffff) padding-box,
    linear-gradient(135deg, #58c1ff, #6a6bec 55%, #ba73f2 75%, #f2a35d) border-box;
  border: 2px solid transparent;
}

.sit-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.sit-card__titles {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.sit-card__title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  line-height: 1.3;
  color: #242424;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sit-card__subtitle {
  margin: 0;
  font-size: 15px;
  color: #424242;
}

/* A real toggle, not a picture of one: unchecked is an empty square; clicking fills it with the
   Fluent brand blue and draws a checkmark, same as the actual product control this represents. */
.sit-card__checkbox {
  flex: none;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 2px solid #616161;
  border-radius: 4px;
  background: #ffffff;
  cursor: pointer;
  transition: background-color 0.1s ease-in-out, border-color 0.1s ease-in-out;
}

.sit-card__checkbox::after {
  content: "";
  display: block;
  width: 100%;
  height: 100%;
  background: no-repeat center / 12px 12px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='white' d='M13.85 4.15a.5.5 0 0 1 0 .7l-7 7a.5.5 0 0 1-.7 0l-3-3a.5.5 0 1 1 .7-.7L6.5 10.79l6.65-6.64a.5.5 0 0 1 .7 0Z'/%3E%3C/svg%3E");
  opacity: 0;
  transition: opacity 0.1s ease-in-out;
}

.sit-card__checkbox[aria-pressed="true"] {
  border-color: #0078d4;
  background: #0078d4;
}

.sit-card__checkbox[aria-pressed="true"]::after {
  opacity: 1;
}

.sit-card__checkbox:disabled {
  border-color: #d1d1d1;
  cursor: not-allowed;
}

.sit-card__checkbox:focus-visible {
  outline: 2px solid #0078d4;
  outline-offset: 2px;
}

.sit-card__metrics {
  display: flex;
  gap: 32px;
}

.sit-card__metric-label {
  margin: 0 0 4px;
  font-size: 12px;
  color: #616161;
}

.sit-card__metric-value {
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  color: #424242;
}

.sit-card__metric-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.sit-card__trend {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px 6px;
  border-radius: 4px;
  background: #f1faf1;
  color: #0e700e;
  font-size: 12px;
  font-weight: 500;
}

/* "MorseCode" loading bar (Figma component 181:100841) — a row of dash/dot pills sharing one
   gradient, animated by sliding that gradient sideways along the whole row rather than tweening
   each pill, so the colour keeps flowing continuously through every pill without a seam. */
.sit-card__morse {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 6px;
  margin-top: 4px;
}

.sit-card__morse span {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, #58c1ff, #6a6bec, #ba73f2, #f2a35d, #58c1ff);
  background-size: 400% 100%;
  animation: sit-morse-flow 2.4s linear infinite;
}

.sit-card__morse span:nth-child(1) { width: 22px; }
.sit-card__morse span:nth-child(2) { width: 6px; }
.sit-card__morse span:nth-child(3) { width: 10px; }
.sit-card__morse span:nth-child(4) { width: 22px; }
.sit-card__morse span:nth-child(5) { width: 10px; }
.sit-card__morse span:nth-child(6) { width: 6px; }
.sit-card__morse span:nth-child(7) { width: 14px; }
.sit-card__morse span:nth-child(8) { width: 6px; }

@keyframes sit-morse-flow {
  0% { background-position: 0% 0; }
  100% { background-position: 400% 0; }
}

.sit-card__upgrading {
  margin: 0;
  font-size: 12px;
  color: #242424;
}

@media (prefers-reduced-motion: reduce) {
  .sit-card__morse span { animation: none; }
}

/* Callout tag — design.md: Components > Callout tag */

.callout {
  background: var(--color-accent-bg);
  padding: var(--space-md);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* Process video. The source has black letterbox bars baked in — content occupies rows 116-616
   of 720 (top bar 116px, bottom 103px), and that content's aspect (2.5549) matches the Figma
   placeholder's 627/245 (2.5592) to within 0.2%. object-position shifts the cover crop so
   exactly those bars fall outside the box: 116 / (720 - 501) = 52.75%. */

.decision__visual--plain {
  background: none;
}

/* The container's aspect (2.5592) is fractionally wider than the video content's (2.5549), so
   an exact cover-crop lands within 0.2px of the letterbox on both edges and leaks black. The
   frame clips instead: a 4px horizontal bleed over-scales just enough to create real slack, and
   translateY lands the visible window at native row 118 — several pixels inside the content on
   every side. translateY resolves against the video's own height, so the crop is
   width-independent and holds at any viewport. */
.video-frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: 627 / 245;
  border: 2px solid var(--color-bg-band);
  background: var(--color-bg-band);
}

.process-video {
  display: block;
  position: absolute;
  top: 0;
  left: -2px;
  width: calc(100% + 4px);
  height: auto;
  transform: translateY(-16.389%);
}

/* The Smart SIT clip is cropped to its content at encode time — the 4K source is boxed on three
   sides (black bars left and right, a black band across the top). The file is 2560x1428, sized so
   the 1280px frame draws it 1:1 on a 2x display, so the frame just takes its aspect ratio and the
   video fills it with no transform. */

.video-frame--smart-sit {
  aspect-ratio: 2560 / 1440;
}

/* The classifier guidance clip is cropped at encode time to start on the Microsoft Purview nav bar
   (browser tab strip and address bar removed), inside the window's grey side frame and the nav bar's
   rounded top corners, then scaled to 2560x1384. The frame takes that exact shape, so cover fills it
   with nothing cropped. */
.video-frame--classifier-guidance {
  aspect-ratio: 2560 / 1384;
}

/* The metric details clip is a full-screen capture rather than a browser window, so there is no
   chrome or window frame to remove — only a black strip above the Microsoft Purview nav bar and a
   2px grey shading line down the left edge. Both are cropped at encode time, so the video opens
   exactly on the nav bar; scaled to 2560x1436. */
.video-frame--metric-details {
  aspect-ratio: 2560 / 1436;
}

/* The DSPM prototype clip is a Figma presentation recording, set up like the Smart SIT clip: black
   bars either side of the prototype and a dark band above it that holds the Figma toolbar when it
   is showing. Cropped at encode time to start exactly on the Microsoft Purview nav bar, then scaled
   to 2560x1430. */
.video-frame--dspm {
  aspect-ratio: 2560 / 1430;
}

/* The endpoints settings banner clip (case study 3, block 03) is a full-screen capture with thin black
   bars and edge shading either side. It is cropped at encode time to this placeholder's own 628x377
   shape — trimming mostly the empty left page margin so the header's icons stay whole — so
   object-fit: cover fills the frame with no bars and no distortion. */
.video-frame--endpoints-banner {
  aspect-ratio: 628 / 377;
}

/* The create-list clip (case study 3, block 04) is a Figma prototype recorded inside a browser
   window, letterboxed in black. It is cropped at encode time to this placeholder's 2:1 shape: from
   the Purview nav bar (below the Figma toolbar and browser chrome) down to just above the panel's
   Save/Cancel footer, with the window sides trimmed to fit. Keeping the footer instead would have
   sliced its buttons in half at the frame's bottom edge. */
.video-frame--create-list {
  aspect-ratio: 2 / 1;
}

/* The "Adding it all up" walkthrough (case study 3, block 04) is a full-screen capture with no
   window chrome to remove. Shown uncropped at its own native 16:9 shape (not the Figma
   placeholder's 1280x623) so object-fit: cover never trims any of the recording — see design.md.
   Not autoplay/loop like the shorter clips: at over two minutes, it plays via its own controls
   instead. */
.video-frame--adding-it-all-up {
  aspect-ratio: 2560 / 1440;
}

/* "05/ PUTTING IT ALL TOGETHER" full prototype walkthrough (case study 1), same treatment as
   .video-frame--adding-it-all-up just above: a full-screen capture with no window chrome, shown
   uncropped at its own native shape, played via native controls rather than autoplay/loop since
   it runs over a minute. Ratio is the source file's own 3024x1700 (not yet downscaled/re-encoded
   — see design.md and the fullPrototypeVideo note in that case study's media.json). */
.video-frame--odc-full-prototype {
  aspect-ratio: 3024 / 1700;
}

.video-frame__media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Full-bleed band strip */

.band-strip {
  background: var(--color-bg-band);
  padding: var(--space-2xl) 0;
}

/* Exploration comparison card — design.md: Components > Exploration comparison card */

.explorations {
  display: flex;
  align-items: stretch;
  gap: var(--space-lg);
}

.exploration {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  flex: 1;
  min-width: 0;
}

.exploration + .exploration {
  border-left: 1px solid var(--color-border);
  padding-left: var(--space-lg);
}

.exploration__figure {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

/* All three sources are natively 308px tall, so a fixed 154px height is a clean 50% scale for
   each — no cropping. Width must be auto: media.json sets a width attribute per image, and
   letting that drive the size is what made the three columns scale by different factors and
   pushed VERDICT/REASON out of alignment. object-fit guards against distortion if a narrow
   column ever forces max-width to cap the computed width. */
.exploration__figure img {
  align-self: flex-start;
  height: 154px;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
}

.exploration__label,
.exploration__notes {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* 16px narrower than the column itself (on request) — at full column width the notes text in
   adjacent columns read as too close together, right up against the dividing border/gap; this
   pulls the text back from that edge without touching the figure above it or the column/gap
   layout itself. */
.exploration__notes {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  width: calc(100% - 16px);
}

/* Verdict value only ("Dropped" / "Used in experience"), not the "VERDICT:" label before it.
   500 (DM Mono Medium), not 600: base.css only loads DM Mono at 300/400/500 (see its @import) —
   600 would fall back to the browser's synthetic/faux bold rather than a real drawn weight, and
   500 is already this project's established "bolder" DM Mono weight everywhere else (eyebrows,
   labels, etc. throughout components.css), one step up from this block's own 400 base. */
.exploration__verdict {
  font-weight: 500;
}

.exploration__notes ul {
  list-style: disc;
  padding-left: var(--space-lg);
}

/* Before/after drag comparison — design.md: Components > Before/after comparison.
   --split is the handle position; the "after" image is clipped to everything right of it, so
   BEFORE reads on the left and AFTER on the right. Driven only by pointer input, never scroll. */

/* Figma sizes this comparison at 743px. 743 + 520 (text) + 24 (gap) = 1287, which is 7px more
   than the 1280 content width, so the visual is fixed and the text column absorbs the
   difference (513px) rather than the comparison coming up short. */
.decision-row--tight .decision-row__text {
  flex: 1 1 0;
  min-width: 0;
}

/* The comparison image's top aligns with the PROCESS label (185.5px below the numeral's top).
   The offset is applied to the column, so it is that target minus the caption block above the
   image: 21px caption + 8px gap = 29  ->  185.5 - 29 = 156.5.
   Applies only while the columns sit side by side.
   Width and offset are per-instance: the fallbacks are case study 1's comparison (743px, 156.5px);
   case study 3's block 01 note panel sets 519px and 48px, leaving the text column exactly 737px. */
.decision-row--tight .decision-row__visual {
  flex: 0 0 auto;
  width: var(--tight-visual-width, 743px);
  max-width: 100%;
  margin-top: var(--tight-visual-offset, 156.5px);
}

/* A visual column that runs the full height of its row, so the band panel inside ends on the left
   column's last line — Figma bottom-aligns both of case study 3 block 04's quote panels that way.
   The top still takes --tight-visual-offset while the columns sit side by side. */
.decision-row__visual--stretch {
  align-self: stretch;
  display: flex;
  flex-direction: column;
}

.decision-row__visual--stretch > .band-panel {
  flex: 1 1 auto;
}

/* 743px is the ceiling in both layouts, so the 708px-wide source is never upscaled much.
   Per instance: case study 3's DLP settings comparison sets 845px, its exports' 2x width. */
.before-after {
  max-width: var(--before-after-width, 743px);
}

.before-after__caption {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-secondary);
  /* Figma has 9px here (caption top 113 -> image top 143, caption box 21px). 9 is off the
     scale; --space-sm lands the image within 1px of Figma and stays on it. */
  margin-bottom: var(--space-sm);
}

.before-after__stage {
  position: relative;
  --split: 50%;
  /* Vertical swipes still scroll the page; horizontal ones drive the slider. */
  touch-action: pan-y;
  cursor: ew-resize;
  -webkit-user-select: none;
  user-select: none;
}

.before-after__stage img {
  display: block;
  width: 100%;
  -webkit-user-drag: none;
  user-select: none;
}

/* The two exports differ slightly in aspect ratio (1.500 vs 1.518), so the overlay is pinned to
   the stage box and cover-fitted rather than stretched. */
.before-after__after {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  clip-path: inset(0 0 0 var(--split));
}

.before-after__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--split);
  width: 2px;
  margin: 0 0 0 -1px;
  padding: 0;
  border: 0;
  background: var(--color-bg);
  cursor: ew-resize;
  touch-action: none;
}

.before-after__handle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.before-after__grabber {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 24px;
  height: 48px;
  border-radius: 999px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
  color: var(--color-text-secondary);
}

.before-after__grabber svg {
  width: 12px;
  height: 12px;
}

/* Card explorations gallery.
   Fixed 386px band. The caption sits 12px above the image, and the image takes whatever
   vertical space is left — sized by height with width:auto so its aspect never distorts and it
   never needs a magic pixel height. The PNG carries its own alpha (27% transparent), so the
   band shows through around the cards; the white areas are the card screenshots themselves. */

.band-strip--gallery {
  height: 386px;
  padding: var(--space-lg) 0;
  display: flex;
  flex-direction: column;
}

.band-strip--gallery > .content {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* Figma has 9px here; 12px as specified. Off the spacing scale — see note in design.md.
   margin-left matches the artwork's own centring offset so the caption starts at the image's
   left edge without moving the image. --gallery-art-w is the rendered artwork width: the band
   is 386 tall, less 48 padding, 21 caption and 12 gap = 305 image height, times the PNG's
   native 2498/644 ratio. max() keeps it flush left once the column is narrower than the art. */
.band-strip--gallery {
  --gallery-art-w: calc(305px * 2498 / 644);
}

.band-strip--gallery .band-panel__caption {
  margin-bottom: 12px;
  margin-left: max(0px, calc((100% - var(--gallery-art-w)) / 2));
}

.band-strip--gallery .card-gallery {
  flex: 1;
  min-height: 0;
}

.card-gallery img {
  width: 100%;
}

/* Height-driven so the box is the artwork itself, then centred with auto margins.
   aspect-ratio is the fix, not decoration: without it, the browser has no way to compute
   width:auto from height:100% until the image has actually decoded, so the very first layout
   (and any layout before decode finishes) falls back to the UA's default replaced-element ratio
   (300x150, i.e. 2:1) instead of this PNG's real 2498:644 — rendering at roughly half the
   intended ~1183px width. Invisible on a local server (the file decodes near-instantly over
   loopback) but visible over a real network, where layout can happen before the image is ready.
   Declaring the true ratio up front makes the box's shape correct from the first paint,
   independent of when — or whether — the image has loaded. */
.band-strip--gallery .card-gallery img {
  height: 100%;
  width: auto;
  aspect-ratio: 2498 / 644;
  max-width: 100%;
  margin-left: auto;
  margin-right: auto;
  object-fit: contain;
  object-position: center;
}

/* Information-architecture block + annotated pointer-line diagrams */

.ia-block {
  /* Figma right-aligns this 1068px block to the content edge. */
  max-width: 1068px;
  margin-left: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
}

.ia-block__lede {
  font-family: var(--font-sans);
  font-size: var(--text-stat-label);
  font-weight: 600;
  color: var(--color-text-primary);
}

.annotated {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  overflow-x: auto;
  min-width: 0;
}

.annotated__caption {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--color-text-secondary);
}

.annotated__stage {
  position: relative;
}

.annotated__image {
  display: block;
}

.annotated__lines {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}

/* Bullets sit in normal flow inside an absolutely-placed column, so their 32px spacing is real
   and each one's height is its own. js/main.js measures them and draws the connectors. */
.annotated__notes {
  position: absolute;
  top: var(--notes-top, 55px);
  left: var(--left, 336px);
  width: var(--w, 518px);
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.annotated__notes li {
  position: relative;
  padding-left: var(--space-lg);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

.annotated__notes li::before {
  content: "\2022";
  position: absolute;
  left: 0;
}

/* Plain side-by-side variant — no pointer lines in the frame for these two. */
.annotated__split {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
}

.annotated__split-text {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
  flex: 1;
  min-width: 0;
}

.annotated__split--cloud .annotated__split-text {
  flex: 0 1 520px;
  max-width: 520px;
  margin-top: var(--space-2xl);
}

/* design.md: annotated diagrams drop their pointer lines below 768px and stack vertically. */
@media (max-width: 767px) {
  .annotated__stage {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
  }

  .annotated__lines {
    display: none;
  }

  .annotated__image {
    width: 100%;
  }

  .annotated__notes {
    position: static;
    width: auto;
    gap: var(--space-xl);
  }

  .annotated__split--cloud .annotated__split-text {
    flex: 1 1 auto;
    max-width: none;
    margin-top: 0;
  }

  .annotated__split {
    flex-direction: column;
  }
}

/* The aligned split needs ~493px of visual column beside the 520px fields column (the image's
   height is fixed to the fields, and its 0.786 aspect sets the width). At 1440 the standard
   128px gutter leaves exactly enough; below that the gutter tightens, and below 1280 there is
   no arrangement that fits, so it stacks. */
@media (max-width: 1439px) {
  .decision__split {
    gap: var(--space-xl);
  }
}

@media (max-width: 1279px) {
  .decision__split {
    flex-direction: column;
    gap: var(--space-xl);
  }

  /* Stacked: caption returns to normal flow above the image. */
  .shot__caption {
    position: static;
    margin-bottom: var(--space-sm);
  }

  /* Stacked: no fields column to match, so the shot returns to normal flow at full width.
     The dot/line percentages still map correctly — .shot continues to wrap the image exactly. */
  .decision__split .decision__fields,
  .decision__visual {
    width: 100%;
  }

  .shot {
    position: relative;
    top: auto;
    bottom: auto;
  }

  .shot .band-panel__shot {
    height: auto;
    width: 100%;
  }
}

/* Stacked but still showing the pointer (768-1279px): the image is now full width, so the
   callout anchors to its right edge and the line stretches between dot and callout rather than
   using a fixed percentage width. */
@media (max-width: 1279px) and (min-width: 768px) {
  /* Stacked but still showing the pointer: the image is full width, so the callout runs from
     the line's end to the right edge instead of keeping its fixed 296px width. */
  .shot__callout {
    left: 68.15%;
    right: 0;
    width: auto;
  }
}

/* Block 03 stacks below 1280: its 743px comparison leaves too little for the text column
   otherwise. The 48px gap here is the DECISION-text-to-caption spacing Figma specifies — it
   used to come from the band panel's padding, which this section no longer has. */
@media (max-width: 1279px) {
  .decision-row--tight {
    flex-direction: column;
    gap: var(--space-2xl);
  }

  .decision-row--tight .decision-row__visual {
    width: 100%;
    margin-top: 0;
  }
}

/* Two-column rows stack below 1024px. The 128px --space-column-gap plus a 520px text column
   needs ~1100px of content width; below that the visual column is starved to zero and its
   contents spill out of the page. */
@media (max-width: 1023px) {
  .case-study-header__row,
  .qa-row,
  .process__intro,
  .annotated__split,
  .decision-row,
  .split-panel,
  .figure-stack__pair {
    flex-direction: column;
    gap: var(--space-xl);
  }

  /* No second column to divide once stacked. */
  .split-panel__rule {
    display: none;
  }

  /* Once stacked there is no fields column to match height against, so the shot returns to
     normal flow at full width. The dot/line percentages still map correctly, because .shot
     continues to wrap the image exactly. */
  .decision__split .decision__fields,
  .decision__visual {
    width: 100%;
  }

  .shot {
    position: relative;
    top: auto;
    bottom: auto;
  }

  .shot .band-panel__shot {
    height: auto;
    width: 100%;
  }

  .case-study-header__text,
  .qa-row__text,
  .decision-row__text,
  .decision-row__visual,
  .process__intro-text,
  .index-list {
    flex: 1 1 auto;
    width: 100%;
  }

  /* The header visual bleeds through the right page margin at desktop; not once stacked. */
  .case-study-header__visual {
    margin-right: 0;
  }
}

/* Mobile overflow containment (< 768px).
   Mechanical collapses only — multi-column layouts fold to a single column so nothing forces a
   horizontal scrollbar. Desktop layout is untouched. This is not the full responsive pass:
   type scale, page margins and vertical rhythm are still desktop values. */
/* The bracketing frame only makes sense while the steps sit in a row. */
@media (max-width: 1023px) {
  .steps {
    padding-top: 0;
    padding-bottom: 0;
  }

  .steps__frame {
    display: none;
  }

  .steps__row {
    flex-wrap: wrap;
    gap: var(--space-xl);
  }

  .steps__item {
    flex: 1 1 185px;
  }
}

@media (max-width: 767px) {
  .steps__row {
    flex-direction: column;
  }

  .steps__item {
    flex: 1 1 auto;
    width: 100%;
  }

  .meta-row,
  .stats-band__row {
    flex-direction: column;
    gap: var(--space-lg);
  }


  /* Three-up grids fold to one column. .boxes interleaves images and captions, which are
     separate grid children, so they need explicit ordering to stay paired. */
  .compare {
    grid-template-columns: 1fr;
    gap: var(--space-xl);
  }

  .boxes {
    grid-template-columns: 1fr;
    row-gap: var(--space-sm);
  }

  .boxes > :nth-child(1) { order: 1; }
  .boxes > :nth-child(4) { order: 2; }
  .boxes > :nth-child(2) { order: 3; }
  .boxes > :nth-child(5) { order: 4; }
  .boxes > :nth-child(3) { order: 5; }
  .boxes > :nth-child(6) { order: 6; }

  /* Exploration cards stack; their dividing rule moves from left edge to top edge. */
  .explorations {
    flex-direction: column;
  }

  .exploration + .exploration {
    border-left: 0;
    border-top: 1px solid var(--color-border);
    padding-left: 0;
    padding-top: var(--space-lg);
  }

  .quote {
    padding: var(--space-xl) var(--space-md);
  }

  /* design.md: annotation connectors are dropped entirely below 768px. */
  .shot__pointer {
    display: none;
  }

  .shot__callout {
    position: static;
    transform: none;
    width: auto;
    margin-top: var(--space-md);
  }

  /* Keep the diagram's design width on mobile; .flow-scroll scrolls it. */
  .flow {
    min-width: 630px;
  }
}

/* Flow diagram — design.md: Components > Flow diagram.
   Offsets below are diagram geometry taken from Figma, not spacing-scale values. They are
   expressed as percentages of the 630px column Figma drew them in, so the diagram keeps its
   proportions and never overflows when the column narrows. */

/* The flow diagram and the annotated stages are fixed-coordinate geometry. They scroll inside
   their own container so they can never stretch the page. No effect when they already fit. */
.flow-scroll {
  overflow-x: auto;
  /* max-width caps this against the parent's content box so the diagram's fixed design width
     scrolls inside it rather than propagating up the ancestor chain. min-width alone is not
     enough: flex items stretch, so the oversized child still widens every wrapper above it. */
  min-width: 0;
  max-width: 100%;
}

.flow {
  position: relative;
  min-height: 145px;
}

.flow__box {
  position: absolute;
  left: 0;
  top: 0;
  width: 63.02%; /* 397/630 */
  height: 142px;
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-sm);
}

.flow__box-label {
  position: absolute;
  left: 2.38%; /* 15/630 */
  top: 14px;
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* 24px outer circle with a 3px accent ring and page-coloured centre — matches Figma's
   pair of concentric 24px/18px ellipses. */
.flow__dot {
  position: absolute;
  top: 42px;
  width: 24px;
  height: 24px;
  border: 3px solid var(--color-accent);
  border-radius: 50%;
  background: var(--color-bg);
}

.flow__dot--1 { left: 2.7%; }   /* 17/630 */
.flow__dot--2 { left: 34.6%; }  /* 218/630 */
.flow__dot--3 { left: 69.05%; } /* 435/630 */

/* Connectors sit below the dots in the DOM, so any overlap at narrow widths hides under them. */
.flow__connector {
  position: absolute;
  top: 55px;
  border-top: 1.5px dashed var(--color-accent);
}

.flow__connector--1 {
  left: 6.51%;  /* 41/630 */
  width: 28.1%; /* 177/630 */
}

.flow__connector--2 {
  left: 38.41%;  /* 242/630 */
  width: 29.84%; /* 188/630 */
}

.flow__connector--2::after {
  content: "";
  position: absolute;
  right: -1px;
  top: 0;
  transform: translateY(-50%);
  border-left: 8px solid var(--color-accent);
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
}

.flow__label {
  position: absolute;
  top: 88px;
  width: 30.63%; /* 193/630 */
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  font-weight: 400;
  color: var(--color-text-secondary);
}

.flow__label--1 { left: 3.17%; }  /* 20/630 */
.flow__label--2 { left: 34.6%; }  /* 218/630 */
.flow__label--3 { left: 69.05%; } /* 435/630 */

/* Spiral binding — design.md: Page chrome > Spiral binding.
   Wrap every section from "01/ CONTEXT" down in .notebook and paste the binding SVG as its first child. */

.notebook {
  position: relative;
}

.notebook__binding {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--binding-width);
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* Coil sits above the page background, behind every section's content. */
.notebook > *:not(.notebook__binding) {
  position: relative;
  z-index: 1;
}

/* Band strips run the full page width, so their background would paint straight over the coil.
   Keep the strip itself out of the stacking order (z-index: auto, so it forms no stacking
   context), push its background to a pseudo-element *behind* the binding, and lift its content
   back above. Result: band background < coil < content. */
.notebook > .band-strip {
  z-index: auto;
  background: none;
}

.notebook > .band-strip::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--color-bg-band);
  z-index: -1;
}

.notebook > .band-strip > * {
  position: relative;
  z-index: 1;
}

/* Nav bar — design.md: Components > Nav bar */

/* No max-width/margin: full-bleed edge to edge (the blurred background needs to actually span the
   viewport, not just this box's own content width), while .nav__inner further down keeps doing
   its own independent centering at --nav-width. Kept padding-left/right here (unchanged) so
   .nav__inner's rendered position is provably identical to before this box went full-bleed — auto
   margins center it at the same point whether its parent is this wide, capped box or the full
   viewport, as long as this padding stays put. */
.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--space-page-margin);
  padding-bottom: var(--space-md);
  padding-left: var(--space-page-margin);
  padding-right: var(--space-page-margin);
}

/* Scroll-hide behaviour, shared by the main site nav and the case study top bar (.topbar, defined
   further down): both are sticky (stay in the page's normal flow until scrolled to, then pin to
   the viewport top for the rest of the page — no JS needed to reserve their old space, unlike
   position: fixed) and fade out while the page is actively scrolling, fading back in once
   scrolling stops — see js/navScroll.js, which toggles .nav-scroll--hidden on a debounced scroll
   listener. .topbar's own background is set further down, matching the hero directly below it —
   see that rule's own comment and design.md. */
.nav,
.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  transition: opacity 200ms ease;
}

/* .nav (home/about/work — pages with no single case-study colour to match) is translucent with a
   background blur rather than either fully transparent (text over arbitrary scrolled-behind
   content wasn't legible — the problem this replaces) or a flat opaque colour (there's no one
   "page colour" on these pages to flatly match, unlike .topbar's case-study accent). Same idea
   both themes: a frosted strip that reads as its own layer above whatever scrolls beneath it,
   legible regardless of what that is. -webkit- prefix needed for Safari.
   Tint is the page's own --color-bg (250,250,247 light / 27,26,23 dark), not literal white/black
   — a plain white looked stark against the page's slightly warm off-white, and plain black read
   darker than the page's own near-black. Alpha stays low enough that the blurred content behind
   still shows through; this is a tint on top of that, not a flat fill. */
.nav {
  background: rgba(250, 250, 247, 0.65);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}

[data-theme="dark"] .nav {
  background: rgba(27, 26, 23, 0.55);
}

.nav.nav-scroll--hidden,
.topbar.nav-scroll--hidden {
  opacity: 0;
  pointer-events: none;
}

/* The 72px is between the labels, not between the pills, so the gap absorbs each pill's 16px
   padding and 1px border either side — same arithmetic as the homepage nav. */
.nav__links {
  display: flex;
  gap: calc(var(--space-3xl) - 2 * var(--space-md) - 2px);
}

/* Every item carries the pill's box and a transparent border, so marking one selected changes
   only its colour and shifts nothing. See design.md: Homepage nav > States. */
.nav__links a {
  display: inline-flex;
  align-items: center;
  height: 48px;
  padding: 0 var(--space-md);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: var(--text-nav);
  font-weight: 400;
  color: var(--color-text-primary);
}

/* The current page. Resume never takes it — it opens a file in a new tab. */
.nav__links a[aria-current="page"] {
  border-color: var(--color-text-primary);
}

/* Pill button — design.md: Components > Pill button. Figma 505:75203 sizes it 98x32 with a 20px
   icon 11px in and the label 10px after it. */
.btn-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--color-button-primary);
  color: var(--color-button-primary-label);
  font-family: var(--font-sans);
  font-size: var(--text-micro);
  font-weight: 500;
  line-height: var(--leading-normal);
  text-decoration: none;
  cursor: pointer;
}

.btn-pill__icon {
  flex: none;
  width: 20px;
  height: 20px;
}

/* Off-screen for sighted readers, still announced. Used for live status text such as the photo
   album's page counter, which has no visible equivalent. */
.u-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Explore these case studies next — design.md: Explore next. Figma 403:49755.
   Built from the data in js/explore.js; each page drops its own entry, so the markup on a case
   study page is one empty element. */

.explore {
  padding-top: var(--space-3xl);
  padding-bottom: var(--space-3xl);
}

/* Matches .section-eyebrow (the "06/ TAKEAWAYS" style every other section uses) exactly, colour
   included — was --color-text-primary, reading noticeably brighter/heavier than every eyebrow
   above it on the same page. font-family/size/weight already agreed; only the colour was off.

   margin-top is a literal 36px, not --space-xl (32px) — .section-eyebrow's own gap above every
   OTHER section's divider — because this one was asked for at that specific value rather than
   asked to match the others. .explore .section-divider itself still carries the shared
   .section-divider rule's own margin: 0, so this 36px is the whole gap on its own, the same way
   .section-eyebrow's margin-top is the whole gap for every other section. */
.explore__label {
  margin: 0;
  margin-top: 36px;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  line-height: var(--leading-normal);
  color: var(--color-text-secondary);
}

/* Two 628px cards, 24px apart, filling the 1280px content column. */
.explore__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-lg);
  margin-top: var(--space-xl);
}

.explore-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-height: 288px;
  padding: 36px;
  border-radius: 5px;
  background: var(--card-bg);
  color: var(--card-accent);
}

.explore-card__eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  line-height: var(--leading-normal);
}

/* 32px, the headline size the book cards and the case study cards share. */
.explore-card__title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 32px;
  font-weight: 600;
  line-height: var(--leading-normal);
}

/* Pinned to the foot, so both cards' links line up however long the headline runs. */
.explore-card__link {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-sans);
  font-size: var(--text-label);
  font-weight: 500;
  color: inherit;
  text-decoration: none;
}

/* Stretched over the whole card, so clicking anywhere follows it while the card stays a single
   link for assistive tech rather than a nest of clickable boxes. */
.explore-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.explore-card__link svg {
  flex: none;
  width: 20px;
  height: 20px;
}

.explore-card:hover .explore-card__link {
  text-decoration: underline;
}

.explore-card__link:focus-visible::after {
  outline: 2px solid var(--card-accent);
  outline-offset: 3px;
}

/* Each card wears its own case study's colours — design.md: Work library book cards. */
.explore-card--on-demand {
  --card-bg: var(--color-lib-ondemand-bg);
  --card-accent: var(--color-lib-ondemand-accent);
}

.explore-card--ai-powered {
  --card-bg: var(--color-lib-ai-bg);
  --card-accent: var(--color-lib-ai-accent);
}

.explore-card--reusable-lists {
  --card-bg: var(--color-lib-lists-bg);
  --card-accent: var(--color-lib-lists-accent);
}

/* Dark mode fill only, no border. This border existed to compensate for a problem that no longer
   applies: when the whole PAGE used to take on a case study's own accent colour, a different case
   study's card sitting on it could nearly vanish (the AI card on the On-Demand page measured
   1.26:1) — so cards dropped to a darker band shade plus a hairline of their accent to draw the
   edge. The page background is now the same uniform #1b1a17 on all three case studies (see
   design.md: Case study page — header & dark mode → Dark mode (per case study)), so that
   cross-case near-invisibility can't happen any more — removed on request rather than kept as a
   fix for a problem that's already gone. Card fills alone now read 1.84 / 2.38 / 2.99:1 against
   the page — still under the 3:1 non-text contrast guideline on their own, but each card is
   visibly distinct from the near-black page and from the other two, which is what matters here. */
[data-theme="dark"] .explore-card {
  background: var(--card-bg-dark);
}

[data-theme="dark"] .explore-card--on-demand { --card-bg-dark: #36352a; }
[data-theme="dark"] .explore-card--ai-powered { --card-bg-dark: #372a24; }
[data-theme="dark"] .explore-card--reusable-lists { --card-bg-dark: #1f242f; }

@media (max-width: 1023px) {
  .explore__grid {
    grid-template-columns: 1fr;
  }
}

/* Below 768px the nav's fixed 72px link gaps exceed the available width, forcing a horizontal
   scrollbar. Wrap the three groups and tighten the gap to the spacing scale. */
@media (max-width: 767px) {
  .nav {
    flex-wrap: wrap;
    gap: var(--space-md);
  }

  .nav__links {
    gap: var(--space-lg);
  }
}

/* Case study header — design.md: Components > Case study header */

/* Outer section is now the full-bleed background layer (its own background paints the whole
   width, same pattern as .stats-band / .stats-band__inner below), with .case-study-header__row
   carrying the "content" class as the centered inner layer — needed so dark mode can give this
   one section a light background full-bleed edge to edge, not just across the capped content
   width. See the [data-theme="dark"] override further down and design.md. */
/* No padding-bottom, on request: the hero image should sit flush against the stats band directly
   below with no gap at all — the band moves up to meet the image, not the other way round. See
   .case-study-header__visual below (align-items: flex-end, no compensating negative margin needed
   now that there's no padding here for it to cancel out — one less thing that has to add up to
   exactly zero). Text sits higher than the image in every case study so far, so this reads as
   intentional breathing room under the text with the image anchoring the section's true bottom
   edge; if a future case study's text ever runs longer than its image, text would also go flush,
   which is an accepted tradeoff of "the section's bottom edge is the band," not a bug. */
.case-study-header {
  background: var(--color-bg);
  padding-top: var(--space-2xl);
}

/* On request: this section (hero header, ending right before the metrics/stats band) keeps that
   case study's own accent pairing even in dark mode — --cs-accent / --cs-accent-text, the same
   tokens .cs--on-demand/.cs--ai-powered/.cs--reusable-lists already define on <body> (used
   elsewhere for the light-mode stats band and, before this, only as dark mode's whole-page tint —
   see design.md). Not the plain site-wide light background/text (#fafaf7/#171717) — an earlier
   pass used those before this correction. The stats band directly below it, and everything from
   "01/ CONTEXT" on, are untouched and stay on the normal dark-mode palette — scoped to
   .case-study-header only, not the whole [data-theme="dark"] page. Re-declaring --color-bg/
   --color-text-primary/--color-text-secondary here (rather than setting background/color directly
   with literals) lets every descendant that already reads var(--color-text-primary) /
   var(--color-text-secondary) (eyebrow, title, body copy, meta-row labels/values) pick up the
   accent text colour automatically. The header's own placeholder visual already renders light in
   dark mode regardless (see the .cs--on-demand/.cs--ai-powered/.cs--reusable-lists dark override
   in tokens.css), so it needed no change here. */
[data-theme="dark"] .case-study-header {
  --color-bg: var(--cs-accent);
  --color-text-primary: var(--cs-accent-text);
  --color-text-secondary: var(--cs-accent-text);
  background: var(--color-bg);
  color: var(--color-text-primary);
}

.case-study-header__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-md);
}

/* Figma 509:75529: 519px text column + 817px visual. The visual bleeds through the right page
   margin, which is the only way those two widths fit (519 + 24 + 817 = 1280 + 80). */
.case-study-header__row {
  display: flex;
  align-items: stretch;
  gap: var(--space-lg);
}

/* Figma 509:75529 sets the text column 519px with the visual 817px beside it, bleeding through the
   right page margin (519 + 24 + 817 = 1280 + 80). The 737px basis this was first built at came
   from the earlier frame. */
.case-study-header__text {
  flex: 0 1 519px;
  min-width: 0;
}

/* 36px, not the 48px display size this was first built at — Figma 509:75529. */
.case-study-header__title {
  font-family: var(--font-sans);
  font-size: var(--text-h2);
  font-weight: 500;
  color: var(--color-text-primary);
  margin-bottom: var(--space-sm);
}

.case-study-header__body {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
  margin-bottom: var(--space-2xl);
}

/* display: flex + align-items: flex-end so the image (sized from its own aspect ratio, not the
   container's) sits flush against the bottom of this box rather than stretching to distort — see
   .case-study-header__visual img below and design.md: Case study hero visual. Bottom-aligned so
   the image's own bottom edge lands exactly on the row's own bottom edge, which — now that
   .case-study-header carries no padding-bottom — is also the section's true bottom edge, flush
   against the stats band with nothing in between. No margin-bottom trick needed: previously this
   had to cancel out the section's padding-bottom with an exactly-equal negative margin, two
   opposite constants that had to add up to precisely zero; removing the padding instead removes
   the gap structurally, with nothing left that depends on two numbers matching. */
.case-study-header__visual {
  display: flex;
  align-items: flex-end;
  flex: 1;
  min-width: 0;
  margin-right: calc(-1 * var(--space-page-margin));
}

/* The hero visual is a single pre-flattened image (two layered screenshots baked into one PNG,
   drop-shadow included) rather than markup composing two separate images. */
.case-study-header__visual img {
  display: block;
  width: 100%;
  height: auto;
}

.meta-row {
  display: flex;
  justify-content: space-between;
}

/* Figma sets these three columns to 193/194/193 inside the header text column on both case
   studies. Without an explicit basis they shrink to fit their copy, which moves every column and
   stops the longer role lines from wrapping where Figma wraps them. */
.meta-row__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  flex: 0 1 193px;
  min-width: 0;
}

.meta-row__label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  color: var(--color-text-secondary);
}

.meta-row__value {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  color: var(--color-text-primary);
}

.meta-row__value--stacked {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* Once .meta-row stacks at 767 the basis governs height, not width, and would hold each column
   open at 193px. This has to sit after the base rule to win the cascade. */
@media (max-width: 767px) {
  .meta-row__item {
    flex: 0 1 auto;
    width: 100%;
  }
}

/* Theme toggle — design.md: Case study page — header & dark mode.
   Unscoped so the homepage nav and the case study top bars share one implementation. The button
   shows the mode it switches *to*: a night badge in light, a sun in dark. */

.nav__theme {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34.162px;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: none;
  cursor: pointer;
}

.nav__theme:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

.nav__theme-icon {
  display: block;
  width: 34px;
  height: 34px;
}

/* Figma's light mode button is 34.162 x 36 — slightly taller than the night badge. */
.nav__theme-icon--sun {
  width: 34.162px;
  height: 36px;
}

.nav__theme-icon--sun,
[data-theme="dark"] .nav__theme-icon--moon {
  display: none;
}

[data-theme="dark"] .nav__theme-icon--sun {
  display: block;
}

/* Homepage nav — design.md: Homepage nav. Figma frames 444:30604 (layout) and 463:74703 (home
   button states). Used on the homepage, work.html and about.html. Case study pages use .topbar instead.

   It sits --space-xl from the top (not the 80px page margin the case studies use), and its inner row
   is --nav-width — the same token the book itself now uses (css/hero.css: .book), so the home pill's
   left edge lines up with the book's left edge at every size, not just the ones where two separate
   formulas happened to agree. */
.nav--home {
  padding-top: var(--space-xl);
}

/* Grid, not flex space-between: the home pill (165px) and theme toggle (34.162px) are different
   widths, so space-between's per-line distribution left nav__links off-center by (165-34.162)/2 ≈
   44px — visible as "About" sitting off the page's true centre. A 1fr/auto/1fr grid pins nav__home
   and nav__theme to the outer edges while keeping the middle column's own width exactly its
   content's, so nav__links centers on the row itself, independent of how wide its flanking
   siblings are — minmax(0, 1fr), not bare 1fr, on the outer columns: a bare 1fr's minimum track
   size still defaults to its item's content size (165px/34px), so the two side columns end up
   unequal despite both being "1fr", pushing the middle column off-centre again just by a smaller
   amount (confirmed by measuring the rendered offset, not just reasoning about it); minmax(0, 1fr)
   floors that minimum at 0 so the equal-fr split is actually equal, and the fixed-width content
   simply sits inside whichever side of that space justify-self points it at. This grid now stays
   in force at every width, including narrow ones — see the nav reflow rules further down (Global
   nav > Reflow) for how it keeps fitting on one row instead of wrapping. */
.nav--home .nav__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  width: var(--nav-width);
  margin: 0 auto;
}

.nav--home .nav__home {
  justify-self: start;
}

.nav--home .nav__links {
  justify-self: center;
}

.nav--home .nav__theme {
  justify-self: end;
}

/* Home button: a fixed 165x48 pill, so the text never moves when it goes Medium on hover. The border
   marks the selected item; unselected keeps a transparent one, so nothing shifts either way. */
.nav--home .nav__home {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 165px;
  height: 48px;
  padding: 11px 16px 11px 13px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  color: var(--color-text-primary);
}

.nav--home .nav__home.is-selected,
.nav--home .nav__link.is-selected {
  border-color: var(--color-text-primary);
}

/* The flower keeps a 24x26 box and scales past it on hover, which is how Figma makes room without
   moving the text (it tightens the gap instead). Scale and spin run together on one transition, so
   leaving mid-spin unwinds smoothly rather than snapping. */
.nav--home .nav__flower {
  display: block;
  flex-shrink: 0;
  width: 24px;
  height: 26px;
  overflow: visible;
}

.nav--home .nav__flower-icon {
  display: block;
  width: 100%;
  height: 100%;
  transform-origin: 50% 50%;
  transition: transform 500ms ease-in-out;
}

/* Hover, selected or not: flower to 28x30 (scale 1.167 / 1.154) with a full turn, text to Medium. */
.nav--home .nav__home:hover .nav__flower-icon,
.nav--home .nav__home:focus-visible .nav__flower-icon {
  transform: scale(1.1667, 1.1538) rotate(360deg);
}

.nav--home .nav__home:hover .nav__label,
.nav--home .nav__home:focus-visible .nav__label,
.nav--home .nav__link:hover .nav__label,
.nav--home .nav__link:focus-visible .nav__label {
  font-weight: 500;
}

@media (prefers-reduced-motion: reduce) {
  .nav--home .nav__flower-icon {
    transition-duration: 1ms;
  }

  .nav--home .nav__home:hover .nav__flower-icon,
  .nav--home .nav__home:focus-visible .nav__flower-icon {
    transform: scale(1.1667, 1.1538);
  }
}

/* Work / About / Resume. Figma spaces the labels 72px apart; the pill padding and its 1px border sit
   inside that, so the gap is 72 - 2x16 - 2x1 and the text-to-text distance still reads 72.
   Plain flex/gap, not grid: an earlier pass made this a minmax(0,1fr) auto minmax(0,1fr) grid (same
   idea as .nav--home .nav__inner just above) specifically to get "About"'s own midpoint exactly on
   the page's centre, since Work (no icon) and Resume (icon + longer label) being different widths
   otherwise puts it off-centre by (Work width − Resume width)/2. That fix worked but had a cost
   this rule's plain-gap predecessor didn't: minmax(0,1fr) forces the Work-side and Resume-side
   columns to equal width regardless of what's actually in them, and since Work's own content is
   narrower than Resume's, the leftover column width shows up as visible dead space between Work
   and About specifically — reported as "a lot of space between Work and About" once seen in a real
   browser, not something the earlier synthetic pixel-measurement testing caught. Figma's own spec
   for this row is a single uniform 72px gap, not "About exactly centred" — reverted to that; About
   is now off true page-centre by a few px (half the Work/Resume width difference), which reads as
   nothing, versus the uneven gap, which reads as broken. */
.nav--home .nav__links {
  display: flex;
  align-items: center;
  gap: calc(var(--space-3xl) - 2 * var(--space-md) - 2px);
}

.nav--home .nav__link {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  height: 48px;
  padding: 11px var(--space-md);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  color: var(--color-text-primary);
}

.nav--home .nav__link-icon {
  display: block;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
}

/* Nav labels: 14px Regular, Medium on hover. The hidden bold twin holds the Medium width in reserve,
   so hovering never nudges the items beside it. */
.nav--home .nav__label {
  display: inline-grid;
  font-family: var(--font-sans);
  font-size: var(--text-nav);
  font-weight: 400;
  line-height: var(--leading-normal);
  white-space: nowrap;
}

.nav--home .nav__label::after {
  content: attr(data-label);
  visibility: hidden;
  height: 0;
  font-weight: 500;
  pointer-events: none;
}

.nav--home .nav__label,
.nav--home .nav__label::after {
  grid-area: 1 / 1;
}

/* Theme toggle: both badges come from Figma — the 33.9px night badge (444:30604) in light mode, and
   the light mode button (463:74786) in dark. Only the icon for the mode being switched *to* is shown,
   and the button takes the larger of the two footprints so it never resizes on toggle. */
/* The toggle button itself is styled by the unscoped .nav__theme rules above. */

/* Nav reflow — design.md: Global nav > Reflow. Figma 568:814 "Nav reflow": below some width, the
   nav must not wrap to multiple lines — instead the brand pill's label swaps from the full
   "Supriya D'Costa" down to initials, "S D", freeing width to help keep the whole bar (brand /
   Work / About / Resume / theme toggle) on one row. Figma's own reflow frames only cover down to
   ~400px of row content though (nowhere near real phone widths once the fixed 80px page margins
   are added back), so getting an actual phone viewport (down to 320px) to hold one row needed
   more than the label swap alone — the nav's own side padding, the gap between the three main
   groups, and the gap/padding inside Work/About/Resume all step down too. Text itself is never
   shrunk or dropped (still Figma's own words at Figma's own font size, just tighter padding
   around them) and Work/About/Resume/the toggle keep their full labels always — only the brand
   pill abbreviates. Scoped to .nav (not the shared --space-page-margin token) so this doesn't
   touch any other component's margins — those stay the known, separately-tracked incomplete
   mobile pass. Numbers below were tuned by rendering real widths from 1512px down to 320px and
   fixing what actually overflowed, not derived from the spec up front — first pass collided
   "Supriya D'Costa" with "Work" around 660-700px (breakpoint moved up to 740 to clear it with
   margin), then still clipped the theme toggle off-screen below ~460px even after the label swap
   (the second, tighter round of padding/gap values below is what closed that gap down to 320px). */
.nav--home .nav__label--short {
  display: none;
}

@media (max-width: 900px) {
  .nav {
    padding-left: var(--space-xl);
    padding-right: var(--space-xl);
  }
}

@media (max-width: 740px) {
  .nav {
    padding-left: var(--space-sm);
    padding-right: var(--space-sm);
  }

  .nav--home .nav__home {
    width: auto;
    gap: 6px;
    padding: 11px 10px 11px 9px;
  }

  .nav--home .nav__label--full {
    display: none;
  }

  .nav--home .nav__label--short {
    display: inline-grid;
  }

  /* Grid swapped for plain flex here, not just tighter grid gaps: minmax(0, 1fr) (the desktop
     centering fix, still in force above 740px) is only safe when there's slack to give — once a
     track's available share is squeezed past its own content's width, a 0-floor minmax track
     collapses toward 0 while the (unshrinkable, white-space: nowrap) content inside it doesn't,
     so the content spills into the next column and visibly overlaps it rather than the row
     overflowing cleanly. Confirmed on a real render (an iframe sized to an exact CSS width, the
     only way to actually test sub-500px viewports in this headless Chrome setup — its
     --window-size clamps to a 500px floor, which is what let this collision go unnoticed for
     several rounds of "testing" earlier in this same change: every width requested below 500
     silently rendered at 500 and reported as fine). Plain flex doesn't have that failure mode —
     items keep their own min-content size and the row overflows outward instead of collapsing
     into itself — and it's a closer match to Figma's own reflow frames anyway, which show the
     narrow state as one tight left-aligned cluster (brand pill, then Work/About/Resume, then the
     toggle, each a small fixed gap apart) rather than spread across the full nav width the way
     the desktop layout is. */
  .nav--home .nav__inner {
    display: flex;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: var(--space-xs);
  }

  .nav--home .nav__links {
    display: flex;
    column-gap: var(--space-sm);
  }

  .nav--home .nav__link {
    padding: 11px var(--space-sm);
  }

  /* A little more room before the toggle than the tight 4px between the other groups — closer to
     Figma's own reflow spacing there (568:814 leaves a visibly bigger gap before the toggle than
     between the brand pill and Work) — on top of, not instead of, .nav__inner's own gap. */
  .nav--home .nav__theme {
    margin-left: var(--space-sm);
  }
}

/* Below 460px even that tightened spacing runs out — real phone widths (most are 360-430px) are
   narrower than the row's own intrinsic content width (S D pill + Work + About pill + Resume +
   its icon + the theme toggle, all at Figma's fixed sizes) can reach through padding/gap alone;
   confirmed by testing with every padding/gap above pushed to near-zero and still not fitting.
   Figma's own reflow reference (568:814) doesn't design a state past "S D", so this drops the one
   remaining non-essential element — Resume's icon — rather than inventing a shrunk font size or
   truncated label Figma never specified. */
@media (max-width: 460px) {
  .nav--home .nav__link-icon {
    display: none;
  }
}

/* The nav no longer wraps at any supported width (design.md: Global nav > Reflow, on request) —
   the old "below 768px the three groups wrap" flex fallback that lived here is gone; the grid
   layout above stays in force at every width, and the reflow rules just above this comment (the
   "S D" label swap + tightened nav padding/gaps below 740px) are what keep it fitting on one row
   instead. */

/* Case study top bar — design.md: Case study page — header & dark mode.
   Replaces the full site nav on case study pages: a way back and the theme toggle, nothing else.
   Figma 509:75529 puts the row on the same 1259px column as the homepage nav. */

/* Same width formula as .nav--home .nav__inner (--nav-width), not the page's own .content column
   (1280px inner, 80px fixed padding) — the two conventions differ by ~21px and a few px of left
   offset, which is exactly the inconsistency this was built to fix. That means the topbar no
   longer lines up with the case study body content directly beneath it; that trade is deliberate,
   made so every page's nav bar matches every other page's exactly.
   Outer <nav> is the full-bleed background layer (same split as .case-study-header /
   .case-study-header__row just above), .topbar__inner the centered content.
   Background restored (on request, after briefly going fully transparent — see design.md): before
   the page is scrolled, the sticky nav sits directly above the hero with nothing painted behind
   it but the plain page background, which in dark mode showed as a black strip between the nav
   and the hero's own accent colour rather than reading as one continuous band. Matches the hero's
   own background exactly (--color-bg here, --cs-accent in dark mode below) so there's no seam. */
.topbar {
  background: var(--color-bg);
  padding-top: var(--space-xl);
}

[data-theme="dark"] .topbar {
  --color-bg: var(--cs-accent);
  --color-text-primary: var(--cs-accent-text);
  --color-text-secondary: var(--cs-accent-text);
  background: var(--color-bg);
  color: var(--color-text-primary);
}

.topbar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: var(--nav-width);
  margin: 0 auto;
}

.topbar__back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-sans);
  font-size: var(--text-nav);
  font-weight: 400;
  color: var(--color-text-primary);
}

.topbar__back svg {
  flex: none;
  width: 20px;
  height: 20px;
}

.topbar__back:hover {
  text-decoration: underline;
}

/* Stats band — design.md: Components > Stats band */

/* The band carries the case study's accent as its surface in both themes: the accent itself in
   light, a much darker shade of it in dark. Its own tokens rather than --color-bg-band, which
   sixteen other rules share and which must stay neutral. */
.stats-band {
  background: var(--color-stats-band-bg);
  color: var(--color-stats-band-text);
  padding: var(--space-lg) 0;
}

.stats-band__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  /* Sits on the page margin like the header text above it. Figma's auto-layout adds 32px of inset
     here (80 + 32 = 112), which put the metrics out of line with the header — deliberately dropped. */
}

.stats-band__headline {
  font-family: var(--font-sans);
  font-size: var(--text-stat-label);
  font-weight: 500;
  color: inherit;
}

/* Figma draws each stat as a fixed ~408px column, left-aligned, --space-lg apart: 408 / 410 / 379
   in case study 1, 408 / 410 in case study 3. A 408px basis reproduces both. On the full 1280px
   content width three stats fit at full size (3 x 408 + 48 = 1272); narrower, they shrink to equal
   thirds. Two stats sit at full size, so the second starts at 408 + 24 rather than being stretched
   halfway across the band. */
.stats-band__row {
  display: flex;
  gap: var(--space-lg);
}

.stat {
  flex: 0 1 408px;
  min-width: 0;
}

/* Once .stats-band__row stacks at 767px the basis governs height, not width, and would hold each
   stat open at 408px tall. This has to sit after the base rule to win the cascade. */
@media (max-width: 767px) {
  .stat {
    flex: 0 1 auto;
  }
}

/* Everything in the band inherits the band's own text colour: the surface is the case study's
   accent in light and a dark shade of it in dark, so the page's text tokens do not apply here. */
.stat__number {
  font-family: var(--font-sans);
  font-size: var(--text-stat);
  font-weight: 500;
  color: inherit;
}

.stat__label {
  font-family: var(--font-sans);
  font-size: var(--text-stat-label);
  font-weight: 500;
  color: inherit;
}

.stats-band__footnote {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  font-weight: 400;
  color: inherit;
  opacity: 0.85;
}

/* 04/ IMPACT — design.md: Layout > Numbered pattern.
   Two equal 628px columns with a --space-lg gutter (628 + 24 + 628 = 1280), not the standard
   --space-column-gap split. The quote is a sibling of the lede rather than of the whole column,
   so its top aligns with the body paragraph automatically — matching Figma, where the band top
   (267) and the body top (267.5) meet. */

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

/* Figma constrains the headline so it breaks across two lines: 625px in case study 1, 949px in
   case study 2. Same role, different measure, so the cap is per-instance. */
.impact__title {
  max-width: var(--impact-title-width, 625px);
}

/* Title plus running text on one rhythm. The gap lives here rather than on .impact because
   .impact-row brings its own margin-top and the two would compound. The running text is capped
   separately from the title, which Figma sets wider. */
.impact__stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-lg);
}

.impact__stack .qa-body {
  max-width: var(--impact-text-width, none);
}

.impact-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
  margin-top: var(--space-lg);
}

.impact-row__text,
.impact-row__visual {
  flex: 1 1 0;
  min-width: 0;
}

.impact-row__lede {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  max-width: 520px;
}

.impact-row__text .field {
  margin-top: var(--space-xl);
}

/* Unbulleted 20px list. The bulleted equivalent is .field__body--emphasis (block 03's
   ANALYSIS); this shares its --text-stat-label sizing without the markers. */
/* .field__body ul sets discs, so this needs equal reach to clear them. */
.field__body .stat-list,
.stat-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  list-style: none;
  padding: 0;
}

.field__body .stat-list li,
.stat-list li {
  font-family: var(--font-sans);
  font-size: var(--text-stat-label);
  font-weight: 500;
  color: var(--color-text-primary);
}

@media (max-width: 1023px) {
  .impact-row {
    flex-direction: column;
    gap: var(--space-xl);
  }

  .impact-row__text,
  .impact-row__visual {
    width: 100%;
  }
}

/* 06/ TAKEAWAYS — two 520px columns reusing the .concern lead+body pattern from 02.
   Figma's 241px gutter is simply the remainder (520 + 241 + 520 = 1281 ~ the 1280 content
   width), so space-between produces it without introducing a value. */

.takeaways {
  display: flex;
  justify-content: space-between;
  gap: var(--space-lg);
  margin-top: var(--space-xl);
}

.takeaways .concern {
  flex: 0 1 520px;
  min-width: 0;
}

/* Case study 1 spreads its two takeaways edge to edge; case study 2 sets them on the standard
   two-column grid instead, so the right column starts at the midpoint rather than the page edge. */
.takeaways--columns {
  justify-content: flex-start;
  gap: var(--space-column-gap);
}

/* Figma sets these headings in Medium rather than the SemiBold .concern__lead uses in 02. */
.takeaways .concern__lead {
  font-weight: 500;
}

@media (max-width: 1023px) {
  .takeaways {
    flex-direction: column;
    gap: var(--space-xl);
  }

  .takeaways .concern {
    flex: 1 1 auto;
    width: 100%;
  }
}

/* "What the design thinking enables" band — case study 2's header band.
   Case study 1's stats band is a different component (48px stat numbers + mono footnote), so
   only .stats-band's background/padding is shared; the content below is new.
   Three 280px columns spaced across the content width, as Figma has them. */

.enables {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.enables__headline {
  font-family: var(--font-sans);
  font-size: var(--text-stat-label);
  font-weight: 500;
  color: var(--color-lib-ai-accent);
}

.enables__group {
  display: flex;
  flex-direction: column;
  /* 24px between the lead and the stat row below, on request — was --space-xs (4px), shared with
     every other gap in this component until now. */
  gap: var(--space-lg);
}

.enables__lead {
  font-family: var(--font-mono);
  font-size: var(--text-body);
  font-weight: 500;
  color: var(--color-lib-ai-accent);
}

.enables__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-lg);
}

.enables__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  flex: 0 1 280px;
  min-width: 0;
}

/* On request: numeral and title sit on one line ("01 Understand") instead of stacked —
   flex-direction: row (was column), baseline-aligned since --enables__num (--text-body) and
   --enables__title (--text-h3) are different sizes. Applies to all 3 items (01/Understand,
   02/Validate, 03/Trust) via this one shared rule; not theme-specific, so both modes get it. */
.enables__head {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: var(--space-sm);
}

.enables__num {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 500;
  color: var(--color-lib-ai-accent);
}

/* Figma sets this title in --color-text-secondary rather than the primary an h3 normally uses;
   now on request set to the case study's own accent colour like the rest of this band. */
.enables__title {
  font-family: var(--font-sans);
  font-size: var(--text-h3);
  font-weight: 500;
  color: var(--color-lib-ai-accent);
}

.enables__body {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 500;
  color: var(--color-lib-ai-accent);
}

@media (max-width: 1023px) {
  .enables__row {
    flex-direction: column;
  }

  .enables__item {
    flex: 1 1 auto;
    width: 100%;
  }
}

/* Chained problem sequence (case study 2, 02/ THE PROBLEM).
   Related to .compare but distinct enough to stand alone: four steps rather than three, the
   icon sits above its text instead of beside it, and arrow images separate the steps. Reuses
   .compare__label and .compare__body for the text styles. */

.chain {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
}

.chain__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  flex: 0 1 var(--chain-item-width, 250px);
  min-width: 0;
}

.chain__icon {
  flex-shrink: 0;
}

.chain__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Columns set from the left, --space-lg apart, instead of spread edge to edge — case study 3,
   block 01's two "hypothesis ruled out" columns (303 / 300 wide, 24 apart). */
.chain--start {
  justify-content: flex-start;
  gap: var(--space-lg);
}

/* Columns set from the left on the standard two-column gutter (case study 3, block 02: "move
   everything" / "move nothing", 302 and 300 wide, 133 apart in Figma). A class rather than an
   inline gap, so the stacked gap below 1024px still comes from the .chain media rule. */
.chain--split {
  justify-content: flex-start;
  gap: var(--space-column-gap);
}

/* Sits level with the icons rather than the text below them. */
.chain__arrow {
  flex-shrink: 0;
  margin-top: var(--space-lg);
}

@media (max-width: 1023px) {
  .chain {
    flex-direction: column;
    gap: var(--space-xl);
  }

  .chain__item {
    flex: 1 1 auto;
    width: 100%;
  }

  /* The horizontal arrows read as decoration once the steps stack. */
  .chain__arrow {
    display: none;
  }
}
@media (max-width: 460px) {
  .nav--home .nav__link-icon { display: none !important; }
}
