/* ============ START OF CASE STUDY PAGE ============ */

/* Registered so both compute to px: the parallax reads --bp-cell back out of
   getComputedStyle to find the pattern's period, and an unregistered custom
   property would hand it the raw "1.625rem" token instead. */
@property --bp-cell {
  syntax: "<length>";
  inherits: true;
  initial-value: 26px;
}

@property --bp-shift {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

/* Prose measure. The outer container is 71.25rem, which is far too wide to read
   1.0625rem body copy across, so single-column blocks get their own limit. */
.case {
  --measure: 68ch;

  /* Blueprint ruling. Kept at the threshold of visible — felt, not read. */
  --bp-cell: 1.625rem;
  --bp-rule: rgba(140, 190, 235, .028);
  --bp-rule-major: rgba(140, 190, 235, .045);
}

/* ============ START OF BLUEPRINT GRID ============ */
/* The sheet the case study is printed on: a fine grid, ruled heavier every
   fifth cell the way blueprint stock is.

   z-index: -1 is the point of it — this sits BEHIND the article, so the ruling
   stops where a card or a photograph begins, as it would on paper. Laid over
   the top it would read as a screen door dragged across the imagery.

   Fixed to the viewport, then drifted by JS at a fraction of scroll speed, so
   the sheet sits behind the article at a little depth instead of being glued
   to it. The drift is wrapped to the pattern's own period, which is why it can
   run forever inside a viewport-sized box — the grid is periodic, so a jump of
   exactly one period is invisible. Everything moves on transform alone, so the
   layer stays composited and never repaints while scrolling.

   The box is oversized top and bottom by more than one period so the drift
   can't pull an edge into view. */
.case::before {
  content: "";
  position: fixed;
  inset: -10rem 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right, var(--bp-rule-major) 0 1px, transparent 1px calc(var(--bp-cell) * 5)),
    repeating-linear-gradient(to bottom, var(--bp-rule-major) 0 1px, transparent 1px calc(var(--bp-cell) * 5)),
    repeating-linear-gradient(to right, var(--bp-rule) 0 1px, transparent 1px var(--bp-cell)),
    repeating-linear-gradient(to bottom, var(--bp-rule) 0 1px, transparent 1px var(--bp-cell));
  transform: translate3d(0, var(--bp-shift), 0);
  will-change: transform;
}

/* Custom grab cursor for the lightbox image */
body.has-cursor.lb-over-img #cursor {
  opacity: 0;
}

body.has-cursor.lb-over-img #follower {
  width: 3.5rem;
  height: 3.5rem;
  background-color: var(--c-blue-wash);
  border-color: transparent;
}

body.has-cursor.lb-over-img #follower::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--c-blue);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 11V6a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M10 9.5V4a2 2 0 0 0-4 0v7'/%3E%3Cpath d='M6 11.5v-1a2 2 0 0 0-4 0v5a7 7 0 0 0 14 0v-2.5'/%3E%3C/svg%3E") center / 1.5rem no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 11V6a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M10 9.5V4a2 2 0 0 0-4 0v7'/%3E%3Cpath d='M6 11.5v-1a2 2 0 0 0-4 0v5a7 7 0 0 0 14 0v-2.5'/%3E%3C/svg%3E") center / 1.5rem no-repeat;
}

body.has-cursor.lb-over-img.lb-dragging #follower,
body.has-cursor.lb-over-img #follower.is-down {
  width: 2.75rem !important;
  height: 2.75rem !important;
}

body.has-cursor.lb-over-img.lb-dragging #follower::after,
body.has-cursor.lb-over-img #follower.is-down::after {
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 9.5V4a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M18 11v5a7 7 0 0 1-14 0v-5a2 2 0 0 1 4 0v7'/%3E%3Cpath d='M14.5 13.5L11 9'/%3E%3C/svg%3E") center / 1.25rem no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 9.5V4a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M18 11v5a7 7 0 0 1-14 0v-5a2 2 0 0 1 4 0v7'/%3E%3Cpath d='M14.5 13.5L11 9'/%3E%3C/svg%3E") center / 1.25rem no-repeat;
}

/* The edge fade. It was a mask on the layer above until that layer started
   moving — a mask travels with its element, so the soft edge would have drifted
   with the ruling and jumped on every wrap. Held here as its own still layer,
   painting the page colour back over the grid, it stays put. */
.case::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(125% 85% at 50% 38%, transparent 40%, var(--c-bg) 100%);
}

/* ============ END OF BLUEPRINT GRID ============ */

/* ============ START OF HERO ============ */
.case-hero {
  margin-top: calc(var(--header-h) * -1);
  padding: calc(var(--header-h) + var(--space-96)) var(--gutter) var(--space-64);
  text-align: center;
}

.case-eyebrow {
  margin-bottom: var(--space-24);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps-wide);
  text-transform: uppercase;
  color: var(--c-blue);
}

.case-title {
  max-width: 18ch;
  margin-inline: auto;
  font-size: var(--fs-case-title);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--c-white);
}

.case-meta {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-16);
  margin-top: var(--space-32);
  font-size: var(--fs-base);
  color: var(--c-muted);
}

.case-meta__sep {
  width: 1px;
  height: 0.875rem;
  background: var(--c-line);
}

.case-meta__link {
  color: var(--c-blue);
  transition: color .3s var(--ease);
}

.case-meta__link:hover {
  color: var(--c-white);
}

/* ============ END OF HERO ============ */

/* ============ START OF TEXT BLOCKS ============ */
.case-block {
  padding-block: var(--space-48);
}

/* Section and subsection sat a single step apart — 1.625rem against 1.375rem,
   a ratio of 1.18 — which is close enough that a subsection read as another
   section. The section head now opens at 2rem and closes on a rule, so a new
   section announces itself; the subsection keeps its size and gains air above
   instead, which puts the steps at 1.45 and 1.29 down to body copy. */
.case-block__title {
  margin-bottom: var(--space-32);
  padding-bottom: var(--space-16);
  font-size: var(--fs-5xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-tight);
  color: var(--c-white);
  /* A hairline the width of the block, with its first 3rem in the accent —
     one line doing both the separation and the flash of colour. */
  background: linear-gradient(to right, var(--c-blue) 0 3rem, var(--c-hairline) 3rem) no-repeat left bottom / 100% 1px;
}

/* Marked rather than merely spaced: an accent rule down its left edge, the same
   device the problem/solution panels use, at a weight that reads as subordinate
   to the section head's full-width rule. Size stays put — shrinking it would
   have crowded it against the body copy — so rank comes from the mark, the air
   above, and the section head's greater size. */
.case-block__subtitle {
  margin-top: var(--space-48);
  margin-bottom: var(--space-16);
  padding-left: var(--space-16);
  /* Longhands, not the shorthand: a var() that failed to resolve inside
     `border-left` would invalidate the whole declaration, mark and all. */
  border-left-width: 0.125rem;
  border-left-style: solid;
  border-left-color: var(--c-blue);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  color: var(--c-white);
}

/* The first subsection follows its own section head, which has already opened
   the space — a second gap on top of it reads as a hole. */
.case-block__title+.case-block__subtitle {
  margin-top: 0;
}

.case-block__text,
.case-block__item {
  max-width: var(--measure);
  font-size: var(--fs-md);
  font-weight: var(--fw-light);
  line-height: var(--lh-prose);
  color: var(--c-muted);
}

