/*
  MarketGuard case study — page-local layout.

  Draft page, deliberately on its own URL so /works/finchguard/ keeps working
  untouched. Depends on, in this order:
    1. the Webflow shared stylesheet (page shell: nav, close button, corners)
    2. design-tokens.css (fonts + colour/type tokens)
    3. components.css (shared case-study patterns)
  Everything below is layout that does not exist in those three yet. When the
  structure is settled, the parts that repeat across cases move up into
  components.css.

  Column width matches the existing cases: 1170px content, edge-to-edge media.
*/

.case {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 0 120px;
  font-family: var(--font-body-brand), 'Inter', sans-serif;
  color: var(--black);
}

/* Horizontal rhythm follows the header: the text column starts exactly under
   "Kirill Bush" in the logo (66px into the 1170px column), while media keeps
   running the full column width, as on the existing cases. */
.case-body {
  padding-left: 66px;
  padding-right: 32px;
}
.case-body .case-figure {
  margin-left: -66px;
  margin-right: -32px;
}
@media screen and (max-width: 991px) {
  .case-body { padding-left: 0; padding-right: 0; }
  .case-body .case-figure { margin-left: 0; margin-right: 0; }
}

/* ---- Chapter rail --------------------------------------------------------
   Navigation down the right side, in the same visual language as the
   "What's done" list on the current cases: purple links, quiet type. It is
   sticky, so on a page this long the reader always knows where they are and
   can jump. Below 991px it is dropped — the headings themselves carry the
   structure on a narrow screen. */
.case-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 220px;
  gap: 48px;
}
/* The rail column runs the full height of the case so the sticky block inside
   it has room to travel, but the rule belongs to the block, not to the column —
   no line running off into empty page.
   padding-top lands "Chapters" on the first line of the intro paragraph:
   48px hero padding + 75.6px h1 (4.5rem × 1.05) + 24px h1 margin, plus the
   difference in half-leading between the two 20px lines (6px vs 2px). */
.case-toc { padding: 152px 0 0; }
/* Pinned at the offset it already sits at, so it does not shift the moment the
   page starts moving — the rail is a fixed point of reference, not a passenger.
   258px = where "Chapters" lands next to the first line of the intro. */
/* Positioned by script: the block is placed so "Chapters" sits on the same
   typographic line as the heading the reader is currently in — the case title
   at the top of the page, then each section heading in turn. Sticky is the
   no-JS fallback and the pre-paint position. */
.case-toc-inner {
  position: sticky;
  top: 258px;
  overflow-y: auto;
  padding-left: 24px;
  border-left: 1px solid rgba(27, 30, 36, 0.16);
}

/* The contact row starts on the chapter rail's line: "Email" is nudged right by
   the 5px it used to sit clear of the rule, and Telegram takes the same amount
   off its own gap so the row still ends where it did. Lives here, not in the
   shared header styles: only case pages draw that rule. */
.kb-links > a:first-child { margin-left: 5px; }
.kb-links > .div-block-5 { margin-left: -5px; }

.case-toc-title {
  margin: 0 0 20px;
  font-family: var(--font-headline);
  font-weight: 600;
  font-size: 20px;
  line-height: 1.2;
  color: var(--black);
}
.case-toc ol,
.case-toc ul { margin: 0; padding: 0; list-style: none; }
/* Hierarchy is carried by rhythm alone — same size, same colour on every level.
   What separates them is how much air a row has around it: rows inside a group
   sit tight, the group itself gets room beneath. */
.case-toc li { margin: 0 0 18px; }
.case-toc ol > li:last-child { margin-bottom: 0; }
/* A group needs more air beneath it than its rows need between them — below a
   ratio of about 2:1 the eye stops reading them as a group at all. */
.case-toc ol > li:has(ul) { margin-bottom: 28px; }
/* The first child needs at least as much air as the children need between them,
   otherwise it reads as a runover of the chapter title rather than a row. */
.case-toc ul { margin: 16px 0 0 14px; }
/* The gap between rows has to beat the leading inside a wrapped row, otherwise
   a two-line label glues itself to its neighbours and the list reads as prose. */
.case-toc ul li { margin: 0 0 14px; }
.case-toc ul li:last-child { margin-bottom: 0; }
.case-toc ul ul { margin: 8px 0 0 12px; }
.case-toc ul ul li { margin: 0 0 6px; }
.case-toc ul ul li:last-child { margin-bottom: 0; }
/* Block links keep both rows of a wrapped label inside one rectangle, so a long
   title cannot read as two separate entries. */
.case-toc a { display: block; }
.case-toc a {
  font-size: 15px;
  line-height: 1.25;
  color: var(--black);
  text-decoration: none;
  transition: color .15s ease-out;
}
/* Sub-chapters read at the same size as chapters — the indent alone carries the
   hierarchy. */