.case-block__text+.case-block__text {
  margin-top: var(--space-16);
}

.case-block__list {
  margin-block: var(--space-24);
  padding-left: var(--space-24);
  list-style: disc;
}

.case-block__item {
  margin-bottom: var(--space-16);
}

.case-block__item::marker {
  color: var(--c-blue);
}

.case-block__lead {
  display: block;
  margin-bottom: var(--space-4);
  font-weight: var(--fw-medium);
  color: var(--c-white);
}

/* The level below __subtitle. These were bare h4s, which the base reset leaves
   at the browser default 1rem — smaller than the --fs-md prose underneath them,
   so a subhead read as less important than its own body copy. Set as a label
   instead of a smaller heading: caps and colour carry the rank, not size. */
.case-block__label {
  margin-top: var(--space-32);
  margin-bottom: var(--space-8);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--c-white);
}

/* Challenge / Solution, side by side */
.case-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-64);
}

.case-split__col {
  min-width: 0;
}

.case-split .case-block__text {
  max-width: none;
}

/* ============ END OF TEXT BLOCKS ============ */

/* ============ START OF IMAGERY ============ */
.case-figure {
  margin: var(--space-16) 0;
  max-width: var(--measure);
}

.case-figure__img {
  width: 100%;
  height: auto;
  border-radius: 1.25rem;
}

.case-figure__caption {
  margin-top: var(--space-16);
  font-size: var(--fs-sm);
  color: var(--c-muted);
  text-align: center;
}

/* Asymmetric grid: a stack of two beside one tall frame. */
.case-bento {
  display: grid;
  grid-template-columns: minmax(12.5rem, 1fr) minmax(18.75rem, 2fr);
  gap: var(--space-24);
  align-items: stretch;
  margin-block: var(--space-32);
}

.case-bento__col {
  display: grid;
  gap: var(--space-24);
  min-width: 0;
}

.case-bento__item {
  overflow: hidden;
  border-radius: 1rem;
  min-height: 9.375rem;
}

.case-bento__img {
  width: 100%;
  height: 100%;
  min-height: 9.375rem;
  object-fit: cover;
}

.case-devices {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-24);
  margin-block: var(--space-32);
}

.case-devices--pair {
  grid-template-columns: repeat(2, 1fr);
}

.case-devices__img {
  width: 100%;
  height: auto;
  border-radius: 1rem;
}

/* Desktop and mobile captures have opposite aspect ratios, so `cover` would
   crop one of them to nonsense. `contain` shows each layout whole. */
.case-devices--pair .case-devices__img {
  padding: var(--space-8);
}

/* ============ END OF IMAGERY ============ */

/* ============ START OF BEFORE / AFTER REDESIGNS ============ */
/* Seven screens, each one an argument: here is the old thing, here is the new
   one, here is what changed. The old layout stacked all three parts vertically
   and left the two shots unlabelled, so nothing said which was which except the
   alt text. Here the reasoning sits beside the evidence, and the pair is read
   left to right — old, arrow, new. */
.case-redesign {
  display: grid;
  gap: var(--space-64);
  margin-top: var(--space-32);
  list-style: none;
  /* Numbered from the markup order, so reordering renumbers itself. */
  counter-reset: redesign;
}

.case-redesign__item {
  counter-increment: redesign;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: var(--space-48);
  align-items: center;
  padding-top: var(--space-48);
  border-top: 1px solid var(--c-hairline);
}

/* The rule is a separator between screens, not a lid on the first one. */
.case-redesign__item:first-child {
  padding-top: 0;
  border-top: 0;
}

/* Grid children default to min-width:auto, which lets a long unbroken word
   push the column wider than its track and squeeze the shots. */
.case-redesign__aside {
  min-width: 0;
}

.case-redesign__name {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  color: var(--c-white);
}

.case-redesign__name::before {
  content: counter(redesign, decimal-leading-zero);
  display: block;
  margin-bottom: var(--space-8);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps-wide);
  color: var(--c-blue);
}

.case-redesign__purpose {
  margin-top: var(--space-8);
  font-size: var(--fs-md);
  font-weight: var(--fw-light);
  line-height: var(--lh-prose);
  color: var(--c-muted);
}

.case-redesign__label {
  margin-top: var(--space-24);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--c-white);
}

.case-redesign__list {
  margin-top: var(--space-8);
  padding-left: var(--space-24);
  list-style: disc;
}

.case-redesign__note {
  margin-top: var(--space-8);
  font-size: var(--fs-base);
  font-weight: var(--fw-light);
  line-height: var(--lh-body);
  color: var(--c-muted);
}

.case-redesign__note::marker {
  color: var(--c-blue);
}

/* Portrait 428x926 captures, so the two sit side by side with the arrow
   between them even on a phone — stacking them would bury the comparison. */
.case-redesign__shots {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-16);
}

.case-redesign__shot {
  margin: 0;
}

.case-redesign__img {
  width: 100%;
  height: auto;
  border-radius: 0.875rem;
  transition: filter .4s var(--ease), opacity .4s var(--ease);
}

/* The old screen is held back rather than hidden: desaturated and dimmed so the
   redesign carries the eye, but restored on hover for anyone who wants to look
   at what was actually there. */
.case-redesign__shot--before .case-redesign__img {
  filter: grayscale(.55) brightness(.8);
  opacity: .85;
}

.case-redesign__shot--before:hover .case-redesign__img {
  filter: none;
  opacity: 1;
}

.case-redesign__shot--after .case-redesign__img {
  box-shadow: 0 0 0 1px var(--c-blue-wash-strong), var(--shadow-card);
}

.case-redesign__tag {
  margin-top: var(--space-8);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps-wide);
  text-transform: uppercase;
  text-align: center;
  color: var(--c-muted);
}

.case-redesign__tag--after {
  color: var(--c-blue);
}

.case-arrow {
  display: grid;
  place-items: center;
  color: var(--c-line);
}

/* ============ END OF BEFORE / AFTER REDESIGNS ============ */

/* ============ START OF PROBLEM → SOLUTION ============ */
/* The same left-to-right reading the redesigns use, one level up: this is the
   state of things, this is what was done about it. Two plain columns of prose
   said the pair was related but not which direction it ran in. */
/* Flex rather than fixed grid tracks, so the chain takes however many panels
   the argument needs — two on Logitech, three on Angop, where a hypothesis
   sits between the problem and the answer. */
.case-contrast {
  display: flex;
  align-items: stretch;
  gap: var(--space-24);
  margin-block: var(--space-32);
}

.case-contrast__panel {
  flex: 1 1 0;
  min-width: 0;
}

.case-contrast__panel {
  padding: var(--space-32);
  /* Square on the left so the accent rule reads as a flat edge rather than
     bending away from the corner. */
  border-radius: 0 1.25rem 1.25rem 0;
  background: var(--c-surface);
}

/* Red for the state of things, blue for the answer — the pair reads as a
   before and after in colour as well as in position. Anything in between is
   working, not concluded, so it stays neutral. */
.case-contrast__panel--problem {
  box-shadow: inset 0.1875rem 0 0 var(--c-red);
}

.case-contrast__panel--objective {
  box-shadow: inset 0.1875rem 0 0 var(--c-line);
}

.case-contrast__panel--solution {
  box-shadow: inset 0.1875rem 0 0 var(--c-blue);
}

.case-contrast__label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--c-muted);
}

.case-contrast__panel--problem .case-contrast__label {
  color: var(--c-red);
}

.case-contrast__panel--solution .case-contrast__label {
  color: var(--c-blue);
}

.case-contrast__text {
  margin-top: var(--space-16);
  font-size: var(--fs-md);
  font-weight: var(--fw-light);
  line-height: var(--lh-prose);
  color: var(--c-muted);
}

/* ============ END OF PROBLEM → SOLUTION ============ */

/* ============ START OF PAIN POINTS ============ */
/* Findings, not prose. These were four bullets buried at the end of the
   research copy; as tiles they can be counted at a glance, which is what a
   list of findings is for. Numbered with the same ordinals as the timeline
   and the redesigns, so the page keeps one counting language. */
.case-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-16);
  margin-top: var(--space-24);
  list-style: none;
  counter-reset: tile;
}

.case-tile {
  counter-increment: tile;
  padding: var(--space-24);
  border-radius: 1rem;
  background: var(--c-surface);
}

.case-tile__name {
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--c-white);
}

.case-tile__name::before {
  content: counter(tile, decimal-leading-zero);
  display: block;
  margin-bottom: var(--space-8);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps-wide);
  color: var(--c-blue);
}

/* ============ END OF PAIN POINTS ============ */

/* ============ START OF CRITERIA CHIPS ============ */
/* Attributes of a set — who the testers had to be — rather than a sequence.
   Bulleted down the page they read as instructions to follow one after another;
   as chips they read as what they are, a handful of conditions held at once. */
.case-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  margin-top: var(--space-16);
  list-style: none;
}

.case-chip {
  padding: var(--space-8) var(--space-16);
  border: 1px solid var(--c-hairline);
  border-radius: 62.5rem;
  font-size: var(--fs-sm);
  font-weight: var(--fw-light);
  color: var(--c-muted);
  background: var(--c-surface);
}

/* ============ END OF CRITERIA CHIPS ============ */

/* ============ START OF PERSONAS ============ */
/* The personas were a flat 1920x1970 artboard: unreadable at the prose measure,
   unsearchable, invisible to a screen reader beyond its alt text, and heavier
   than the words it carried. Rebuilt as two tabs over one panel — the avatars
   are cropped out of that same artboard, so the illustration survives.

   Only the selected panel is in the page: the inactive one ships `hidden` in
   the markup rather than waiting for JS to hide it, so a persona's goals never
   appear under the other persona's name in the gap before main.js runs — the
   script is a plain tag at the end of the body, so that gap is real. */
.case-personas {
  /* Held to the reading measure like the prose and figures around it, rather
     than running the full container. */
  max-width: var(--measure);
  margin-block: var(--space-32);
}

.case-personas__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-16);
}

.case-persona__tab {
  display: flex;
  flex: 1 1 14rem;
  align-items: center;
  gap: var(--space-16);
  padding: var(--space-16);
  border: 1px solid var(--c-hairline);
  border-radius: 1.25rem;
  background: var(--c-surface);
  /* Buttons don't inherit type by default. */
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color .3s var(--ease), background .3s var(--ease), transform .3s var(--ease);
}

.case-persona__tab:hover {
  transform: translateY(-0.125rem);
  border-color: var(--c-line);
}

.case-persona__tab:focus-visible {
  outline: 2px solid var(--c-blue);
  outline-offset: 2px;
}

.case-persona__tab.is-active {
  border-color: var(--c-blue);
  background: var(--c-blue-wash);
}

.case-persona__avatar {
  width: 4rem;
  height: 4rem;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
}

.case-persona__id {
  display: grid;
  gap: var(--space-4);
  min-width: 0;
}

.case-persona__name {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--c-white);
}

.case-persona__role {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* `display` here is set by an author rule, which outranks the UA stylesheet's
   [hidden] { display: none } — without the rule below, the hidden panel stays
   on screen and both personas' content shows at once. Any component that sets
   display on an element it also toggles with `hidden` needs this pair. */
.case-persona__panel[hidden] {
  display: none;
}

.case-persona__panel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-32);
  margin-top: var(--space-16);
  padding: var(--space-32);
  border-radius: 1.25rem;
  background: var(--c-surface);
}

/* Grid children default to min-width:auto, so a long goal could push its column
   past its track instead of wrapping inside it. */
.case-persona__group {
  min-width: 0;
}

.case-persona__label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--c-blue);
}

.case-persona__list {
  margin-top: var(--space-8);
  padding-left: var(--space-24);
  list-style: disc;
}

.case-persona__point {
  margin-top: var(--space-8);
  font-size: var(--fs-base);
  font-weight: var(--fw-light);
  line-height: var(--lh-body);
  color: var(--c-muted);
}

.case-persona__point::marker {
  color: var(--c-blue);
}

/* The interview results run pros against cons in the same frame, so the two
   columns are told apart by colour the way problem is told from solution. */
.case-persona__label--con {
  color: var(--c-red);
}

.case-persona__list--con .case-persona__point::marker {
  color: var(--c-red);
}

/* ============ END OF PERSONAS ============ */

/* ============ START OF CLOSING NOTE ============ */
/* The sign-off is an aside rather than another section of the argument, so it
   sits under a hairline at a larger, lighter size — but it stays left aligned
   on the same measure as the prose above it, so the column doesn't break at
   the last paragraph. */
.case-closing__text {
  max-width: var(--measure);
  padding-top: var(--space-48);
  border-top: 1px solid var(--c-hairline);
  font-size: var(--fs-lg);
  font-weight: var(--fw-light);
  line-height: var(--lh-prose);
  color: var(--c-muted);
}

/* ============ END OF CLOSING NOTE ============ */

/* ============ START OF OUTCOME STATS ============ */
.case-stats {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  gap: var(--space-32);
  margin-block: var(--space-32);
  padding: var(--space-48) var(--space-32);
  border-radius: 1.25rem;
  background: var(--c-surface);
}

.case-stat {
  text-align: center;
}

.case-stat__figure {
  font-size: var(--fs-case-stat);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-tight);
  color: var(--c-blue);
}

.case-stat__label {
  margin-top: var(--space-8);
  font-size: var(--fs-base);
  font-weight: var(--fw-light);
  color: var(--c-muted);
}

/* ============ END OF OUTCOME STATS ============ */

/* ============ START OF OTHER WORKS ============ */
.case-more {
  padding-block: var(--space-96);
}

.case-more__title {
  font-size: var(--fs-case-h2-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-tight);
  margin-bottom: var(--space-32);
}

.case-more__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16.25rem, 1fr));
  gap: var(--space-24);
}

.case-more__card {
  display: block;
  overflow: hidden;
  border-radius: 1.25rem;
  background: var(--c-surface);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}

.case-more__card:hover {
  transform: translateY(-0.375rem);
  box-shadow: var(--shadow-lift);
}

.case-more__img {
  width: 100%;
  height: 11.25rem;
  object-fit: cover;
}

.case-more__body {
  padding: var(--space-24);
}

.case-more__kind {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--c-blue);
}

.case-more__name {
  margin-top: var(--space-8);
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  color: var(--c-white);
}

/* Back to the index */
.case-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  padding-block: var(--space-48);
  font-size: var(--fs-base);
  color: var(--c-muted);
  transition: color .3s var(--ease);
}