.case-toc a:hover,
.case-toc a.is-current { color: var(--blue-violet, #9847ff); }

@media screen and (max-width: 991px) {
  .case-grid { grid-template-columns: minmax(0, 1fr); }
  .case-toc { display: none; }
}

/* ---- Hero ---------------------------------------------------------------- */
/* The Webflow shared stylesheet colours bare h1/h2/a for the marketing pages
   (pale yellow display type on dark hero, white section headings). This page
   is text on white, so every heading and link colour is re-stated here. */
.case h1,
.case h2,
.case h3 { color: var(--black); }

/* Emphasis is carried by the display face, not by bolding the body face.
   TT Firs Neue exists in one weight (600 / DemiBold) — see design-tokens.css —
   so every `b`, `strong`, `dt` and table header switches typeface instead of
   asking the browser to synthesize a heavier Graphik. */
.case b,
.case strong,
.case dt {
  font-family: var(--font-headline);
  font-weight: 600;
  letter-spacing: 0.1px;
}
/* Two exceptions to that: a label opening a paragraph, and the headers of a
   table's columns and rows. Both sit inside running text and inside the grid of
   the table, where the display face reads as a second voice; Graphik Medium
   marks them without changing typeface mid-sentence. */
.case p b,
.case p strong,
.case th {
  font-family: var(--font-body-brand), 'Inter', sans-serif;
  font-weight: 500;
  letter-spacing: normal;
}
.case a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px; /* the browser default thickens with font size */
  text-underline-offset: 3px;
}
.case a:hover { color: var(--blue-violet, #9847ff); }
/* Trailing scroll arrow is a glyph, not one of the link's words. inline-block
   stops the parent's underline from being drawn across it — `text-decoration:
   none` alone cannot cancel a decoration inherited from an ancestor. */
.case a .link-arrow { display: inline-block; margin-left: 6px; text-decoration: none; }
.case a .keep-arrow { white-space: nowrap; }
/* Rail links are navigation, not references in prose — no underline. Restated
   here because the generic .case a rule above would otherwise win on order. */
/* Same specificity as `.case a` above, and later in the file, so the rail keeps
   its own colours: chapters violet, the one the viewport is in black. */
/* Same weight as the generic `.case a`, declared later, so the rail keeps its
   own scheme: chapters black, the chapter in view violet. */
.case .case-toc a { text-decoration: none; color: var(--black); }
.case .case-toc a:hover,
.case .case-toc a.is-current { color: var(--blue-violet, #9847ff); }

.case-hero { padding: 48px 0 72px; }
/* The air above a case title comes from the inline note the title carries: its
   box is what grows the first line box. A title with nothing to annotate keeps
   an invisible note of the same size, so both cases open on the same line at
   every width – copying the mechanism rather than measuring the gap once. */
.note.is-spacer { visibility: hidden; }
/* 13px puts the intro's first baseline 53px under the title's — the same
   baseline-to-baseline distance a section heading keeps before its paragraph,
   which is what lets the rail's first chapter land on the intro's line. */
.case-hero h1 {
  margin: 0 0 13px;
  /* Same display face as the section headings: the case title is the largest h2,
     not a different typeface. */
  font-family: var(--font-headline);
  font-weight: 600;
  font-size: 4.5rem;
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin-left: -0.25rem;
}
.case-lede {
  max-width: 720px;
  margin: 0 0 32px;
  font-size: 19px;
  line-height: 1.6;
}
/* Five facts, five lines, labels in one column so the values line up. The
   wrapper divs pair a dt with its dd for semantics; display:contents lets both
   sit directly in the grid. */
.case-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 12px 24px;
  max-width: 760px;
  padding: 0 0 40px;
  font-size: 15px;
  line-height: 23px;
  color: var(--black-72);
}
.case-facts > div { display: contents; }
.case-facts dt { font-weight: 500; color: var(--black); }
.case-facts dd { margin: 0; }

/* Result tiles: the numbers live above the fold, as text, never as an image.
*/
.results-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: rgba(27, 30, 36, 0.16);
  border: 1px solid rgba(27, 30, 36, 0.16);
}
.result-tile {
  background: var(--white);
  /* Vertical padding sits 7px under the horizontal one, so the tiles read as
     wide rather than boxy. */
  padding: 17px 20px;
}
/* Medium rather than DemiBold, and sized so the longest value ("0:57 → 3:10")
   holds one line inside a tile: broken across two lines the row loses its
   baseline and reads as four different numbers. */
.result-value {
  display: block;
  font-family: var(--font-headline);
  font-weight: 500;
  font-size: 28px;
  line-height: 1.05;
  letter-spacing: 0.5px;
  white-space: nowrap;
  font-feature-settings: 'lnum' 1, 'tnum' 1;
}
.result-label {
  display: block;
  margin-top: 10px;
  font-size: 13px;
  line-height: 19px;
  color: var(--black-72);
}
/* Same tiles, quieter: used inside a section where the numbers are evidence for
   the paragraph above them rather than the headline of the whole case. */
/* One line of framing above the compact tiles: without it the numbers float and
   the reader cannot tell when they were taken or what they are measured against. */
.case-section .results-note {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 19px;
  color: var(--black);
}
/* A second set of tiles in the same section needs air above its own caption. */
.case-section .results-note.has-gap { margin-top: 32px; }
/* Text that follows a set of tiles gets the same air as the next caption would:
   flush against the tiles it read as their footnote rather than as prose. */
.case-section .results-row + p { margin-top: 32px; }
.results-row.is-compact .result-tile { padding: 13px 16px; }
/* nowrap keeps "0:57 → 3:10" on one line, so all four labels sit on the same
   baseline; the size is the largest that still fits the narrowest tile. */
.results-row.is-compact .result-value { font-size: 22px; }
.results-row.is-compact .result-label { margin-top: 6px; }
/* Between 992 and 1199 the rail still takes its 220px while the page is already
   narrow: four tiles in a row leave no space for "0:57 → 3:10", so they fold
   into 2×2 rather than shrinking the number to unreadable. */
@media screen and (min-width: 992px) and (max-width: 1279px) {
  .results-row,
  .results-row.is-trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Fewer than four tiles: the row keeps its rhythm at two or three across. */
.results-row.is-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.results-row.is-trio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Declared after the base rule on purpose: same specificity, so order decides.
   Three tiles need the full width to hold "324 → 68 min" on one line. */
@media screen and (min-width: 640px) and (max-width: 1279px) {
  .results-row.is-trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media screen and (max-width: 639px) {
  .results-row.is-pair,
  .results-row.is-trio { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Charts ---------------------------------------------------------------
   Iteration data drawn as data, not screenshotted from a deck. One measure per
   plot — two y-scales in one chart is the mistake this avoids. Single series,
   so no legend: the title names what the line is. */
/* Charts sit on the text measure, not on the screenshot width: they are read
   like a paragraph, so their frame starts and ends where the sentences do. */
.case-body .case-figure.charts {
  /* 720px is the paragraph measure — the frame ends where the sentences do. */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
  max-width: 720px;
  margin-left: 0;
  margin-right: 0;
}
/* One card per measure: two scales never share a frame, so they cannot be read
   as one plot. */
.chart-card {
  padding: 20px 20px 12px;
  border: 1px solid rgba(27, 30, 36, 0.16);
}
/* The paragraph that follows needs its own air: the caption belongs to the
   figure, and without this the two run together. */
.case-figure.charts + p { margin-top: 28px !important; }
.chart-title {
  margin: 0 0 12px !important;
  font-size: 13px !important;
  line-height: 19px !important;
  color: var(--black-72);
}
.mini-chart { display: block; width: 100%; height: auto; overflow: visible; }
.mini-chart .grid line { stroke: rgba(27, 30, 36, 0.12); stroke-width: 1; }
.mini-chart .line { fill: none; stroke: var(--blue-violet, #9847ff); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* The 2px surface ring keeps a marker readable where it sits on the line. */
.mini-chart .dots circle { fill: var(--blue-violet, #9847ff); stroke: var(--white); stroke-width: 2; }
.mini-chart text { font-family: var(--font-body-brand), 'Inter', sans-serif; }
.mini-chart text.v { font-size: 13px; font-weight: 500; fill: var(--black); }
.mini-chart text.s { font-size: 11px; fill: var(--black-72); }
.charts figcaption { grid-column: 1 / -1; margin-top: 0; }

@media screen and (max-width: 767px) {
  .case-body .case-figure.charts { grid-template-columns: minmax(0, 1fr); }
}

/* Three versions of the same widget, one under the other at native width so the
   detail stays sharp, each with its own line instead of one caption for all. */
.case-body .case-figure.versions {
  display: grid;
  gap: 36px;
  /* The last caption sits close to its screen, so the paragraph after the
     figure needs the air instead. */
  margin-bottom: 28px;
  /* Same width as the screenshots above in this section: full text area, edges
     level with the Trading portal figures. */
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding: 0;
}
/* Overlay chart: both measures in one frame, on a modular grid like the source
   board. No axis is drawn — the pair under each step carries the value, the
   lines carry direction only, so nothing invites reading height as magnitude. */
.case-body .case-figure.overlay {
  max-width: 720px;
  margin-left: 0;
  margin-right: 0;
  /* Extra air under the value pairs: without it the labels read as the first
     line of the paragraph that follows. */
  margin-bottom: 12px;
  padding: 0;
}
/* The key is drawn inside the plot, in the corner of the grid where no line
   runs — nothing above the chart competing with the section's own type. */
.overlay-plot .k-line { stroke-width: 3; stroke-linecap: round; }
.overlay-plot .k-line.k-time { stroke: var(--blue-violet, #9847ff); }
.overlay-plot .k-line.k-ctr { stroke: #12855f; }
.overlay-plot .k-text { font-size: 12px; fill: var(--black-72); }
.overlay-plot { display: block; width: 100%; height: auto; overflow: visible; }
.overlay-plot .grid line { stroke: rgba(27, 30, 36, 0.1); stroke-width: 1; }
.overlay-plot .line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.overlay-plot .dots circle { stroke: var(--white); stroke-width: 2; }
.overlay-plot .s-time .line { stroke: var(--blue-violet, #9847ff); }
.overlay-plot .s-time .dots circle { fill: var(--blue-violet, #9847ff); }
.overlay-plot .s-ctr .line { stroke: #12855f; }
.overlay-plot .s-ctr .dots circle { fill: #12855f; }
.overlay-plot text { font-family: var(--font-body-brand), 'Inter', sans-serif; }
.overlay-plot text.step { font-size: 11px; fill: rgba(27, 30, 36, 0.5); }
.overlay-plot text.pair { font-size: 14px; font-weight: 500; }
.overlay-plot .t-time { fill: var(--blue-violet, #9847ff); }
.overlay-plot .t-ctr { fill: #12855f; }
.overlay-plot .t-sep { fill: rgba(27, 30, 36, 0.4); }
.overlay figcaption { margin-top: 14px; }

/* Screen left, its two numbers right: each version states where both measures
   stood at that step, so the reader never scrolls back to a shared chart. */
/* Screen left, everything that describes it right: caption, the two numbers and
   the theses share one column, so the image keeps its full height and the text
   reads as a single note beside it. */
.version {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 160px;
  /* Screen and its metrics share the first row, so the numbers can finish level
     with the bottom of the image; the caption sits under the screen only. */
  grid-template-rows: auto auto;
  column-gap: 28px;
  row-gap: 0;
  align-items: start;
}
.version img { grid-column: 1; grid-row: 1; }
.version img { display: block; width: 100%; border: 1px solid rgba(27, 30, 36, 0.16); }
/* Two states of the same screen, stacked. The top one is cut by a hard line that
   sits at the middle of the viewport, so scrolling wipes one frame into the
   other — no fade, no blur, the seam is the scroll position itself. */
.swap { position: relative; }
.swap .swap-top {
  position: absolute;
  inset: 0;
  /* JS drives --cut; before it runs the collapsed frame covers nothing. */
  clip-path: inset(0 0 var(--cut, 100%) 0);
}
@media (prefers-reduced-motion: reduce) {
  .swap .swap-top { clip-path: inset(0 0 100% 0); }
}
/* The caption belongs to the screen, so it sits under the screen's column only
   — 8px below it, centred on the image rather than on the whole row. */
.version-note { grid-column: 1; grid-row: 2; text-align: center; }
/* Each version explains itself right under its own screen, instead of the whole
   set being narrated in one paragraph above them. */
/* The screen is figure-width, but its explanation is prose: it returns to the
   text column, so paragraphs across the section share one left edge. */
/* The explanation is bound to its screen, so it takes the screen's column and
   its width — not the wider text measure. */
.version-text {
  grid-column: 1;
  grid-row: 2;
  margin: 12px 0 0;
}
.version-text p { margin: 0 !important; }
/* The version number is the label of the paragraph it opens, so it carries a
   little more weight than the sentence around it. */
.vn { font-weight: 500; }
.version-text .lead-in { margin-bottom: 8px !important; }
.version-text .version-problems { margin-bottom: 0; }
/* The pair sits on the screen's baseline, not its top edge: the numbers are the
   outcome of what the screenshot shows, so they land where it ends. */
/* The whole group finishes level with the screenshot: the trace and the two
   values read as the outcome of what the screen shows. */
.version-stats {
  grid-column: 2;
  grid-row: 1;
  /* Full height of the screenshot: chart at the top, values at the bottom, and
     the space between them is whatever is left over. */
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.version-stats .spark { margin-bottom: auto; overflow: visible; }
/* No frame and no padding around a measure: the trace, the value and the label
   are already a group, and a box here only added height the screen had to match. */
.vstat { padding: 0; border: 0; }
/* The trace spans its whole column, so its right edge lines up with the right
   edge of the screenshots — the section reads on one vertical. */
/* overflow visible so the end markers hang over the frame instead of being cut
   in half — the frame is a rail, not a crop. */
.spark { display: block; width: 100%; height: auto; overflow: visible; }
.spark .vgrid line { stroke: rgba(27, 30, 36, 0.12); stroke-width: 1; }
.spark .line { fill: none; stroke: rgba(27, 30, 36, 0.22); stroke-width: 1; stroke-linejoin: round; stroke-linecap: round; }
.spark .dots .off { fill: currentColor; }
.spark .dots .on { stroke: var(--white); stroke-width: 2; }
/* Both measures share one trace per version: violet time, green CTR, with the
   step this screen belongs to inked on each line. */
/* Solid tints rather than alpha: where the two traces cross, neither shows
   through the other. */
.spark .s-time { color: #d7bafc; }
.spark .s-ctr { color: #b0cfc1; }
.spark .s-time .line { stroke: #d7bafc; }
.spark .s-ctr .line { stroke: #b0cfc1; }
.spark .s-time .dots .on { fill: var(--blue-violet, #9847ff); }
.spark .s-ctr .dots .on { fill: #12855f; }
/* Where one line cannot carry the diagnosis, the version states it as theses —
   under its own screen, in the same column. */
.case-section .lead-in { margin-bottom: 8px !important; }
.version-problems {
  max-width: 720px;
  margin: 0 0 24px;
  padding-left: 20px;
  font-size: 16px;
  line-height: 1.65;
}
.version-problems li { margin: 0 0 2px; }
.version-problems li { margin: 0 0 4px; }
.version-problems li:last-child { margin-bottom: 0; }
.vstat-value {
  display: block;
  font-family: var(--font-headline);
  font-weight: 500;
  font-size: 22px;
  line-height: 1.1;
  letter-spacing: 0.5px;
  white-space: nowrap;
  font-feature-settings: 'lnum' 1, 'tnum' 1;
}
/* Same colour coding as the chart: violet is time, green is CTR, so a value
   beside a screen is recognised without reading the label under it. */
.vstat-value.is-time { color: var(--blue-violet, #9847ff); }
.vstat-value.is-ctr { color: #12855f; }
.vstat-label {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  line-height: 19px;
  color: var(--black-72);
}

@media screen and (max-width: 767px) {
  .version { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto; }
  .version img { grid-column: 1; grid-row: auto; }
  .version-note,
  .version-stats,
  .version-problems { grid-column: 1; grid-row: auto; }
  .version-note { margin-top: 10px !important; }
  .version-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.version-note {
  margin: 8px 0 0 !important;
  font-size: 13px !important;
  line-height: 19px !important;
  color: var(--black-72);
}

/* ---- Text sections ------------------------------------------------------- */
/* scroll-margin keeps a heading clear of the viewport edge when the rail jumps
   to it, and matches the offset the animated scroll uses. */
.case-section { padding: 72px 0 0; scroll-margin-top: 216px; }
.case-section h3 { scroll-margin-top: 40px; }
#personas { scroll-margin-top: 40px; }
.case-section h2 {
  margin: 0 0 24px;
  font-family: var(--font-headline);
  font-size: 40px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: 0.4px;
}
.case-section h3 {
  margin: 68px 0 12px;
  font-family: var(--font-headline);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0.2px;
}
.case-section p {
  max-width: 720px;
  margin: 0 0 18px;
  font-size: 16px;
  line-height: 1.65;
}

/* ---- Insight: the repeating unit of the narrative ------------------------ */
.insight {
  border-top: 1px solid rgba(27, 30, 36, 0.16);
  padding: 39px 0 4px;
}
/* The section already gives h3 68px of air; inside an insight the rule above it
   does that job, so the heading sits close to its own line. */
/* Heading sits on the rule's own air above, and gives the rows below a little
   more room than a section heading needs. */
.insight h3 { margin-top: 0; margin-bottom: 20px; }
/* Three rows of one thought, not three blocks: the rows sit close enough to read
   as a single unit, and the air is spent between insights instead. */
.insight-step {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: 6px 32px;
  padding: 0 0 10px;
}
.insight-step dt:not(:first-of-type),
.insight-step dd:not(:first-of-type) { padding-top: 10px; }
.insight-step dt {
  font-size: 13px;
  line-height: 20px;
  color: var(--black-72);
}
.insight-step dd {
  margin: 0;
  max-width: 660px;
  font-size: 16px;
  line-height: 1.6;
}

/* ---- Media --------------------------------------------------------------- */
.case-figure { margin: 32px 0 40px; }
/* Caption sits close to its image, so the gap to the next paragraph has to come
   from the figure itself — otherwise the text crowds the caption. Consecutive
   figures keep a single gap between them instead of stacking both margins. */
.case-figure + .case-figure { margin-top: 0; }
.case-figure img { display: block; width: 100%; height: auto; }
.case-figure figcaption {
  /* auto side margins keep the caption centred on its frame even when the
     line-shaping script narrows it */
  margin: 12px auto 0;
  padding: 0;
  font-size: 13px;
  line-height: 19px;
  color: var(--black-72);
}

/* ---- Research tables -----------------------------------------------------
   The whole point of the appendix: artifacts as real text. Wide tables scroll
   inside their own container so the page body never scrolls sideways. */
.case-eyebrow { margin: 0 0 16px; font-size: 13px; color: var(--black-72); }
/* A table that fits is not a scroll container at all — no scrollbar, no rubber
   banding, nothing for the trackpad to grab. The script adds .is-pannable only
   when the table is genuinely wider than its box. */
.table-scroll { overflow: clip; margin: 24px 0 0; }
.table-scroll.is-pannable { overflow-x: auto; overflow-y: hidden; }
/* Tables are sized to the column, not to a fixed width: on desktop they must
   fit, so no inner scrollbar appears. The scroll container is kept for phones,
   where a four-column table genuinely has to scroll. */
.research-table {
  border-collapse: collapse;
  width: 100%;
  table-layout: fixed;
  font-size: 14px;
  line-height: 1.5;
  text-align: left;
}
.research-table td,
.research-table th { overflow-wrap: break-word; }
@media screen and (max-width: 767px) {
  .research-table { min-width: 640px; }
}
.research-table caption {
  padding: 0 0 16px;
  font-size: 13px;
  line-height: 19px;
  color: var(--black-72);
  text-align: left;
}
.research-table th,
.research-table td {
  border: 1px solid rgba(27, 30, 36, 0.16);
  padding: 14px 16px;
  vertical-align: top;
}
.research-table thead th {
  font-size: 16px;
  font-weight: 500;
}
.research-table tbody th {
  width: 160px;
  font-weight: 500;
  color: var(--black);
  background: rgba(27, 30, 36, 0.03);
}
.research-table ol { margin: 0; padding-left: 18px; }
/* Some cells open with a line that names what the items below are; it is body
   text, not a heading, and only needs a little air before the list. */
.research-table .cell-lead { display: block; margin-bottom: 4px; }
.research-table li { margin: 0 0 6px; }
.research-table li:last-child { margin-bottom: 0; }

.plain-list { max-width: 720px; margin: 0 0 18px; padding-left: 18px; font-size: 16px; line-height: 1.65; }
.plain-list li { margin: 0 0 8px; }

/* Artifacts are all tables — see .research-table. Two-column guides (interview
   questions, metric definitions) use .guide: narrower index column and a lower
   min-width so the table doesn't force sideways scroll on mid-size viewports. */
/* Three columns (script section / question / purpose), so 720px minimum. */

/* The script-section column uses rowspan and needs real width for label text. */


/* Metric definitions: the term column is wider, description takes the rest. */


/* ---- Recommendation ------------------------------------------------------
   Rebuilt from the LinkedIn screenshot that used to carry it. Same anatomy —
   photo, name, standing, date, relationship, quote — but as text, so the name
   links out and the date is machine-readable. The source row on top carries the
   LinkedIn mark, so the card still reads as a LinkedIn recommendation once the
   screenshot chrome is gone. */
.recommendation {
  display: flex;
  gap: 20px;
  max-width: 820px;
  margin: 32px 0 0;
  padding: 28px 28px 24px;
  border: 1px solid rgba(27, 30, 36, 0.16);
  border-radius: 12px;
}
.recommendation-avatar {
  flex-shrink: 0;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  object-fit: cover;
}
.recommendation-body > p { margin: 0; }
.recommendation-source {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 10px !important;
  font-size: 13px;
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--black-72);
}
.recommendation-source svg { flex-shrink: 0; }
.recommendation-source a { color: inherit; }
.recommendation-name { font-family: var(--font-headline); font-weight: 600; font-size: 17px; line-height: 1.3; }
.recommendation-role { font-size: 14px; line-height: 1.4; color: var(--black-72); }
.recommendation-meta { margin-top: 2px !important; font-size: 13px; line-height: 1.4; color: var(--black-72); }
.recommendation-quote { margin: 16px 0 0; padding: 0; border: 0; }
/* The opening quote hangs into the margin so the first letter lines up with the
   name below it — text-indent does the work everywhere, hanging-punctuation is
   the native version for the browsers that have it. */
.recommendation-quote p {
  margin: 0;
  max-width: 640px;
  font-size: 16px;
  line-height: 1.6;
  text-indent: -0.42em;
  hanging-punctuation: first;
}

/* Compact cut for the hero: the reference stands next to the result tiles, so
   it has to read as a caption to them rather than as a section of its own —
   smaller portrait, quote first, one line of attribution under it. */
.recommendation.is-compact { gap: 16px; max-width: 820px; padding: 20px 24px; }
.recommendation.is-compact .recommendation-avatar { width: 40px; height: 40px; }
.recommendation.is-compact .recommendation-quote { margin: 0 0 16px; }
.recommendation.is-compact .recommendation-quote p { font-size: 15px; line-height: 1.5; }
.recommendation.is-compact .recommendation-name { font-size: 15px; }
.recommendation.is-compact .recommendation-role { font-size: 13px; }
.recommendation.is-compact .recommendation-role a { color: inherit; }

@media screen and (max-width: 600px) {
  .recommendation { flex-direction: column; gap: 16px; padding: 20px; }
}

/* ---- Draft markers -------------------------------------------------------
   Visible on purpose: this page is a work in progress and every gap should be
   impossible to miss while reviewing it. All .todo blocks go away before the
   page replaces /works/finchguard/. */
.todo {
  margin: 16px 0;
  padding: 16px 20px;
  border: 1px dashed #9847ff;
  border-radius: 8px;
  background: rgba(152, 71, 255, 0.05);
  font-size: 14px;
  line-height: 1.55;
  color: #5b2ea6;
}
/* .todo strong is covered by the .case strong rule above (headline face, 600). */

@media screen and (max-width: 991px) {
  .results-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* No horizontal padding of our own here: the Webflow shell already insets
     .content at these widths, and adding to it doubled the margin. */
  .case { padding-left: 0; padding-right: 0; }
  .case-hero h1 { font-size: 3rem; }
  .insight-step { grid-template-columns: 1fr; gap: 4px; }
  .result-value { font-size: 36px; }
}

/* Journey map: six steps as rows, the four lanes as columns — transposed from
   the board, where it is six columns wide and cannot fit a reading column. */

.research-table.cjm ol { margin: 0; padding-left: 16px; }
.research-table.cjm li { margin: 0 0 8px; }
.research-table.cjm li:last-child { margin-bottom: 0; }

/* ---- Research stages card ------------------------------------------------
   HTML rebuild of the Figma card (node 2665:30496) that used to ship as a
   raster. Numbered badges come from components.css (.step-badge); only the
   card frame and the row live here. Blue sampled from the original export. */
.stages {
  margin: 24px 0 8px;
  padding: 8px 0 28px;
}
.stages-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  justify-items: center;
}
.stages-row .step-badge { width: 100%; }
@media screen and (max-width: 767px) {
  .stages-row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 16px; }
}

/* ---- Two-column split (tasks / questions) --------------------------------
   HTML rebuild of the A/B testing block (Figma node 2665:30497). Pills come
   from components.css (.pill); the divider rule matches the original. */
.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 40px;
  margin: 32px 0 0;
}
/* Columns stretch to the same height so the logo row can sit on the bottom edge
   of the taller list, where it used to be. */
.split-col { display: flex; flex-direction: column; }
.split-col + .split-col { padding-left: 40px; }
/* The rule divides the bubbles, nothing else: it belongs to the list, not to the
   whole column, so it stops where the questions stop. */
.split-col + .split-col .pill-list {
  margin-left: -40px !important;
  padding-left: 40px;
  border-left: 1px solid rgba(27, 30, 36, 0.16);
}
/* Logos keep their old vertical position (bottom of the tasks list) and align to
   the right edge of the column they now live in. */
.split-note.is-logos { margin-top: auto !important; margin-bottom: 0 !important; }
/* Aligned with the text inside the bubbles above, not with the column edge —
   20px is the pill's own horizontal padding. */
.split-note.is-logos .tool-logos { margin: 20px 0 4px 20px; }
.split-title {
  margin: 0 0 16px !important;
  font-family: var(--font-headline);
  font-weight: 600;
  font-size: 16px;
  letter-spacing: 0.2px;
}
.pill-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.pill-list .pill { padding: 12px 20px; font-size: 14px; line-height: 1.4; }
/* The stack has no bottom margin of its own, so whatever follows it crowds the
   last pill. Only add the gap when text actually follows — inside split columns
   the list is usually the last child and needs nothing. */
.pill-list + p, .pill-list + h3, .pill-list + h2 { margin-top: 28px !important; }
.split-note { margin: 20px 0 0 !important; font-size: 14px; line-height: 1.6; color: var(--black-72); }
/* The outcome of the runs sits under both columns, so it needs its own air. */
.split + p { margin-top: 32px !important; }
@media screen and (max-width: 767px) {
  .split { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .split-col + .split-col { padding-left: 0; border-left: 0; }
}

/* The journey map is genuinely wide — six steps against four lanes — so it keeps
   its own sideways scroll instead of being squeezed into the reading column.
   Grab cursor and pointer-drag make that scroll discoverable. */
/* Four columns instead of five, so this one fits the reading width sooner. */
.research-table.cjm.inproduct { min-width: 980px; }

.research-table.cjm { min-width: 1180px; }

/* Segmented journey map: one map per buyer, on the same board and the same six
   steps, so they belong in one block the reader switches between rather than
   three tables stacked down the page. The tab row borrows the underline the
   original board used to mark the segment in view. */
.segment-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  margin: 24px 0 0;
  border-bottom: 1px solid rgba(27, 30, 36, 0.16);
}
.segment-tab {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 0 0 10px;
  font-family: var(--font-body-brand), 'Inter', sans-serif;
  font-size: 15px;
  line-height: 1.4;
  color: var(--black-72);
  cursor: pointer;
}
.segment-tab:hover { color: var(--black); }
.segment-tab[aria-selected="true"] {
  color: var(--black);
  border-bottom-color: var(--blue-violet);
}
.segment-tab:focus-visible { outline: 2px solid var(--blue-violet); outline-offset: 3px; }
.segment-panel[hidden] { display: none; }
/* No caption inside a panel: the selected tab already names the segment, and a
   caption repeating it read as a second, longer label. With the caption gone the
   table sits directly on the tab row, which then acts as its top edge — so the
   table drops its own top border and the two top corners stay square. */
.segment-panel .table-scroll { margin-top: 0; }
.segment-panel .research-table thead th { border-top-width: 0; }
.segment-panel .research-table thead th:first-child,
.segment-panel .research-table thead th:last-child { border-top-left-radius: 0; border-top-right-radius: 0; }
.table-scroll.draggable { cursor: grab; overscroll-behavior-x: contain; }
.table-scroll.draggable.is-grabbing { cursor: grabbing; user-select: none; }

/* Product video sits in the media column like any figure. */
/* 1280x720 source: the ratio is declared so the block reserves its exact height
   before the first frame arrives, with no grey band left over from a poster. */
.case-video { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; border-radius: 6px; }



/* Keyboard focus outlines are suppressed along with tab access (see the script
   at the end of the page) — no element on this page is tab-selectable. */
.case :focus { outline: none; }


/* ---- Video with sound control -------------------------------------------
   The product video autoplays muted (browsers require it); the control gives
   the sound back, as on the current case page. */
.video-figure { position: relative; }
.video-mute {
  position: absolute;
  right: 20px;
  bottom: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.48);
  color: #fff;
  cursor: pointer;
  transition: background .15s ease-out;
}
.video-mute:hover { background: rgba(0, 0, 0, 0.72); }
.video-mute svg { display: block; }
.video-mute .icon-on { display: none; }
.video-mute[aria-pressed="false"] .icon-on { display: block; }
.video-mute[aria-pressed="false"] .icon-off { display: none; }

/* Journey map: the step column is the map's index, so it stays put while the
   lanes scroll under it. Backgrounds are solid (not the translucent tint used
   elsewhere) or the scrolling cells would show through. */
/* One standard index column across every table, so the frozen first column
   reads the same everywhere and at every width. */
.research-table thead th:first-child { width: 160px; }
/* The index column is pinned again on tables that have to scroll sideways. With
   separated borders the cell carries its own edges, so nothing has to be
   redrawn as pseudo-elements this time. */
.research-table th:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: #f2f2f2;
}
.research-table thead th:first-child { z-index: 3; background: #f7f7f7; }

/* Story map and sitemap: wide by nature, same treatment as the journey map —
   own sideways scroll, first column frozen as the index. */
.research-table.usm { min-width: 1560px; }
.research-table.ia-table { min-width: 1720px; }
.research-table.usm thead th,
.research-table.ia-table thead th { font-size: 15px; }

/* Table captions sit outside the scrolling box, so they stay put while the
   table pans sideways. */
/* Scoped to the section, or `.case-section p` outranks it and the caption is
   set at body size — which is how it came to read as a faded paragraph rather
   than as a label. Solid ink, not 72% of it: at this size the alpha thinned the
   strokes instead of quieting the line, and the size already does the quieting. */
.case-section .table-caption {
  max-width: 720px;
  margin: 24px 0 12px !important;
  font-size: 13px;
  line-height: 19px;
  color: var(--black);
}
.table-scroll { margin-top: 0; }



/* The rail's title doubles as a link back to the top of the case — a link, but
   still the title, so it keeps the title's type. */
.case .case-toc-title a {
  color: inherit;
  text-decoration: none;
  font: inherit;
  letter-spacing: inherit;
}
/* Only the arrow takes the accent on hover — the label itself stays black so
   the rail's top line does not flash colour every time the cursor passes. */
.case .case-toc-title a:hover { color: inherit; }
.case .case-toc-title a:hover .toc-up-arrow { color: var(--blue-violet, #9847ff); }

/* Video: the control belongs to the video box, and the box is the width of the
   text column rather than the full media column. */
.case-body .video-figure { margin-left: 0; margin-right: 0; }
.video-box { position: relative; }
.video-figure { position: static; }


/* Pills: never wider than their column, and the radius follows the shape.
   A one-line pill reads as a capsule (half its height); once the text wraps and
   the pill becomes a block, that same radius looks like a mistake, so it drops
   to a corner radius proportional to the smaller box. */
.pill-list .pill { max-width: 100%; border-radius: 22px; }
@media screen and (max-width: 767px) {
  .pill-list .pill { border-radius: 12px; padding: 10px 14px; }
  .split-col + .split-col { padding-left: 0; }
}

/* Frozen column: the two vertical rules are drawn by the pseudo-elements only.
   The cell's own collapsed borders are switched off on both sides (and on the
   neighbour's facing edge), otherwise the real border and the drawn one stack
   a pixel apart and that column reads darker than every other line. */

/* Tablet and down: the index column drops 30% off the 200px standard, so the
   table's own content keeps more of the little width there is. */
@media screen and (max-width: 991px) {
  .research-table thead th:first-child,
  .research-table tbody th { width: 140px; }
}

/* Phone: 12px tighter again on the frozen column — 128px. Tablet keeps 140px. */
@media screen and (max-width: 767px) {
  .research-table thead th:first-child,
  .research-table tbody th { width: 128px; }
}

/* Tablet and down: the header block sits over the case title's left edge
   instead of being indented 12px further in by the shell's own wrappers. */
@media screen and (min-width: 768px) and (max-width: 991px) {
  .body .content > nav.head .kb-line { margin-left: -12px; }
}


/* Rail title: reads "Chapters" at rest and turns into "Intro" on hover, which
   is what the click does — scroll back to the top of the case. */
/* Both labels occupy the same grid cell, so the link is always as wide as the
   longer of the two. Swapping with display:none resized the box under the
   pointer, which un-hovered it and made the label flicker. */
.case-toc-title a { display: inline-grid; cursor: default; }
.case-toc-title a > span { grid-area: 1 / 1; }
.case-toc-title .label-hover { visibility: hidden; }
/* The swap only exists once the page has actually scrolled — at the top there
   is nowhere to go back to, so the title is just a title. */
.is-scrolled .case-toc-title a { cursor: pointer; }
.is-scrolled .case-toc-title a:hover .label-rest { visibility: hidden; }
.is-scrolled .case-toc-title a:hover .label-hover { visibility: visible; }
/* From the first chapter down the swap is permanent: the rail names the chapter
   the reader is in, so "Chapters" has nothing left to say and the row carries
   the trip back to the top instead. */
.is-in-chapter .case-toc-title a { cursor: pointer; }
.is-in-chapter .case-toc-title .label-rest { visibility: hidden; }
.is-in-chapter .case-toc-title .label-hover { visibility: visible; }

/* Figures whose artwork is trimmed of its own margins sit in the text column,
   with the surrounding white space coming from the layout instead of pixels. */
.case-body .case-figure.in-text {
  margin-left: 0;
  margin-right: 0;
  max-width: 720px;
}

/* Screens exported from Figma with their own white field (node 2670:30631):
   the margin around the artwork is part of the export, so the figure adds
   nothing of its own and just spans the content width. */
.case-body .case-figure.in-text {
  max-width: none;
  margin-left: -66px;
  margin-right: -32px;
  padding: 0 32px;
  background: var(--white);
}
/* White field left and right comes from the layout; the block itself spans the
   full content width, so the artwork is inset by that padding. */
.case-figure.in-text img { width: 100%; }
.case-figure.in-text figcaption { padding-left: 0; padding-right: 0; }

/* Artwork that already fills its own frame edge to edge: no CSS field around
   it, just the content width. */
.case-body .case-figure.to-content {
  margin-left: -66px;
  margin-right: -32px;
}
.case-figure.to-content img { width: 100%; }

/* Table scrollbars: the track blends into the page, only the thumb shows. */
.table-scroll { scrollbar-width: thin; scrollbar-color: rgba(27, 30, 36, 0.28) transparent; }
.table-scroll::-webkit-scrollbar { height: 10px; }
.table-scroll::-webkit-scrollbar-track { background: transparent; }
.table-scroll::-webkit-scrollbar-thumb {
  background: rgba(27, 30, 36, 0.28);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: 999px;
}
.table-scroll::-webkit-scrollbar-thumb:hover { background: rgba(27, 30, 36, 0.44); background-clip: content-box; }

/* Screens whose export is trimmed to the artwork: the white field around them
   is layout: an even 32px on every side, so the screenshot sits centred in the
   column rather than hanging off the text line. */
.case-body .case-figure.field {
  max-width: none;
  /* Inside the text area, not hanging out of it: same left and right edge as
     the paragraphs and the result tiles. */
  margin-left: 0;
  margin-right: 0;
  /* No CSS field and no rounding: the screenshots sit straight in the column,
     exactly as they come out of Figma. */
  padding: 0;
}
.case-figure.field img { width: 100%; }

/* Captions belong to the desktop screen, not to the whole export: where a
   phone mock sits beside it, --desk carries the desktop's share of the image
   width (measured from the transparent gutter between them) and the caption
   stops there. */
/* Captions are centred under the shot they belong to, everywhere. */
.case-figure figcaption {
  max-width: var(--desk, 100%);
  text-align: center;
}

/* Compositions assembled in markup instead of shipped as one flattened export:
   desktop and phone are separate images in a grid, so the caption belongs to
   the desktop column and the gap between image and text is the standard one. */
.case-body .case-figure.shots {
  display: grid;
  /* One standard split for every website preview: the desktop shot is the same
     width in all of them, the phone takes the fixed remainder. */
  grid-template-columns: minmax(0, 1fr) 154px;
  column-gap: 24px;
  /* Rows are sized by the images; the phone is nudged by script so its centre
     matches the desktop's, and the caption never enters that calculation. */
  align-items: start;
  margin-left: 0;
  margin-right: 0;
  max-width: none;
}
/* Desktop and its caption are one block, so the caption never waits for a
   taller phone to finish. */
.shots .shot-main { grid-column: 1; grid-row: 1; min-width: 0; }
.shots .shot-desktop { display: block; width: 100%; }
/* The caption belongs to the desktop shot: the row is sized by the desktop, and
   a taller phone scales down to that height instead of pushing the text away. */
/* Phone stays in the first row: spanning both rows let a taller phone push the
   caption away from the desktop it belongs to. */
.shots .shot-phone {
  grid-column: 2;
  grid-row: 1;
  width: 100%;
  align-self: start;
  position: relative;
}
/* A dialog that floats over the desktop screen ships as its own image instead
   of being flattened into the shot: the desktop keeps the standard column width
   in every composition, the caption sits under the desktop alone, and the panel
   hangs into the same right-hand strip a phone would occupy. Position and size
   are percentages of the desktop, so the collage holds at any width. */
.shots .shot-stack { position: relative; }
.shots .shot-stack .shot-desktop { display: block; width: 100%; }
.shots .shot-over {
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
}
/* A panel can instead be centred on the phone column it hangs in, whatever its
   own width: 100% is the desktop's right edge, then the 24px gap and half of
   the 154px column, less half the panel. --l is unused on these. */
.shots .shot-over.is-col-centred { left: calc(100% + 101px - var(--w) / 2); }

/* An inlay sits back into the still it was cut from, so it keeps its place at
   every width — unlike .shot-over, which is a panel hanging off the edge and
   tucks in on narrow screens. */
.shots .shot-inlay {
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
}
@media screen and (max-width: 767px) {
  /* One column, no strip to hang into: the panel tucks against the right edge of
     the desktop instead, so the desktop keeps its standard full-column width. */
  .shots .shot-over,
  .shots .shot-over.is-col-centred {
    left: auto;
    right: 0;
    width: calc(var(--w) * 0.8);
  }
}

/* The FAQ page's own section nav is shown doing what it does live: it rides
   along its rail while the page scrolls past. The span marks the rail – the
   empty column inside the shot, given as percentages of the desktop image – and
   the card inside it is sticky, so it travels and then releases at the end of
   the range. Recovered from the Webflow project, where the rule existed but the
   element was never published. */
.shots .shot-stick {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  height: var(--h);
  pointer-events: none;
}
.shots .shot-stick img {
  position: sticky;
  top: 120px; /* clear of the pinned contact links */
  display: block;
  width: 100%;
}
@media screen and (max-width: 767px) {
  /* The shot is a third of its desktop size here – the card would cover it. */
  .shots .shot-stick { display: none; }
}

/* The phone column can hold more than one screen – two steps of the same flow
   stack inside it, and the column as a whole is what gets centred. */
.shots .shot-phone > img { display: block; width: 100%; }
.shots .shot-phone > img + img { margin-top: 16px; }
.shots figcaption {
  max-width: none;
  padding: 0;
  margin: 12px auto 0;
  text-align: center;
}
@media screen and (max-width: 767px) {
  .case-body .case-figure.shots { grid-template-columns: minmax(0, 1fr); row-gap: 16px; }
  /* A phone shot shown on a phone is a screen inside a screen – it reads as a
     thumbnail of nothing. On this width the composition keeps the desktop only;
     the pairing is what a wide screen is for. */
  .shots .shot-phone { display: none; }
}

/* 6px outer radius on the table itself, not on the scroll box: clipping the
   box shaved the table's own edges. Separated borders let the corner cells
   round; each cell draws only its right and bottom edge, and the first
   column/header row add back the outer left and top, so nothing doubles. */
.research-table {
  border-collapse: separate;
  border-spacing: 0;
}
.research-table th,
.research-table td { border-width: 0 1px 1px 0; }
.research-table tr > *:first-child { border-left-width: 1px; }
.research-table thead th { border-top-width: 1px; }
.research-table thead th:first-child { border-top-left-radius: 6px; }
.research-table thead th:last-child { border-top-right-radius: 6px; }
.research-table tbody tr:last-child > *:first-child { border-bottom-left-radius: 6px; }
.research-table tbody tr:last-child > *:last-child { border-bottom-right-radius: 6px; }


/* Working notes: a quiet marker in the flow, with the text on hover, so an
   unfinished passage no longer shouts louder than the case itself. */
.note { position: relative; display: inline-block; vertical-align: middle; margin: 4px 0; }
.note-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: 1px solid rgba(27, 30, 36, 0.28);
  border-radius: 50%;
  font-family: var(--font-headline);
  font-size: 10px;
  line-height: 1;
  color: var(--black-72);
  cursor: help;
}
.note:hover .note-dot { border-color: var(--blue-violet, #9847ff); color: var(--blue-violet, #9847ff); }
.note-body {
  position: absolute;
  left: 24px;
  top: -10px;
  z-index: 30;
  display: none;
  width: max-content;
  /* ~60 characters a line: long enough to read in one pass, short enough to
     stay a tooltip. Full-contrast body text — 72% grey at 13px was the reason
     these were unreadable. */
  max-width: 340px;
  padding: 14px 16px 15px;
  border: 1px solid rgba(27, 30, 36, 0.12);
  border-radius: 8px;
  background: var(--white);
  box-shadow: 0 6px 20px rgba(27, 30, 36, 0.1);
  font-family: var(--font-body-brand), 'Inter', sans-serif;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--black);
  text-align: left;
  white-space: normal;
}
.note:hover .note-body { display: block; }
.note-body strong {
  display: block;
  margin-bottom: 2px;
  font-family: var(--font-headline);
  font-weight: 600;
  color: var(--black);
}
/* Unfilled insight rows read as placeholder text, not as an alarm. */
.insight-step dd.dim { color: rgba(27, 30, 36, 0.4); }

/* The marker rides with the heading of the section it comments on. */
.case-section h2 .note,
.case-section h3 .note { margin-left: 10px; vertical-align: middle; }
.case-section h2 .note-dot,
.case-section h3 .note-dot { font-size: 10px; font-weight: 600; }
.case-section h2 .note-body,
.case-section h3 .note-body { font-family: var(--font-body-brand), 'Inter', sans-serif; font-weight: 400; letter-spacing: 0; }


/* Guide tables hold questions in the first column, not labels, so the columns
   split evenly. Declared last on purpose: the generic index-width rule carries
   the same weight. */
.research-table.guide thead th:first-child,
.research-table.guide tbody th { width: 23%; }
.research-table.guide tbody th {
  font-family: var(--font-body-brand), 'Inter', sans-serif;
  font-weight: 400;
  color: var(--black);
  /* Reads as an ordinary cell, not as an index – but it is still the frozen
     column, so it has to be opaque in the page colour: `background: none` let
     the answers slide through it once the table panned sideways. */
  background: #f7f7f7;
}

/* Grab cursor only where there is something to pan — the class is set by script
   once the table's real width is known. */
.table-scroll.draggable { cursor: default; }
.table-scroll.draggable.is-pannable { cursor: grab; }
.table-scroll.draggable.is-pannable.is-grabbing { cursor: grabbing; }

/* List numbers inside tables sit 2.5pt under the cell text, so they index the
   items without competing with them. */
.research-table ol li::marker {
  font-size: 11.5px;
  color: var(--black-72);
}

/* The tool line reuses the logo row from the source instead of spelling the
   names out. */
/* In the source the logo row is 400px inside a 496px column — 80% of it. Same
   proportion here instead of an arbitrary 200px. */
.tool-logos { display: block; width: 80%; max-width: 300px; height: auto; }
/* The row lines up with the text inside the pills above it, not with the column
   edge — 20px is the pill's own horizontal padding — and drops 7px so it reads
   as a note under the list rather than one more item in it. */
/* The logo row closes the column, so the paragraph that follows the whole split
   needs air under it, not just above. */
.split-note .tool-logos { margin: 20px 0 4px 20px; }
@media screen and (max-width: 767px) {
  .split-note .tool-logos { margin-left: 14px; }
}

/* More air between one shot-with-caption and the next inside a section. */
.case-figure + .case-figure { margin-top: 48px; }

/* The title's marker needs its own spacing: at 72px the glyphs run right up to
   it, and the dot should sit near the cap height, not on the baseline. */
.case-hero h1 .note { margin-left: 16px; vertical-align: 0.5em; }

/* A sideways swipe that runs past a table's end used to chain to the page, and
   the whole layout slid under the fixed chapter rail. Contain the chaining, and
   clip any horizontal page scroll as a backstop. */
.table-scroll { overscroll-behavior-x: contain; }
html, body { overflow-x: clip; }

/* ---- Narrow screens: tiles and type ---------------------------------------
   The result tiles carry before → after pairs, which are wider than a bare
   percentage: on a phone they get the full width and a smaller number. */
@media screen and (max-width: 991px) {
  .result-value { font-size: 32px; }
}
@media screen and (max-width: 560px) {
  .results-row,
  .results-row.is-pair,
  .results-row.is-trio { grid-template-columns: minmax(0, 1fr); }
  .result-value { font-size: 28px; white-space: nowrap; }
  .result-tile { padding: 14px 18px; }
  .case-hero h1 { font-size: 2.5rem; }
  .case-lede { font-size: 17px; }
  .case-section h2 { font-size: 30px; }
  .case-section h3 { font-size: 20px; }
  .case-section { padding-top: 48px; }
  .case-facts { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
  .case-facts dt { margin-top: 10px; }
  .note-body { left: auto; right: 0; max-width: 260px; }
}

/* A cell that is nothing but a list does not need markers — the rows already
   read as a list. Cells that mix a sentence with a list keep theirs. */
.research-table td > ul:only-child,
.research-table th > ul:only-child {
  list-style: none;
  padding-left: 0;
}
.research-table td > ul:only-child > li,
.research-table th > ul:only-child > li { margin-bottom: 6px; }

/* Portrait phone screenshots. A 360x812 frame stretched to the content width
   turns into a wall of pixels, so cap the height and centre it instead. */
.case-body .case-figure.phone img {
  width: auto;
  max-width: 100%;
  max-height: 650px;
  margin-left: auto;
  margin-right: auto;
}
.case-body .case-figure.phone figcaption { text-align: center; }

/* Video behaves like the images around it: full column width, natural ratio. */
.case-figure video { display: block; width: 100%; height: auto; border-radius: 8px; }

/* Photo gallery: one slide at a time, with arrows and dots. Slides are stacked
   and cross-faded, so the figure keeps a stable height and nothing jumps when
   images differ in proportion. */
.case-body .case-figure.gallery { margin: 32px 0 40px; }
.case-figure.gallery .gallery-viewport {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  background: var(--black-04, #f4f4f4);
}
/* Height comes from the first slide: the frame keeps that shape for every other
   slide, so switching never resizes the figure. Fallback until the script runs. */
.case-figure.gallery .gallery-viewport { aspect-ratio: 16 / 10; }
.case-figure.gallery .gallery-slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 260ms ease;
}
.case-figure.gallery .gallery-slide.is-active { opacity: 1; visibility: visible; }
/* Images are never stretched. They keep their own size and only shrink to fit
   the frame, so switching slides cannot resize anything. */
.case-figure.gallery .gallery-slide img,
.case-figure.gallery .gallery-slide video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
.case-figure.gallery .gallery-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(20, 20, 24, 0.62);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transition: opacity 180ms ease, background 180ms ease;
}
.case-figure.gallery .gallery-viewport:hover .gallery-nav,
.case-figure.gallery .gallery-nav:focus-visible { opacity: 1; }
.case-figure.gallery .gallery-nav:hover { background: rgba(20, 20, 24, 0.86); }
.case-figure.gallery .gallery-nav[disabled] { opacity: 0 !important; pointer-events: none; }
.case-figure.gallery .gallery-nav.prev { left: 12px; }
.case-figure.gallery .gallery-nav.next { right: 12px; }
.case-figure.gallery .gallery-nav svg { width: 18px; height: 18px; }
.case-figure.gallery .gallery-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 14px 0 0;
}
.case-figure.gallery .gallery-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--black-24, rgba(0,0,0,0.24));
  cursor: pointer;
  transition: background 180ms ease, transform 180ms ease;
}
.case-figure.gallery .gallery-dot.is-active { background: var(--black-72, rgba(0,0,0,0.72)); transform: scale(1.25); }
.case-figure.gallery figcaption { margin-top: 12px; }
@media screen and (max-width: 767px) {
  .case-figure.gallery .gallery-nav { opacity: 1; width: 38px; height: 38px; }
}

/* Footnote under a data table: the qualifier belongs with the numbers, not in a
   distant section, so a reader cannot quote the figure without the caveat. */
.case-body .table-note {
  margin: 12px 0 0 !important;
  font-size: 13px;
  line-height: 1.5;
  color: var(--black-72);
}

/* Numbered process steps: the number carries meaning here (the stages run in
   order), so it stays visible instead of being replaced by a bullet. */
.case-body .case-steps { margin: 20px 0 0; padding-left: 22px; }
.case-body .case-steps li { margin: 0 0 14px; padding-left: 6px; }
.case-body .case-steps li:last-child { margin-bottom: 0; }
/* Text that follows the steps is a new thought, not a fifth step — it needs a
   full paragraph gap, one step up from the spacing between the items. */
.case-body .case-steps + p { margin-top: 28px !important; }

/* The whole case title links to the live product. The arrow is a glyph, so it
   inherits the heading font and scales with it. No underline: at 72px a rule
   under the title would read as a divider, not as a link. */
.case .case-title-link,
.case .case-title-link:hover,
.case .case-title-link:focus {
  color: inherit;
  text-decoration: none;
  border-bottom: 0;
}
.case-title-arrow {
  display: inline-block;
  margin-left: 0.14em;
  font-size: 0.42em;
  line-height: 1;
  vertical-align: super;
  color: inherit;
}
.case .case-title-link:hover .case-title-arrow,
.case .case-title-link:focus .case-title-arrow { color: var(--purple, #9847FF); }