.case-back:hover {
  color: var(--c-blue);
}

/* ============ END OF OTHER WORKS ============ */

/* ============ START OF SECTION NAV ============ */
.case-nav {
  position: sticky;
  top: var(--header-h);
  z-index: 50;
  margin-bottom: var(--space-8);
  padding-block: var(--space-16);
  background: transparent;
  pointer-events: none;
  /* Let clicks pass through the sticky container */
}

.case-nav__backdrop {
  display: block;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s var(--ease);
  z-index: 100;
}

.case-nav.is-open .case-nav__backdrop {
  opacity: 1;
  pointer-events: auto;
}

.case-nav__toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  pointer-events: auto;
  padding: var(--space-8) var(--space-16);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: 999px;
  color: var(--c-white);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);

  /* Fixed positioning so it's always visible on mobile */
  position: fixed;
  bottom: var(--space-24);
  left: 50%;
  transform: translateX(-50%);
  z-index: 99;
}

.case-nav__list {
  display: flex;
  flex-direction: column;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 280px;
  /* Fixed height to create the wheel viewport */
  overflow-y: auto;
  overflow-x: hidden;
  scroll-snap-type: y mandatory;
  background-color: var(--c-surface);
  border-top: 1px solid var(--c-line);
  border-top-left-radius: 1.5rem;
  border-top-right-radius: 1.5rem;
  /* Top padding ensures the first item can reach the center.
     Bottom padding ensures the last item can reach the center. */
  padding: 116px 0 calc(116px + env(safe-area-inset-bottom, 0));
  transform: translateY(100%);
  transition: transform .3s cubic-bezier(0.4, 0, 0.2, 1);
  z-index: 101;
  pointer-events: auto;
  gap: 0;
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.6);
  perspective: 1000px;
  /* Hide scrollbar for a cleaner wheel */
  scrollbar-width: none;
}

.case-nav__list::-webkit-scrollbar {
  display: none;
}

.case-nav__item {
  scroll-snap-align: center;
}

/* Drag handle indicator */
.case-nav__list::before {
  content: "";
  position: absolute;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  width: 3rem;
  height: 0.25rem;
  background: var(--c-line);
  border-radius: 1rem;
}

.case-nav.is-open .case-nav__list {
  transform: translateY(0);
}

.case-nav__link {
  font-size: var(--fs-xl);
  text-align: center;
  color: var(--c-white);
  transition: all .4s cubic-bezier(0.16, 1, 0.3, 1);
  display: block;
  max-width: 100%;
  padding: var(--space-12) var(--space-32);
  margin-inline: var(--space-16);
  border-radius: var(--space-8);
  transform-origin: center center;
}

@media (max-width: 74.9375rem) {

  /* Default inactive state (items far away) */
  .case-nav__link {
    opacity: 0.15;
    transform: rotateX(60deg) scale(0.85);
  }

  /* 2 items before */
  .case-nav__item:has(+ * + .case-nav__item .is-active) .case-nav__link {
    opacity: 0.3;
    transform: rotateX(45deg) scale(0.9);
  }

  /* 1 item before */
  .case-nav__item:has(+ .case-nav__item .is-active) .case-nav__link {
    opacity: 0.6;
    transform: rotateX(25deg) scale(0.95);
  }

  /* Active item */
  .case-nav__link.is-active {
    opacity: 1;
    transform: rotateX(0deg) scale(1);
    background: rgba(255, 255, 255, 0.1);
  }

  /* 1 item after */
  .case-nav__item:has(.is-active)+.case-nav__item .case-nav__link {
    opacity: 0.6;
    transform: rotateX(-25deg) scale(0.95);
  }

  /* 2 items after */
  .case-nav__item:has(.is-active)+*+.case-nav__item .case-nav__link {
    opacity: 0.3;
    transform: rotateX(-45deg) scale(0.9);
  }

  .case-nav__link:hover,
  .case-nav__link:focus-visible {
    opacity: 0.7;
  }
}

/* ============ END OF SECTION NAV ============ */

/* ============ START OF PROJECT FACTS — DURATION, TYPE, ROLE ============ */
.case-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11.25rem, 1fr));
  gap: var(--space-24);
  margin-block: var(--space-32);
  padding: var(--space-32);
  border-radius: 1.25rem;
  background: var(--c-surface);
}

.case-fact__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--c-blue);
}

.case-fact__value {
  margin-top: var(--space-8);
  font-size: var(--fs-lg);
  color: var(--c-white);
}

/* ============ END OF PROJECT FACTS — DURATION, TYPE, ROLE ============ */

/* ============ START OF TIMELINE STEPS — THE PHASES THE PROJECT RAN THROUGH ============ */
/* An actual timeline: one rail, a numbered node per phase sitting on it, and
   the phase's copy hanging off it. Runs left to right here and top to bottom
   below 55rem, where four columns of prose would be unreadable.

   grid-auto-flow: column instead of an auto-fit track, because auto-fit wraps
   to a second row on narrower desktops — and a wrapped timeline reads as two
   unrelated rails rather than one sequence. */
.case-steps {
  /* The rail runs through the centre of the node, so both derive from --node. */
  --node: 1.75rem;
  --rail: calc(var(--node) / 2);
  --link: var(--space-32);

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--link);
  margin-block: var(--space-32);
  /* Counter-based numbering, so reordering the markup renumbers itself. */
  counter-reset: step;
}

.case-step {
  position: relative;
  padding-top: calc(var(--node) + var(--space-16));
}

/* Each phase draws its own leg of the rail — from its node across the gap to
   the next node — rather than one line drawn under the whole row. The row's
   track count isn't knowable in CSS, so a single line would have to guess where
   the last node falls and either overshoot it or fade out early. Per-leg, the
   rail ends exactly on the final node, at any number of phases. */
.case-step::before {
  content: "";
  position: absolute;
  top: var(--rail);
  left: var(--rail);
  right: calc((var(--link) + var(--rail)) * -1);
  height: 1px;
  background: var(--c-line);
}

.case-step:last-child::before {
  content: none;
}

/* The node. Its own background punches a hole in the rail, so the line reads as
   passing behind it, and the step number rides inside the ring. */
.case-step__name::before {
  counter-increment: step;
  content: counter(step, decimal-leading-zero);
  position: absolute;
  top: 0;
  left: 0;
  display: grid;
  place-items: center;
  width: var(--node);
  height: var(--node);
  border-radius: 50%;
  background: var(--c-bg);
  box-shadow: inset 0 0 0 1px var(--c-blue);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  color: var(--c-blue);
}

.case-step__name {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--c-white);
}

.case-step__text {
  margin-top: var(--space-8);
  font-size: var(--fs-base);
  color: var(--c-muted);
}

/* ============ END OF TIMELINE STEPS — THE PHASES THE PROJECT RAN THROUGH ============ */

/* ============ START OF TEAM ============ */
/* Portraits with the name and role as text. The section used to render whole
   card images — coloured banner, circular portrait, name and role baked into a
   294x285 PNG — so it captioned a picture that already carried its own caption,
   and the role was locked inside the pixels where nothing could read it.

   Ordered by role: manager, owner, scrum master, then the designers. */
/* Four fixed columns, not auto-fit: at this container width auto-fit resolved
   to six, which split eight people into a row of six and a row of two. Four
   gives two even rows, and it keeps the two designer pairs from being broken
   across a wrap.

   Every other card is dropped by --stagger and every portrait is tilted a
   degree or two off square, alternating direction, so the block reads as
   pinned-up photographs rather than a table of headshots. The bottom padding
   is the stagger paid back, or the dropped row would collide with whatever
   follows. */
.case-team {
  --stagger: 1.75rem;
  --tilt: 2deg;

  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-16);
  margin-block: var(--space-32);
  padding-bottom: var(--stagger);
  list-style: none;
}

.case-member {
  /* The offset lives in a variable so hover can lift the card without
     forgetting where its resting position was. */
  --offset: 0rem;
  --lift: 0rem;

  /* One step between name and role; the portrait then adds a second step of
     its own, so the gap above the name is exactly twice the gap below it. */
  --step: var(--space-8);

  display: grid;
  align-content: start;
  justify-items: center;
  gap: var(--step);
  padding: var(--space-32) var(--space-16);
  /* Longhands so an unresolved var() can't invalidate the whole border. */
  border-width: 1px;
  border-style: solid;
  border-color: var(--c-hairline);
  border-radius: 1.25rem;
  background: var(--c-surface);
  text-align: center;
  transform: translateY(calc(var(--offset) - var(--lift)));
  transition: transform .45s var(--ease-soft), border-color .3s var(--ease);
}

.case-member:nth-child(even) {
  --offset: var(--stagger);
}

.case-member:hover {
  --lift: 0.375rem;
  border-color: var(--c-line);
}

.case-member__portrait {
  /* The uploaded portraits are 1000x1000, so this can be sized for the layout
     rather than for what the source could stand — the earlier crops were 108px
     and had to be kept small. */
  width: 6rem;
  height: 6rem;
  /* Adds to the grid gap above the name: --step + --step = 2 x --step. */
  margin-bottom: var(--step);
  /* A percentage radius on a square box, so the shape holds if the size
     changes — the theme's corner-shape turns it into an actual squircle, where
     28% alone only approximated one. It also clips well inside the rounded
     corners baked into the uploads, so none of that shows through. */
  border-radius: 28%;
  object-fit: cover;
  /* Off square by a couple of degrees, alternating with the stagger, and
     straightening under the pointer. */
  transform: rotate(var(--tilt));
  transition: transform .45s var(--ease-soft);
}

.case-member:nth-child(even) .case-member__portrait {
  transform: rotate(calc(var(--tilt) * -1));
}

.case-member:hover .case-member__portrait {
  transform: rotate(0deg) scale(1.04);
}

/* Two lines reserved whether the name needs them or not, so the roles below
   sit on one line across the row instead of stepping up and down with the
   length of each name. */
.case-member__name {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--c-white);
}

.case-member__role {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  line-height: var(--lh-snug);
  color: var(--c-muted);
}

/* ============ END OF TEAM ============ */


.case-figure--full {
  max-width: none;
}

.case-figure--full .case-figure__img {
  height: auto;
  object-fit: contain;
  border-radius: 1rem;
}

/* Very tall artboards — guidelines, whole user flows — would otherwise run
   for several screens, so they scroll inside their own frame instead. */
.case-figure--tall {
  text-align: center;
}

.case-figure--tall .case-figure__img {
  max-height: 40vh;
  width: auto;
  object-fit: contain;
  margin: 0 auto;
  display: inline-block;
  border-radius: 1rem;
}

/* Screen gallery — many small captures, laid out dense. */
.case-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr));
  gap: var(--space-16);
  margin-block: var(--space-32);
}

.case-gallery__img {
  width: 100%;
  height: auto;
  border-radius: 0.75rem;
}

/* ============ END OF IMAGERY THAT MUST NOT BE CROPPED ============ */

/* ============ START OF PROTOTYPE EMBED ============ */
.case-embed {
  margin-block: var(--space-32);
  border-radius: 1.25rem;
  overflow: hidden;
  background: var(--c-surface);
}

.case-embed__frame {
  display: block;
  width: 100%;
  height: min(75vh, 42.5rem);
  border: 0;
}

/* ============ END OF PROTOTYPE EMBED ============ */

/* ============ START OF FEATURE CALL-OUTS — A LEAD LINE ABOVE THE EXPLANATION ============ */
.case-feature {
  margin-top: var(--space-32);
}

.case-feature__lead {
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  color: var(--c-blue);
}

/* ============ END OF FEATURE CALL-OUTS — A LEAD LINE ABOVE THE EXPLANATION ============ */

/* ============ START OF IMAGE LIGHTBOX ============ */
/* The viewer is built by JS and appended to the body, so it sits outside .case
   and owns the viewport outright. Nothing in here is in the page flow.

   It reads as the same room the case study is printed in — the surfaces, the
   hairlines and the squircle corners are the article's own — but with the
   lights off, so the picture is the only lit thing on screen. */

/* Any image the viewer will take says so before it is clicked. On pointer
   devices the site's own cursor covers this; it is here for everything else. */
.case-figure__img,
.case-gallery__img,
.case-devices__img,
.case-redesign__img,
[data-lightbox] {
  cursor: zoom-in;
}

/* They became focusable when JS wired them up, so they need a focus ring. */
[data-lb-index]:focus-visible {
  outline: 0.125rem solid var(--c-blue);
  outline-offset: 0.25rem;
}

.lightbox {
  /* Falls from 1 to 0 as a downward drag pulls the image away, taking the
     backdrop and the chrome with it — the page underneath comes back into
     view as you throw the picture at it, so a half-hearted drag shows what it
     would have done and then puts everything back. */
  --lb-dismiss: 1;

  position: fixed;
  inset: 0;
  /* Over the context menu at 9000, under the cursor at 9999. */
  z-index: 9500;
  /* Nothing in here can affect the article's layout or paint, so a zoom or a
     pan is not allowed to send the browser back over the page behind it. */
  contain: layout paint;
}

.lightbox[hidden] {
  display: none;
}

/* The dialog takes focus on open so the keys have somewhere to land; it is not
   itself a control, so it doesn't draw a ring for it. */
.lightbox:focus {
  outline: 0;
}

.lightbox__btn:focus-visible,
.lightbox__nav:focus-visible,
.lightbox__thumb:focus-visible {
  outline: 0.125rem solid var(--c-blue);
  outline-offset: 0.125rem;
}

.lightbox__backdrop {
  position: absolute;
  inset: 0;
  background: var(--c-overlay);
  backdrop-filter: blur(1.25rem) saturate(1.15);
  -webkit-backdrop-filter: blur(1.25rem) saturate(1.15);
  opacity: 0;
  transition: opacity .32s var(--ease);
}

.lightbox.is-open .lightbox__backdrop {
  opacity: var(--lb-dismiss);
}

/* The stage is padded clear of the bars, and the image is centred in what is
   left — so the fit, the pan limits and the zoom all work against one box. */
.lightbox__stage {
  position: absolute;
  inset: 0;
  /* Flex rather than grid: the image is capped at 100% of this box, and a
     percentage only resolves against a definite height. A grid row sized by
     its own content isn't one, and tall artboards ran straight off screen. */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5.5rem clamp(1rem, 5vw, 5.5rem) 9.5rem;
  /* The gestures are ours: pinch, pan, swipe and drag-to-dismiss all come
     through pointer events, and the browser's own panning would fight them. */
  touch-action: none;
}

.lightbox__img {
  /* The viewer states the image's natural width and height so the box has its
     shape before the file decodes. Those attributes are presentational hints
     that set the used size, not just the ratio, so they have to be overridden
     back to auto — otherwise max-height clamps the height alone and the
     picture is squashed to fit. Auto leaves the ratio and lets the caps size
     the box. */
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  /* Flex would otherwise hand a tall image its full intrinsic height. */
  min-height: 0;
  border-radius: 0.75rem;
  box-shadow: var(--shadow-lift);
  /* Held on the compositor: every gesture in here writes transform and
     nothing else, so no frame of a pinch or a pan costs a repaint. */
  will-change: transform;
  touch-action: none;
  opacity: 0;
}

/* Only while the viewer is driving it — a drag writes transform every frame
   and must not be eased, so JS adds this for the moves it wants smoothed. */
.lightbox__img.is-eased {
  transition: transform .3s var(--ease);
}

/* The image fades with the room. The open animation grows it out of the
   article image and overrides this while it runs; on the way out, and any time
   there is no article image on screen to fold back into, this is the exit. */
.lightbox.is-open .lightbox__img {
  opacity: 1;
  transition: opacity .28s var(--ease);
}

/* Ordered last of the three so it wins while the file is still decoding. */
.lightbox.is-loading .lightbox__img {
  opacity: .3;
}

.lightbox__spinner {
  position: absolute;
  width: 2rem;
  height: 2rem;
  border: 0.125rem solid var(--c-line);
  border-top-color: var(--c-blue);
  border-radius: 50%;
  /* A true circle: this one spins, and a squircle would wobble as it turned. */
  corner-shape: round;
  opacity: 0;
  animation: lbSpin .8s linear infinite;
  transition: opacity .2s var(--ease);
}

.lightbox.is-loading .lightbox__spinner {
  opacity: 1;
}

@keyframes lbSpin {
  to {
    transform: rotate(360deg);
  }
}

/* The site's own dot and ring are the pointer inside the viewer. These native
   cursors are the fallback for when the custom one isn't running — no JS, a
   coarse pointer, or before the first mouse move has set has-cursor — and they
   are held behind :not(.has-cursor) for a reason: `cursor: none` is only a
   universal selector, so a plain `.lightbox__img { cursor: zoom-in }` outranks
   it and puts the browser's magnifier on screen alongside our own ring. */
body:not(.has-cursor).lb-open .lightbox__stage {
  cursor: zoom-out;
}

body:not(.has-cursor).lb-open .lightbox__img {
  cursor: grab;
}

body:not(.has-cursor).lb-open .lightbox.is-dragging .lightbox__img {
  cursor: grabbing;
}

/* ---- chrome ---- */
.lightbox__bar,
.lightbox__foot {
  position: absolute;
  left: var(--space-16);
  right: var(--space-16);
  display: flex;
  align-items: center;
  gap: var(--space-16);
  opacity: 0;
  transition: opacity .3s var(--ease), transform .35s var(--ease);
  /* The bars run the full width so their panels can sit at the corners, but
     the air between the panels belongs to the stage — a click there closes. */
  pointer-events: none;
}

.lightbox__bar {
  top: var(--space-16);
  justify-content: space-between;
  transform: translateY(-0.5rem);
}

.lightbox__foot {
  bottom: var(--space-16);
  flex-direction: column;
  gap: var(--space-8);
  transform: translateY(0.5rem);
}

.lightbox.is-open .lightbox__bar,
.lightbox.is-open .lightbox__foot {
  opacity: var(--lb-dismiss);
  transform: none;
}

/* The chrome gets out of the way while the image is being handled. */
.lightbox.is-dragging .lightbox__bar,
.lightbox.is-dragging .lightbox__foot {
  opacity: calc(var(--lb-dismiss) * .35);
}

.lightbox__count,
.lightbox__tools,
.lightbox__caption,
.lightbox__rail {
  border: 1px solid var(--c-hairline);
  background: var(--c-menu-bg);
  box-shadow: var(--shadow-menu);
  pointer-events: auto;
}

.lightbox__count {
  padding: var(--space-8) var(--space-16);
  border-radius: 0.75rem;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  font-variant-numeric: tabular-nums;
  color: var(--c-muted);
}

.lightbox__tools {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-left: auto;
  padding: var(--space-4);
  border-radius: 0.875rem;
}

.lightbox__btn {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  border-radius: 0.625rem;
  background: transparent;
  color: var(--c-muted);
  cursor: pointer;
  transition: background-color .25s var(--ease), color .25s var(--ease);
}

.lightbox__btn:hover,
.lightbox__btn:focus-visible {
  background: var(--c-surface-2);
  color: var(--c-white);
}

.lightbox__btn:disabled {
  opacity: .3;
  cursor: default;
  background: transparent;
}

.lightbox__btn--close {
  color: var(--c-white);
}

/* The zoom readout is also the reset button — it reads as a label until it is
   hovered, which is the only moment it needs to look like a control. */
.lightbox__btn--level {
  width: auto;
  min-width: 3.5rem;
  padding-inline: var(--space-8);
  font-family: var(--font);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
}

.lightbox__nav {
  position: absolute;
  top: 50%;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  margin-top: -1.5rem;
  border: 1px solid var(--c-hairline);
  border-radius: 1rem;
  background: var(--c-menu-bg);
  box-shadow: var(--shadow-menu);
  color: var(--c-white);
  cursor: pointer;
  opacity: 0;
  transition: opacity .3s var(--ease), background-color .25s var(--ease);
}

.lightbox.is-open .lightbox__nav {
  opacity: var(--lb-dismiss);
}

.lightbox__nav:hover,
.lightbox__nav:focus-visible {
  background: var(--c-surface-2);
}

.lightbox__nav--prev {
  left: var(--space-16);
}

.lightbox__nav--next {
  right: var(--space-16);
}

/* ---- minimap ---- */
/* Parked under the counter in the top-left, the one corner the viewer leaves
   empty: the tools are opposite it, the arrows are at the waist, and the
   caption and filmstrip own the floor. Its size is set by the script from the
   picture's own proportions, so the box is the shape of what it shows. */
.lightbox__map {
  position: absolute;
  top: 4.25rem;
  left: var(--space-16);
  overflow: hidden;
  border: 1px solid var(--c-hairline);
  border-radius: 0.75rem;
  background: var(--c-menu-bg);
  box-shadow: var(--shadow-menu);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-0.25rem);
  transition: opacity .25s var(--ease), transform .3s var(--ease), visibility .25s;
  touch-action: none;
}

/* Only once there is more picture than screen. */
.lightbox.is-mapped .lightbox__map {
  opacity: var(--lb-dismiss);
  visibility: visible;
  transform: none;
}

.lightbox__map-img {
  display: block;
  width: 100%;
  height: 100%;
  /* The box is already the image's shape, so this only guards a rounding
     pixel — and it keeps every event on the map itself. */
  object-fit: contain;
  pointer-events: none;
  opacity: .55;
}

/* What is on screen, drawn on the whole. Positioned by transform alone so a
   pan moves it without laying anything out. */
.lightbox__map-view {
  position: absolute;
  top: 0;
  left: 0;
  border: 0.125rem solid var(--c-blue);
  border-radius: 0.25rem;
  background: var(--c-blue-wash);
  box-shadow: 0 0 0 100vmax rgba(11, 13, 18, .45);
  pointer-events: none;
}

/* The window's outsize shadow is what dims the rest of the picture; the map's
   own overflow is what keeps that dimming inside the box. */
.lightbox__map.is-held .lightbox__map-view {
  border-color: var(--c-blue-hover);
}

.lightbox__caption {
  max-width: 60ch;
  padding: var(--space-8) var(--space-16);
  border-radius: 0.75rem;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--c-white);
  text-align: center;
}

/* ---- filmstrip ---- */
/* Twenty screens into a gallery, the counter alone stops meaning anything.
   The strip is the map: where you are, what is around you, and one click to
   anywhere. Thumbnails point at the article's own files, so the ones already
   on screen cost nothing and the rest load as the strip is scrolled. */
.lightbox__rail {
  display: flex;
  gap: var(--space-8);
  max-width: 100%;
  padding: var(--space-8);
  border-radius: 1rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* Sideways only — a vertical drag here is aimed at the page, not the strip. */
  touch-action: pan-x;
  /* The blue handle the rest of the site uses would read as another control
     in a row that is nothing but controls. */
  scrollbar-width: none;
}

.lightbox__rail::-webkit-scrollbar {
  display: none;
}

.lightbox__thumb {
  flex: none;
  width: 3.25rem;
  height: 3.25rem;
  /* Fifty-five of these on RupaOne, each pointing at a full-size file. Off
     screen in the strip they are skipped entirely — not laid out, not painted,
     and their images not fetched — so the cost tracks what is on show rather
     than what the case study holds. The size is stated so the skipped ones
     still take up their place in the row. */
  content-visibility: auto;
  contain-intrinsic-size: 3.25rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 0.625rem;
  background: var(--c-surface);
  overflow: hidden;
  opacity: .45;
  cursor: pointer;
  scroll-snap-align: center;
  transition: opacity .25s var(--ease), border-color .25s var(--ease), transform .25s var(--ease);
}

.lightbox__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.lightbox__thumb:hover,
.lightbox__thumb:focus-visible {
  opacity: .85;
}

.lightbox__thumb.is-active {
  opacity: 1;
  border-color: var(--c-blue);
  transform: translateY(-0.125rem);
}

/* A lone image has nowhere to go, so the whole apparatus of a collection —
   arrows, counter, strip — would be furniture. */
.lightbox.is-single .lightbox__nav,
.lightbox.is-single .lightbox__count,
.lightbox.is-single .lightbox__rail {
  display: none;
}

@media (max-width: 48rem) {
  .lightbox__stage {
    padding: 4.5rem var(--space-8) 7rem;
  }

  /* Swipe covers this on a phone, and at this width the arrows sit on top of
     the picture rather than beside it. */
  .lightbox__nav {
    display: none;
  }

  .lightbox__rail {
    display: none;
  }

  .lightbox__caption {
    font-size: var(--fs-xs);
  }
}

/* ============ END OF IMAGE LIGHTBOX ============ */

/* ============ START OF RESPONSIVE ============ */
@media (max-width: 55rem) {
  .case-hero {
    padding-top: calc(var(--header-h) + var(--space-48));
    padding-bottom: var(--space-48);
  }

  .case-block {
    padding-block: var(--space-48);
  }

  /* 2rem section heads crowd a phone line, and long titles like Griya's would
     take three of them. */
  .case-block__title {
    font-size: var(--fs-case-h2);
  }

  .case-block__subtitle {
    margin-top: var(--space-32);
    font-size: var(--fs-xl);
  }

  .case-split {
    grid-template-columns: 1fr;
    gap: var(--space-32);
  }

  .case-bento {
    grid-template-columns: 1fr;
  }

  .case-devices {
    grid-template-columns: 1fr;
  }

  .case-devices {
    grid-template-columns: 1fr;
  }

  .case-stats {
    padding: var(--space-32) var(--space-24);
  }

  .case-facts {
    padding: var(--space-24);
  }

  /* Problem above solution, with the arrow turned to match the new reading
     direction so the causal link survives the stack. */
  .case-contrast {
    flex-direction: column;
    gap: var(--space-16);
  }

  .case-contrast__arrow {
    transform: rotate(90deg);
  }

  /* Reasoning above the evidence once there's no room to set them side by
     side. The pair itself stays side by side — it's the whole point. */
  .case-redesign {
    gap: var(--space-48);
  }

  .case-redesign__item {
    grid-template-columns: 1fr;
    gap: var(--space-24);
    padding-top: var(--space-32);
  }

  .case-nav {
    top: var(--header-h);
  }

  /* .case-nav__list is handled globally for mobile */

  .case-gallery {
    grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
  }

  /* Two columns on a phone — four would leave no card wide enough for a
     surname like Fadhlurrahman. The stagger halves: at this width the full
     drop reads as a mistake rather than a rhythm. */
  .case-team {
    --stagger: 0.875rem;

    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-8);
  }

  .case-member {
    padding: var(--space-24) var(--space-8);
  }

  .case-member__portrait {
    width: 5rem;
    height: 5rem;
  }
}

/* The timeline only runs left to right at the width where the container is at
   its full 71.25rem — four phases share that row, so each column of prose is
   about 15rem wide, which is already the floor for readable copy. Below it the
   rail turns on its side: same nodes, same sequence, stacked down the page.
   Paired with the breakpoint below, so the timeline goes horizontal exactly
   when the TOC moves out to the left margin. */
@media (max-width: 74.9375rem) {
  .case-steps {
    grid-auto-flow: row;
    grid-auto-columns: auto;
  }

  .case-step {
    padding-top: 0;
    padding-left: calc(var(--node) + var(--space-16));
    min-height: var(--node);
  }

  .case-step__text {
    max-width: var(--measure);
  }

  /* The leg of rail now runs down from the node to the next one. */
  .case-step::before {
    top: var(--rail);
    right: auto;
    bottom: calc((var(--link) + var(--rail)) * -1);
    width: 1px;
    height: auto;
  }
}

@media (min-width: 75rem) {

  /* ============ DESKTOP: VERTICAL TOC ============ */
  /* Hide mobile elements */
  .case-nav__toggle,
  .case-nav__backdrop {
    display: none;
  }

  /* On wide screens, pull the sticky header into a fixed left sidebar. */
  .case-nav {
    /* Fixed width so the number column never shifts as an item expands */
    --toc-w: 15rem;
    position: fixed;
    top: 50%;
    width: var(--toc-w);
    transform: translate(-50%, -50%);
    /* The block is centered in the left void (outside the 71.25rem container),
       clamped so it never runs off the viewport on narrower desktops */
    left: max(calc(var(--toc-w) / 2), calc((50vw - 35.625rem) / 2));
    padding: 0;
    margin: 0;
    pointer-events: auto;
  }

  .case-nav__list {
    /* Reset off-canvas styles */
    position: static;
    transform: none;
    background: transparent;
    border-top: none;
    border-radius: 0;
    padding: 0;
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    overflow-y: visible;
    scroll-snap-type: none;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-24);
    counter-reset: toc-counter;
    box-shadow: none;
    perspective: none;
  }

  .case-nav__list::before {
    display: none;
  }

  .case-nav__item {
    scroll-snap-align: none;
    counter-increment: toc-counter;
  }

  .case-nav__link {
    display: flex;
    align-items: center;
    text-align: left;
    color: transparent;
    opacity: 1;
    max-width: 1.5rem;
    overflow: hidden;
    white-space: nowrap;
    font-size: var(--fs-md);
    font-style: italic;
    padding: 0;
    /* Reset mobile touch target padding */
    margin: 0;
    /* Reset mobile margin */
    background: transparent;
    border-radius: 0;
    border: none;
    /* Reset iOS selector borders */
    /* Collapsing: the label fades out first, then the row closes behind it. */
    transition:
      max-width .45s var(--ease-soft) .06s,
      color .22s var(--ease);
  }

  .case-nav__link::before {
    content: counter(toc-counter);
    display: flex;
    align-items: center;
    flex: none;
    min-width: 1.5rem;
    color: var(--c-muted);
    font-style: normal;
    font-size: var(--fs-base);
    opacity: 0.5;
    /* The connector is declared here at zero width rather than only on the
       active rule, so background-size has a length to interpolate from —
       growing out of `auto` isn't animatable and pops in at full size. */
    background: linear-gradient(currentColor, currentColor) no-repeat right center;
    background-size: 0 1px;
    transition:
      color .22s var(--ease),
      opacity .22s var(--ease),
      padding-right .45s var(--ease-soft) .06s,
      margin-right .45s var(--ease-soft) .06s,
      background-size .28s var(--ease);
  }

  .case-nav__link.is-active,
  .case-nav__link:hover,
  .case-nav__link:focus-visible {
    /* JS measures each row's own expanded width into --toc-item-w. A shared
       guess would overshoot every shorter label, and the ease would spend its
       slow tail on invisible pixels — the reveal reads as a snap, then a stall.
       The fallback covers the longest label if the script never runs. */
    max-width: var(--toc-item-w, 12rem);
    color: var(--c-blue);
    transition:
      max-width .5s var(--ease-soft),
      color .35s var(--ease) .12s;
  }

  .case-nav__link.is-active::before,
  .case-nav__link:hover::before,
  .case-nav__link:focus-visible::before,
  .case-nav__link.is-measuring::before {
    color: var(--c-blue);
    opacity: 1;
    /* Draw the connecting line within the padding */
    padding-right: 1.5rem;
    margin-right: 1rem;
    background-size: 1rem 1px;
    transition:
      color .3s var(--ease),
      opacity .3s var(--ease),
      padding-right .5s var(--ease-soft),
      margin-right .5s var(--ease-soft),
      background-size .32s var(--ease) .1s;
  }

  /* Held open, untransitioned, only for the split second JS spends measuring.
     Must outrank the rules above, hence the position at the end. */
  .case-nav__link.is-measuring,
  .case-nav__link.is-measuring::before {
    transition: none;
  }

  .case-nav__link.is-measuring {
    max-width: none;
  }
}

/* ============ END OF RESPONSIVE ============ */

/* ============ END OF CASE STUDY PAGE ============ */

/* Custom grab cursor for the lightbox image */
body.has-cursor.lb-over-img #cursor {
  opacity: 0;
}

body.has-cursor.lb-over-img #follower {
  width: 3.5rem;
  height: 3.5rem;
  background-color: var(--c-blue-wash);
  border-color: transparent;
}

body.has-cursor.lb-over-img #follower::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--c-blue);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 11V6a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M10 9.5V4a2 2 0 0 0-4 0v7'/%3E%3Cpath d='M6 11.5v-1a2 2 0 0 0-4 0v5a7 7 0 0 0 14 0v-2.5'/%3E%3C/svg%3E") center / 1.5rem no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 11V6a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 0-4 0v4'/%3E%3Cpath d='M10 9.5V4a2 2 0 0 0-4 0v7'/%3E%3Cpath d='M6 11.5v-1a2 2 0 0 0-4 0v5a7 7 0 0 0 14 0v-2.5'/%3E%3C/svg%3E") center / 1.5rem no-repeat;
}

body.has-cursor.lb-over-img.lb-dragging #follower,
body.has-cursor.lb-over-img #follower.is-down {
  width: 2.75rem !important;
  height: 2.75rem !important;
}

body.has-cursor.lb-over-img.lb-dragging #follower::after,
body.has-cursor.lb-over-img #follower.is-down::after {
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 9.5V4a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M18 11v5a7 7 0 0 1-14 0v-5a2 2 0 0 1 4 0v7'/%3E%3Cpath d='M14.5 13.5L11 9'/%3E%3C/svg%3E") center / 1.25rem no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 9.5V4a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M14 10V5.5a2 2 0 0 1 4 0v4'/%3E%3Cpath d='M18 11v5a7 7 0 0 1-14 0v-5a2 2 0 0 1 4 0v7'/%3E%3Cpath d='M14.5 13.5L11 9'/%3E%3C/svg%3E") center / 1.25rem no-repeat;
}

/* ============ START OF FLOW TABS ============ */
.flow-tabs-container {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  margin-top: var(--space-24);
}

.flow-tabs-nav {
  display: flex;
  gap: var(--space-8);
  overflow-x: auto;
  padding: var(--space-8);
  background: rgba(255, 255, 255, 0.05);
  border-radius: 2.5rem;
  border: 1px solid var(--c-hairline);
  width: 100%;
  scroll-behavior: smooth;
  /* Scrollbar hide */
  scrollbar-width: none;
}

.flow-tabs-nav::-webkit-scrollbar {
  display: none;
}

.flow-tab-btn {
  white-space: nowrap;
  padding: var(--space-8) var(--space-24);
  border-radius: 2rem;
  background: transparent;
  border: none;
  color: var(--c-muted);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  transition: all .3s var(--ease);
  cursor: pointer;
  outline: none;
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
  flex-shrink: 0;
}

.flow-tab-btn:hover {
  color: var(--c-white);
  background: rgba(255, 255, 255, 0.05);
}

.flow-tab-btn.is-active {
  background: var(--c-white);
  color: var(--c-bg);
  box-shadow: 0 0 8px rgba(255, 255, 255, 0.25);
}

.flow-tabs-content {
  position: relative;
  width: 100%;
}

.flow-tab-pane {
  display: none;
  animation: fadeTabIn .4s var(--ease);
}

.flow-tab-pane.is-active {
  display: block;
}

@keyframes fadeTabIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* ============ END OF FLOW TABS ============ */

.flow-tabs-scroll-wrapper {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  max-width: var(--measure);
}

.flow-tabs-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: var(--c-white);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 10;
  transition: all .3s var(--ease);
  opacity: 0;
  pointer-events: none;
}

.flow-tabs-arrow.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.flow-tabs-arrow:hover {
  background: rgba(255, 255, 255, 0.2);
}

.flow-tabs-arrow--left {
  left: -12px;
}

.flow-tabs-arrow--right {
  right: -12px;
}


.case-figure--natural .case-figure__img {
  height: auto !important;
}



.case-split--align-center {
  align-items: center;
}

@media screen and (min-width: 48rem) {
  .case-split--reverse .case-split__col:first-child {
    order: 2;
  }
}

@media screen and (min-width: 48rem) {
  .case-split--mobile-video {
    grid-template-columns: 1fr 16rem;
  }

  .case-split--mobile-video.case-split--reverse {
    grid-template-columns: 16rem 1fr;
  }
}