/* Selected Case Studies.

   Collapsed rows come from the old file's node 464:84257; the OPEN state and
   everything in it comes from the 2026-08-24 file
   (MLetTEKQTnoqEtJ4PbLR7Q, node 34:3319), which shows all four cards
   expanded — that is a presentation choice in the comp, not the resting
   state. A row is still a single component that opens on hover.

   Per-case data lives on the item as `--case-bg` / `--case-img` plus its own
   markup, so colour, copy and images all change per case study.

   Rows sit 88u from the page edges: the card is inset 40u and carries 48u of
   inner padding, in BOTH states, so content lands at 88 throughout. */

.rdk-cases {
  position: relative;
  z-index: 1;
  background: var(--rdk-black-green);
}

/* ---------- header ---------- */

.rdk-cases__head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* 104, not the old file's 144: 144 pad + 13 eyebrow + 104 + 88 title row
     + 104 pad + the 0.5 rule = the new file's 453.5. */
  gap: calc(104 * var(--u));
  padding: calc(144 * var(--u)) calc(88 * var(--u)) calc(104 * var(--u));
  /* Same fade as the rows: this rule sits above the FIRST case study. */
  border-bottom: calc(0.5 * var(--u)) solid
                 rgba(85, 85, 85, calc(1 - var(--open-next, 0)));
}

/* Nodes 46:4276 / 46:4277: Satoshi Variable BOLD at 15/1.5, up from Avantt
   SemiBold at 12/1.2. Figma trims the run to its cap height, which is why the
   row is 11 tall on 15px type — `normal` leading renders 20 and pushes the
   header past its designed height. */
.rdk-cases__eyebrow {
  display: flex;
  align-items: center;
  gap: calc(16 * var(--u));
  font-family: var(--rdk-font-body);
  font-weight: 700;
  font-size: calc(15 * var(--u));
  line-height: calc(11 * var(--u));
  letter-spacing: calc(1.5 * var(--u));
  font-feature-settings: "frac" 1;
  text-transform: uppercase;
  color: var(--rdk-grey);
}

.rdk-cases__title-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  width: 100%;
}

/* The desktop keeps the title on one line; node 1:2050 breaks it after
   "Selected". An explicit <br> rather than a measure that happens to force
   it — at 46px on a 350 measure "Selected Case" fits, so nothing about the
   width would produce the frame's break on its own. */
.rdk-cases__title-break {
  display: none;
}

  /* Display/L, from the file's own variables: 88 / lh 1 / tracking -4%.
     Tracking in that file is a PERCENTAGE, so -4 on 88 is -3.52 -- it was read
     as literal px here and shipped at -5.28, i.e. -6%, 50% tighter than the
     token and tighter than Display/XL above it. Corrected 2026-09-09; do not
     put -5.28 back. */
.rdk-cases__title {
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(88 * var(--du));
  line-height: 1;
  letter-spacing: calc(-3.52 * var(--du));
  color: var(--rdk-off-white);
}

/* NOTE: the 2026-08-25 frame drops this block — node 1:707 holds the title
   alone. Left at its Avantt-era 12/1.2/600 rather than rescaled to the new
   15/1.5 label style, because a 3-line stat list at 15 would not fit the
   88 title row. Confirm whether it is meant to stay at all. */

/* Figma sets the counts in "Avant Garde ITC BT Demi" against an Avantt
   label — a face pairing we do not have. Redonk Avant Garde IS an Avant
   Garde, so it carries the intended contrast. */

/* ---------- rows ---------- */

.rdk-cases__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* `--open` is the ONE thing that animates: 0 closed, 1 open. Every part of
   the open state is expressed as a function of it below, which means
   js/cases.js tweens a single property per row and every length stays in
   `--u` — so the row keeps working through a viewport resize mid-hover,
   which tweening resolved pixel values would not. */
.rdk-cases__item {
  --open: 0;
  /* Set on the row BEFORE an open one (and on the header, for the first row)
     so the rule sitting above an expanded card fades out with it. */
  --open-next: 0;
  /* Per-case colour, set by the `[data-case]` rules below. */
  --case-bg: var(--rdk-case-datascan);
  --reveal-h: calc(472 * var(--u)); /* 72u gap + 400u of artwork */
  position: relative;
  padding: 0 calc(40 * var(--u));
  border-bottom: calc(0.5 * var(--u)) solid
                 rgba(85, 85, 85, calc(1 - max(var(--open), var(--open-next))));
}

/* Per-case theming. This lives HERE and not in an inline style on the item
   because a `url()` inside a custom property is resolved against the
   stylesheet that USES the property, not the document — so an inline
   `--case-img: url(assets/...)` resolves to /css/assets/... and 404s. Colours
   would work inline, but keeping both together means there is one place to
   theme a case study. */
/* No rule under the LAST study — that line has nothing below it to separate
   and reads as the section being cut off rather than closed (client,
   2026-08-25). Safe against the --open-next machinery: js/cases.js only ever
   sets that on a PREVIOUS sibling, so the final row's own bottom border is
   decoration and nothing reads it. */
.rdk-cases__item:last-child {
  border-bottom: 0;
}

/* Node 689:10256 (2026-09-15) draws all four cards as a FLAT fill, but
   nuHarbor KEEPS its rotated ripple photo over the fill (client, same day) —
   the one place the build deviates from that frame. `--case-img` is optional
   and nothing else declares it, so a card that wants artwork is one line. */
.rdk-cases__item[data-case="datascan"] {
  --case-bg: var(--rdk-case-datascan);
}

.rdk-cases__item[data-case="everpure"] {
  --case-bg: var(--rdk-case-everpure);
}

.rdk-cases__item[data-case="exabeam"] {
  --case-bg: var(--rdk-case-exabeam);
}

.rdk-cases__item[data-case="nuharbor"] {
  --case-bg: var(--rdk-case-nuharbor);
  --case-img: url(../assets/img/cases/nuharbor-bg.webp);
}

.rdk-cases__card {
  position: relative;
  /* 48 sides in BOTH states, so content sits at 88 open or closed and there
     is no horizontal shift on hover. The bottom padding CLOSES as the card
     opens — 64 -> 48.5 (node 1:701, 2026-08-26), where an earlier pass had it
     growing to 96.5. The whole open card is then 48 + 128 + 472 + 48.5 =
     696.5 and the closed row 48 + 120 + 64 = 232, both exactly as Figma. */
  padding: calc(48 * var(--u))
           calc(48 * var(--u))
           calc((64 - 15.5 * var(--open)) * var(--u));
}

/* The colour is its own layer rather than a background on the card, so it can
   fade independently without taking the content's opacity with it — and so
   GSAP never has to interpolate from `transparent`, which fades through
   transparent BLACK and dirties the mid-frames. */
.rdk-cases__bg {
  position: absolute;
  inset: 0;
  /* `--case-img` is optional: only nuHarbor has artwork over its fill, and a
     card without one falls back to `none` and just shows the colour. `cover`
     scales the photo up to fill the card exactly — the card is `inset: 0` on
     this layer, so there is no gutter to account for. */
  background-color: var(--case-bg);
  background-image: var(--case-img, none);
  background-position: center;
  background-size: cover;
  border-radius: calc(64 * var(--u));
  opacity: var(--open);
  pointer-events: none;
}

/* 104 closed, 128 open (node 689:10256), and both are FLOORS.
   Open, the number is what is on screen: the LEAD column is name 51 + 24 +
   the 52.5 button = 127.5, and the button is absolutely placed so it adds no
   height of its own — which is the whole reason this is stated rather than
   left to content.
   Closed, the frame's 104 is 24 of meta padding over an 80-tall pill stack
   (three pills at 24 plus two gaps of 4 — it was 96 when there were four),
   giving its 216-tall card. OURS MEASURES 226, and the 10 is the body scale
   paying for itself: the description is the scale's standfirst step at 20/1.5
   where the frame sets it 18/1.4, so its three lines are 90 rather than 75.6
   and the DESCRIPTION is what sets the closed row, not the pills. That is the
   2026-09-09 pass's decision and it is deliberate — do not shrink the type
   here to recover the 10. */
.rdk-cases__row {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  min-height: calc((104 + 24 * var(--open)) * var(--u));
}

.rdk-cases__lead {
  display: flex;
  flex: 1 0 0;
  flex-direction: column;
  align-self: stretch;
  justify-content: space-between;
  min-width: 0;
}

.rdk-cases__name {
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(51 * var(--du));
  line-height: 1;
  letter-spacing: calc(-1.37 * var(--du));
  color: var(--rdk-off-white);
}

/* A direct child of the CARD, not of the lead column, so that mobile can put
   it in normal flow at the bottom (where node 1:2049 has it, full width)
   while the desktop keeps it under the name. Absolute here: 48 of card
   padding + the 75 Figma gives it inside the top block. */
.rdk-cases__btn-group {
  /* `--push` drives the hover: 0 at rest, 1 fully shoved. Same trick as
     `--open` — js/cases.js tweens the one property and CSS does the
     arithmetic, so the sizes stay in `--u`. */
  --push: 0;
  position: absolute;
  left: calc(48 * var(--u));
  top: calc(123 * var(--u));
  display: inline-flex;
  align-items: flex-end;
  opacity: var(--open);
  /* Not a tab stop while the row is closed — it is invisible there. */
  pointer-events: none;
}

.rdk-cases__item:hover .rdk-cases__btn-group,
.rdk-cases__item:focus-within .rdk-cases__btn-group {
  pointer-events: auto;
}

.rdk-cases__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: calc(52.465 * var(--u));
  /* The pill grows a little on hover and THAT is what shoves the chip along —
     the chip is its flex sibling, so the push is layout, not a number anyone
     had to measure. `--grow` is the pill's own spring. */
  padding: 0 calc((24.332 + var(--grow-travel, 7) * var(--grow, 0)) * var(--u));
  margin-right: calc(-2 * var(--u));
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(17.968 * var(--u));
  line-height: 1;
  white-space: nowrap;
  color: var(--rdk-white);
  border: calc(2.281 * var(--u)) solid var(--rdk-white);
  border-radius: calc(39.539 * var(--u));
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* Two bodies, not one animation (client, 2026-08-26). The pill grows and
   pushes; the chip has its own mass and carries on past where the pill left
   it, then falls back and COLLIDES with the pill's edge rather than sliding
   through it as it used to.

   `--push` is the chip's own offset on top of the layout push, and js keeps
   it at or above 0 so the chip can never overlap the pill. `--squish` is the
   impact: it lands on the frame of the collision and decays, squashing the
   chip along the axis it hit and letting it bulge across — volume-ish
   preserved, and scaleY only ever reaches ~1.1 so it still reads as a
   deformation and not as the circle growing, which the client ruled out. */
/* The chip's ring is drawn in CSS and only the chevron is SVG — the same
   arrangement as the About CTA and the dialog's Submit, and it is what lets the
   button go solid on hover. As an `<img>` (which this was) the ring and the
   chevron shared one hard-coded `#ffffff` and neither could invert. The svg is
   given the chip's FULL size rather than 100% of the content box, so the
   chevron lands exactly where the old asset put it inside the 52 viewBox
   instead of being scaled down by the border. */
.rdk-cases__btn-arrow {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(52 * var(--u));
  height: calc(52.465 * var(--u));
  color: var(--rdk-white);
  border: calc(2.281 * var(--u)) solid currentColor;
  border-radius: 50%;
  translate: calc(var(--push, 0) * 1px) 0;
  scale: calc(1 - 0.16 * var(--squish, 0)) calc(1 + 0.11 * var(--squish, 0));
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

.rdk-cases__btn-arrow svg {
  flex: none;
  width: calc(52 * var(--u));
  height: calc(52.465 * var(--u));
}

/* Solid on hover (client, 2026-08-28). Both bodies fill white and their ink
   becomes the CARD's own colour, so the button reads as one object inverting
   rather than two shapes changing separately. Scoped to the GROUP, not the
   anchor, so pointing at the chip inverts the pill too — they are one control,
   and the shove is triggered from the group for the same reason. */
.rdk-cases__btn-group:hover .rdk-cases__btn,
.rdk-cases__btn-group:focus-within .rdk-cases__btn,
.rdk-cases__btn-group:hover .rdk-cases__btn-arrow,
.rdk-cases__btn-group:focus-within .rdk-cases__btn-arrow {
  color: var(--case-bg);
  background: var(--rdk-white);
}

/* AND THE CHIP'S RING HAS TO BE PINNED WHITE, which the note above used to
   claim it did not need. Its border is `currentColor` — white at rest, and
   correct there — but the ink DELIBERATELY becomes --case-bg on hover, so the
   ring followed the ink and drew the card's own colour around a white disc.
   The pill escapes it only because its border is a literal --rdk-white.
   Client, 2026-09-15: the filled button should be a solid white circle with no
   outline. Only the CHEVRON takes the card's colour, and it still does — it is
   an inline svg on `currentColor`, which this rule does not touch. */
.rdk-cases__btn-group:hover .rdk-cases__btn-arrow,
.rdk-cases__btn-group:focus-within .rdk-cases__btn-arrow {
  border-color: var(--rdk-white);
}

/* ---------- the drag affordance (node 114:15246) ----------
   Replaces the paging arrows that used to sit in the card's bottom-right
   (client, 2026-08-26). A glass pill that appears while the pointer is over
   the artwork and follows it, saying what the strip does.

   TWO elements on purpose: `__drag` is a zero-size POINT that GSAP moves, and
   `__drag-pill` centres itself on it with a standalone `translate`. GSAP then
   owns `transform` outright — the same split as the hero's DONK rig — and
   because the centring is a percentage of the pill's own box rather than a
   hard-coded margin, the pill can be resized from the stylesheet without
   anyone having to recompute half of it. */
.rdk-cases__drag {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
  width: 0;
  height: 0;
  opacity: 0;
  pointer-events: none;
}

.rdk-cases__drag-pill {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(12.199 * var(--u));
  padding: calc(12.174 * var(--u)) calc(18.551 * var(--u));
  translate: -50% -50%;
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(15 * var(--u));
  line-height: 1;
  letter-spacing: calc(0.3 * var(--u));
  white-space: nowrap;
  color: var(--rdk-white);
  /* Node 114:15246 as specified, at the client's direction (2026-08-26) after
     I had substituted a darker plate for contrast. The reasoning for keeping
     the comp's value: the pill only ever sits over photography, never over
     flat white, so the 30% tint picks up the colour behind it rather than
     washing out — which is the effect the design is after. The blur is 30
     rather than the node's 15.248, also per the client. */
  background: rgba(71, 93, 102, 0.3);
  -webkit-backdrop-filter: blur(calc(30 * var(--u)));
  backdrop-filter: blur(calc(30 * var(--u)));
  border-radius: calc(30.145 * var(--u));
}

/* Inlined rather than an <img> so the carets take `currentColor`, which does
   not cross into an external SVG. One shape, mirrored for the left. */
/* 5.878 x 8.928, which is the size the asset RENDERS at — not the 3.05 x
   6.099 the node reports for the chevron's path. The 2px stroke with square
   caps adds 1.414 on every side, and Figma's own export carries that in its
   viewBox. Taking the path's bounding box for the element size makes the
   arrows about half the width they should be. */
.rdk-cases__drag-caret {
  flex: none;
  width: calc(5.878 * var(--u));
  height: calc(8.928 * var(--u));
}

.rdk-cases__drag-caret--back {
  scale: -1 1;
}

/* ---------- the mobile scroll indicator ----------
   Desktop has the drag pill; a touch screen has no pointer to hang it on, so
   node 1:2049 shows position instead — the active segment stretched into a
   bar with the rest left as dots. Built by js/cases.js from the real thumb
   count, so it stays right when artwork is added. */
.rdk-cases__progress {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 1.6vw;
}

.rdk-cases__progress span {
  display: block;
  flex: none;
  width: 1.4vw;
  height: 1.4vw;
  background: rgba(255, 255, 255, 0.4);
  border-radius: 999px;
  transition: width 0.35s ease, background-color 0.35s ease;
}

.rdk-cases__progress span[data-active] {
  width: 29.7vw;
  background: var(--rdk-white);
}

.rdk-cases__meta {
  display: flex;
  flex: 1 0 0;
  align-items: flex-start;
  justify-content: space-between;
  min-width: 0;
  padding-top: calc(24 * var(--u));
}

/* Node 689:10243 and siblings. Measure 418 (was 394), and 80% opacity.
   ss02/ss03 are the same pair the intro's paragraph carries: single-storey
   `g`, alternate `t` and `4`.
   The SIZE is deliberately not the frame's: it stays on the body scale's
   standfirst step (--rdk-body-l, 20/1.5) where the frame says 18/1.4, which
   is the 2026-09-09 pass's decision and applies across the site. */
.rdk-cases__desc {
  width: calc(418 * var(--u));
  font-family: var(--rdk-font-body);
  font-weight: var(--rdk-body-weight);
  font-size: var(--rdk-body-l);
  line-height: var(--rdk-body-lh);
  letter-spacing: var(--rdk-body-track);
  font-feature-settings: "ss03" 1, "ss02" 1;
  color: var(--rdk-white);
  opacity: 0.8;
}

/* 112, where the node says 104. The column is right-aligned in the meta by
   `space-between`, so its WIDTH is what sets the pills' shared left edge —
   and the node keeps that edge identical on all four cards (every "category
   pills" frame is 104 at x=608, while the stack inside it hugs its content at
   105 / 92 / 105). So this has to stay a FIXED width, not `max-content`,
   which would rag each card's stack differently.
   104 is exactly the widest pill ("Product Design") at the frame's 12px type;
   112 is that same pill at the 13 the client asked for, measured at 111.3.
   A longer label than that overhangs the card's 88 content line rather than
   moving the edge — which is what the node itself does, by 1. */
.rdk-cases__facts {
  display: flex;
  flex: none;
  flex-direction: column;
  gap: calc(24 * var(--u));
  width: calc(112 * var(--u));
}

.rdk-cases__tags {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(4 * var(--u));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Node 793:93122 and siblings (2026-09-15). Re-cut: the uppercase Satoshi
   MEDIUM 10 chips with their alternating pill/rectangle corners are gone, and
   every pill is now sentence-case Satoshi BOLD 12 on a fully round 1u ring.
   Both the ink and the ring are `--rdk-off-white` (#eff2f3), not white.
   The HEIGHT is stated, not left to the padding, because the frame states it:
   its own 10 of vertical padding would give 33 where the pill is drawn 24,
   and 3 pills at 24 plus 2 gaps of 4 = the 80 it gives the column — which is
   what sizes the closed row. base.css is border-box, so the 1u ring is inside
   the 24 and the label centres in the 22 that is left.
   THE SIZE IS 13, ONE STEP OVER THE FRAME'S 12 (client, 2026-09-15). Only the
   type moved: the 24 box, the ring and the 4 gap are the node's, so the stack
   is still 80 and the closed row still 104. The pills got ~7 wider, which is
   why `.rdk-cases__facts` went 104 -> 112 — see there. */
.rdk-cases__tags li {
  display: flex;
  align-items: center;
  height: calc(24 * var(--u));
  padding: 0 calc(8 * var(--u));
  font-family: var(--rdk-font-body);
  font-weight: 700;
  font-size: calc(13 * var(--u));
  line-height: 1.015;
  white-space: nowrap;
  color: var(--rdk-off-white);
  border: calc(1 * var(--u)) solid var(--rdk-off-white);
  border-radius: 9999px;
}

/* ---------- the strip that unfolds ---------- */

/* Height is animated 0 -> 472u (72u gap + 400u of artwork). Both are design
   constants, so nothing has to be measured at runtime and there is no
   height:auto tween. */
/* Pulled out to BOTH card edges, so the scroller spans the full card rather
   than the padding box. That is what lets the artwork run off the left edge
   as you drag through it (client, 2026-08-26) instead of being clipped 48
   short of it. The first tile keeps its alignment with the title above via
   padding on the track, not by insetting the scroller. */
.rdk-cases__reveal {
  position: relative;
  margin-left: calc(-48 * var(--u));
  margin-right: calc(-48 * var(--u));
  height: calc(var(--reveal-h) * var(--open));
  overflow: hidden;
}

.rdk-cases__strip {
  padding-top: calc(72 * var(--u));
  overflow-x: auto;
  /* Explicit, and load-bearing. Setting only `overflow-x` forces the other
     axis from `visible` to `auto`, so the strip silently became a VERTICAL
     scroll container as well — and the thumbnail cascade animates `y: 18/30`,
     which counts toward scrollable overflow. That gave it ~30px of vertical
     range while a row was opening, so a wheel over the artwork scrolled those
     30px instead of the page, and Chrome's scroll latching then kept the rest
     of the gesture on the strip. It presented as the page intermittently
     refusing to scroll while the cursor was over an image.
     Nothing is lost by clipping here: the reveal is the same height and
     already clips the same overflow. */
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  /* Click-and-drag, wired in js/cases.js. `grab` is the standard signal for
     it and complements the pill rather than replacing it — the pill says what
     the gesture does, the cursor says it is available here. */
  cursor: grab;
  /* Or the drag selects the artwork instead of scrolling it. */
  user-select: none;
  -webkit-user-select: none;
  /* The comp shows no scrollbar; the strip is meant to read as artwork that
     happens to continue past the edge. */
  scrollbar-width: none;
}

.rdk-cases__strip::-webkit-scrollbar {
  display: none;
}

.rdk-cases__strip.is-dragging {
  cursor: grabbing;
  /* The strip is scrolled by script while dragging; letting the browser also
     smooth-scroll it would fight the pointer. */
  scroll-behavior: auto;
}

/* Chrome will happily start a native image drag from a <img>, which hijacks
   the gesture and leaves a ghost trailing the cursor. */
.rdk-cases__thumb img {
  -webkit-user-drag: none;
  user-select: none;
}

/* The insets live HERE, on the track, not on the scroller. Padding on a
   scroll container is unreliable at the far end — several engines drop the
   trailing side out of `scrollWidth` — whereas this element is
   `width: max-content`, so its padding is part of its own width and is
   always scrolled to.

   Left 48 puts the first tile at the designed x=88 while the scroller itself
   still reaches the card's edge, so tiles pass off it rather than being cut.
   Right 48 is what stops the last tile 48 short of the card's right edge
   instead of hard against it (client, 2026-08-26). */
.rdk-cases__thumbs {
  display: flex;
  align-items: flex-start;
  gap: calc(16 * var(--u));
  width: max-content;
  margin: 0;
  padding: 0 calc(48 * var(--u));
  list-style: none;
}

/* NOTE the drag physics are NOT here, and cannot be.
   `js/cases.js` gives each tile a hang-back spring while the strip is moving,
   and it drives it through GSAP rather than through this stylesheet. The
   open/close cascade animates `y` on these same tiles, and the moment GSAP
   takes a transform over it writes `translate: none; rotate: none; scale: none`
   inline — which silently kills any standalone `scale`/`translate` declared
   here. (Verified: the tiles' computed transform froze at whatever the first
   frame set and never moved again, while the custom property feeding it went
   on updating perfectly.) One element, one transform, one owner. */
/* THE FOCAL SLOT (client, 2026-09-15, after originkit's Coverflow Carousel).
   The tile sitting at the LEFT of the card — where node 689:10256 already
   draws the big one — is `--focus: 1` and stands 400 tall; every other tile
   eases down to 300 as it leaves. js/cases.js writes the one property and
   this rule derives both dimensions from it, the same arrangement as `--open`
   on the row.

   THE THREE NUMBERS LIVE HERE AND THE JS READS THEM BACK, so there is one
   place to change them. `--squeeze` is how much narrower a non-focal tile is
   than its own proportions: 0 reproduces the design exactly (its non-focal
   tile is 436 x 300, which IS 581.5 x 400 scaled to 300), and raising it
   crops the tile into the narrow slab the reference uses. */
.rdk-cases__thumbs {
  --tile-min: 300;
  --tile-max: 400;
  --squeeze: 0;
}

/* `--w` / `--h` are the artwork's own box, 400 tall with the width following
   it (client, 2026-09-15). Height is the dimension that has to agree in a
   strip that travels SIDEWAYS; width is free because you scroll through it,
   so nothing crops or letterboxes to fit a box. The reveal can therefore stay
   a constant 472u (72 of gap + 400 of artwork) with nothing measured. */
.rdk-cases__thumb {
  --focus: 0;
  --tile-h: calc((var(--tile-min)
                  + (var(--tile-max) - var(--tile-min)) * var(--focus)) * var(--u));
  flex: none;
  width: calc(var(--tile-h) * var(--w) / var(--h)
              * (1 - var(--squeeze) * (1 - var(--focus))));
  height: var(--tile-h);
  border-radius: calc(16 * var(--u));
  overflow: clip;
}

/* With no JS the first tile is the focal one, which is the state the design
   draws — so the strip reads correctly before the ticker has run once, and
   under reduced motion, where js/cases.js never touches `--focus` at all. */
.rdk-cases__thumb:first-child {
  --focus: 1;
}

.rdk-cases__thumb img {
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}

/* ---------- no-hover devices ----------
   There is no hover to expand with, so every card is simply shown open. The
   strip is horizontally scrollable, which is a native gesture on touch. */
@media (hover: none) {
  /* `--open: 1` used to live here, pinning every row open because there was
     no hover to open one with. js/cases.js drives them from SCROLL now
     (client, 2026-08-27: one at a time, opening as it arrives), so the
     resting state is CLOSED — which is also what node 1:2049 shows, and a
     sane no-JS fallback: name, description and tags, just no artwork. */

  .rdk-cases__btn-group {
    pointer-events: auto;
  }

  /* There is no pointer to hang the drag pill on, and the gesture is native
     anyway — the indicator carries the same information instead. */
  .rdk-cases__drag {
    display: none;
  }

  /* THE INDICATOR IS PHONE-ONLY — see the `max-width: 767px` block, which is
     where it is switched on. It used to be here, and on a tablet that showed
     it with none of the geometry that makes it fit: it lives INSIDE
     `.rdk-cases__reveal`, which is `overflow: hidden` and sized to the strip
     alone, and the phone block is what gives the reveal the extra height and
     the indicator its 5.6vw top margin. Measured at 1024 it was clipped
     ENTIRELY — all 14px of it below the reveal's bottom edge — and its dots
     are `vw`-sized, so the active bar was 304px (client, 2026-09-16, QA on a
     tablet). A tablet therefore has no strip affordance at all: the drag
     pill needs a pointer and the indicator needs the phone's box. The
     gesture is native and discoverable, which is the same bet the phone
     makes about vertical scrolling. */
}

@media (max-width: 767px) {
  /* Node 1:2050, in px — the phone frame gives these their own absolute
     sizes rather than scaling the desktop's. */
  .rdk-cases__head {
    gap: 16px;
    padding: 72px 20px 40px;
  }

  .rdk-cases__eyebrow {
    gap: 8px;
    font-size: 11px;
    line-height: normal;
    letter-spacing: 1.32px;
  }

  /* The frame's eyebrow is the words alone — no trailing slash. (The
     leading dot came off every breakpoint on 2026-09-24.) */
  .rdk-cases__eyebrow span[aria-hidden] {
    display: none;
  }

  .rdk-cases__title-row {
    flex-direction: column;
    align-items: flex-start;
  }

  .rdk-cases__title-break {
    display: inline;
  }

  .rdk-cases__title {
    font-size: 46px;
    line-height: 1;
    letter-spacing: -1.84px;
    white-space: normal;
  }

  /* ---------- a STICKY STACK (2026-08-27) ----------
     Every card sticks to the top of the window and the next one slides up
     over it, like a deck being dealt. This is the standard answer to the
     problem and it replaces four passes of trying to make a reflowing list
     behave.

     WHAT IT DISSOLVES. Everything before this had the list REFLOW: one card
     gave up 411px of height at the same instant the next took it, because
     otherwise the page jumped. That single constraint is what produced every
     symptom — the skipped rows, the head shooting off the top, and two
     part-expanded cards on screen at once. A stack has no reflow at all. The
     cards are simply their full size, in flow, one after another; the only
     thing that changes is which one is on top, and that is `position: sticky`
     doing it in the compositor.

     So: no height trading, no pull on the card below, no overlap to time, and
     no JavaScript — `initScrollOpen` is not called on touch any more.

     `--open: 1` because a card in a stack is always itself. The stylesheet
     did exactly this before the one-at-a-time pass; the difference is that
     covering is what now enforces "one at a time" — you can only ever SEE the
     top card, whatever the ones underneath are doing.

     `top: 0` rather than an offset under the nav: the cards are full-bleed
     here, so a card pinned to the very edge reads as the page, and the nav
     hides on the way down anyway. */
  .rdk-cases__item {
    padding: 0;
    position: sticky;
    top: 0;
    --open: 1;
  }

  .rdk-cases__reveal {
    margin-left: -20px;
    margin-right: -20px;
  }

  /* 40 top, 20 at the sides — the sides match the header's own inset so the
     name, the description and the tags all sit on one measure.

     THE BOTTOM IS 64, NOT THE FRAME'S 40, AND THE DIFFERENCE IS BUYING SCROLL
     (client, 2026-08-28: "View project" was covered up too fast). The studies
     are a sticky stack: each card pins at `top: 0` and the next slides up
     over it, so the scroll a reader gets before the next card reaches the
     button is EXACTLY this padding — the button's bottom sits that far above
     the card's own bottom edge, which is where the next card starts. At 40 it
     was ~41px, gone within a flick; at 64 it is ~65.

     160 was tried first and is far too much (client): the buffer wanted here
     is small. Note the space is invisible while the card is pinned — the card
     is about the height of the phone, so the extra sits below the fold — which
     is exactly why it is easy to overshoot by reasoning about it rather than
     looking at it. */
  .rdk-cases__card {
    padding: 40px 20px 64px;
  }

  /* 30, measured off the frame's own corner arc — it was 6vw (23.4). */
  .rdk-cases__bg {
    border-radius: 30px;
    transition: border-radius 0.18s ease;
  }

  /* Pinned to the top of the window, the top corners go square — otherwise
     they reveal the page ground behind them as two black notches in the
     corners of the screen. `js/cases.js` (initStackCorners) sets the class.
     A card keeps its rounded top for the whole of its slide up, which is when
     it reads as a card arriving; once it is the one being read it is a
     full-bleed slab. */
  .rdk-cases__item.is-stuck .rdk-cases__bg {
    border-radius: 0 0 30px 30px;
  }

  /* Nothing sets the row's height here — it stacks, so its content does. */
  .rdk-cases__row {
    min-height: 0;
  }

  /* The two-column split needs 1424u; below 768 the row stacks and the meta
     block sits under the name. */
  .rdk-cases__row {
    flex-direction: column;
    gap: 27px;
  }

  .rdk-cases__lead {
    gap: 27px;
  }

  .rdk-cases__name {
    font-size: 34px;
    line-height: 1;
    letter-spacing: -1.36px;
  }

  /* Out of the lead column and into the flow at the bottom of the card, full
     width, with the circled chevron dropped — node 1:2049. Its height and the
     gap above it both scale with --open, so a CLOSED row adds nothing —
     which is the resting state on touch now that scroll drives them. */
  .rdk-cases__btn-group {
    /* `relative`, NOT `static`. `.rdk-cases__bg` is an absolutely positioned
       sibling, so it paints above every non-positioned box in the card — a
       static button here is simply covered by the card's own colour. It is
       still in normal flow either way; the name and the tags only escape this
       because their wrappers are already `relative`. And `left`/`top` must be
       cleared with it: the desktop rule sets them to place the button
       absolutely, and under `relative` those same values become OFFSETS —
       11.7px right and 30px down at 390 wide, which is exactly how far the
       button drifted. */
    position: relative;
    left: auto;
    top: auto;
    display: flex;
    /* THE GROUP'S HEIGHT IS THE BUTTON'S HEIGHT, and it has to be kept in step
       with it. This is the reveal: the group grows with `--open` and clips
       what does not fit yet, so anything taller than the number here is cut.
       It was `11vw` — the button's own old height — and when the button went
       to the design's 56 (node 267:41720) it was left clipping 9px off the
       top of it, which is the pill's top stroke.
       Node 1:2072 also puts the button on the card's full content measure
       with 20 above it, so the side margin is 0 and the top is 20 rather than
       the 11.8vw / 4.1vw this was carrying. */
    height: calc(56px * var(--open));
    margin: calc(20px * var(--open)) 0 0;
    overflow: hidden;
  }

  /* Node 267:41720: 350 x 56 (full width inside the card's 20 inset), a 2
     white border, 12/20 padding, fully round, label Redonk Avant Garde Bold
     15.272 — replacing a vw-derived set that made it 43 tall at 390. The
     height is stated rather than left to the padding, the same reason the
     nav's pills state theirs. */
  .rdk-cases__btn {
    flex: 1 1 auto;
    height: 56px;
    padding: 12px 20px;
    margin-right: 0;
    font-size: 15.272px;
    line-height: 1;
    border-width: 2px;
    border-radius: 999px;
  }

  .rdk-cases__btn-arrow {
    display: none;
  }

  .rdk-cases__meta {
    flex-direction: column;
    /* 14px between the description and the tags, per node 1:2049 — 6vw put
       them 23 apart and pushed everything below out of register. */
    gap: 3.6vw;
    padding-top: 0;
  }

  .rdk-cases__desc {
    width: 100%;
  }

  .rdk-cases__facts {
    gap: 4vw;
    width: 100%;
  }

  /* The tags run as a wrapping row down here rather than a 104u column, and
     they need their own gap in px: the designed 4u is 0.94px at 375, which
     lets the pill borders sit on top of each other. */
  .rdk-cases__tags {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 6px;
  }

  /* EVERY --u-sized value on the pill has to be restated here, not just the
     type: the desktop's 24u box is 5.6px at 375 and its 1u ring is 0.23px, so
     a partial override leaves a hairline sliver with the label spilling out
     of it. Straight px, as everywhere else on the phone.
     12/22 keeps the three pills on ONE row, as node 1:2049 has them — the
     widest set (Everpure's) comes to 259 against a 335 measure, so there is
     room even though the labels are longer than the four uppercase chips this
     replaced. 12 is the desktop's 13 less one, the same step down the phone
     block already takes on the type around it. */
  .rdk-cases__tags li {
    height: 22px;
    padding: 0 8px;
    font-size: 12px;
    line-height: 1.015;
    border-width: 1px;
  }

  .rdk-cases__item {
    /* Sized off the viewport down here, not off --u, so the reveal height has
       to follow: 11.5vw of gap + 54.4vw of artwork + 5.6vw + 1.4vw for the
       indicator underneath it. */
    --reveal-h: 72.9vw;
  }

  .rdk-cases__strip {
    padding-top: 11.5vw;
  }

  .rdk-cases__progress {
    display: flex;
    margin-top: 5.6vw;
  }

  /* The tiles keep their aspect ratios but are sized off the viewport, since
     600u is 141px at 375 — unreadably small for a screenshot. A uniform
     height needs nothing extra here: this already drives width FROM height,
     which is the same rule the desktop now follows.
     THE FOCAL SLOT IS DESKTOP-ONLY, and these two lines are what turn it off:
     stating both dimensions makes `--focus` inert, so the phone keeps its
     native scroller, its snap dots and six equal tiles. A slot that picks one
     tile out needs a pointer to move between them, and there is no room on a
     375 card for a 400-tall tile beside 300-tall ones anyway. */
  .rdk-cases__thumb {
    width: calc(var(--w) / var(--h) * 54.4vw);
    height: 54.4vw;
    border-radius: 3vw;
  }

  .rdk-cases__thumbs {
    gap: 3vw;
    padding: 0 5.2vw;
  }
}

/* ---------- tablet portrait: the measure (2026-09-16) ----------
   Two faults, and both are the collapsing unit meeting type that no longer
   collapses with it. Scoped to PORTRAIT because that is where they were
   reported and because a landscape tablet is wide enough for neither to
   bite; desktop is not in this query at all. */
@media (min-width: 768px) and (orientation: portrait) {
  /* THE MEASURE WAS IN `--u` WHILE THE TYPE IS FLOORED IN px, SO
     CHARACTERS-PER-LINE HALVED (client: "the descriptor text has too short a
     measure and there is plenty of room to the left").

     Nothing was proportionally wrong — at 820 the row splits 357/357 exactly
     as the design splits 688/688, and `418 * --u` is 214, exactly the
     design's share. What changed is that the copy is 17px here where the
     design's is 20px: 418/20 is 20.9em a line, 214/17 is 12.6em, so three
     designed lines became five and six.

     SO THE MEASURE IS STATED IN THE TYPE'S OWN UNITS. `418em / 20` is the
     node's measure over the node's font size, which is 418px wherever the
     type is `--u`-scaled — i.e. it reproduces the design exactly on every
     desktop width — and 355 here, restoring the designed line count. A
     measure belongs to the font, not to the viewport. */
  .rdk-cases__desc {
    width: calc(418em / 20);
  }

  /* THE ARTWORK IS 10% BIGGER AND THE CARD IS NOT (client, 2026-09-17: "make
     the images maybe 10% bigger on tablet portrait only? But don't make the
     sections bigger, this should help fill the gap between copy and images").

     THE TWO ASKS ARE ONE SUM. The strip's height is a design CONSTANT —
     `--reveal-h` is 472u, stated as 72 of gap plus 400 of artwork, and
     nothing about it is measured at hover time. So 400 -> 440 has to come out
     of the 72: 440 + 32 = 472, the card is byte-identical, and the 40u the
     images gained is exactly the gap they were asked to fill. Raising the
     tiles without paying for them would have grown the reveal past its own
     height and `overflow: hidden` would have cropped the artwork's feet.

     `--tile-min` AND `--tile-max` BOTH MOVE, or the coverflow changes shape
     rather than scale: the focal tile is `min + (max - min) * --focus`, so
     scaling only the max would stretch the difference between a focal tile
     and its neighbours by 40% instead of 10%. 300 -> 330 with 400 -> 440.
     js/cases.js reads both back out of the stylesheet, so it needs no edit —
     that is the arrangement those two numbers exist for. */
  .rdk-cases__thumbs {
    --tile-min: 330;
    --tile-max: 440;
  }

  .rdk-cases__strip {
    padding-top: calc(32 * var(--u));
  }

  /* AND THE ROOM COMES FROM THE LEFT, WHICH IS WHAT WAS ACTUALLY SPARE.
     The lead column holds the client's name and nothing else — 116px of ink
     in 357 — so the meta column is sized by what it has to CARRY (the
     measure, the gap, the pills) and the lead simply takes the rest. That is
     a derivation rather than a picked share: it lands 279/436 here, so the
     name keeps more slack than the design gives it at 1600 while the
     description gets the width it needs. */
  .rdk-cases__lead {
    flex: 1 1 auto;
  }

  .rdk-cases__meta {
    flex: 0 1 auto;
    gap: calc(48 * var(--u));
  }
}

/* ---------- the touch target floor (2026-09-16) ----------
   THE BUTTONS ARE PURE `N * --u` WITH NO FLOOR, AND THIS IS A TOUCH DEVICE
   (client: "the buttons also seem too small"). The pill renders 26.9 with a
   9.2px label at 820 — under every platform's 44 minimum, on a screen
   operated by a thumb. **The nav's CTA is the proof and the pattern**: it
   draws 44 there where its scaled value is 28.7, because it already carries
   px floors.

   IT ASKS ABOUT THE POINTER, NOT THE ORIENTATION, because a touch target is
   a property of the DEVICE. Scoped to portrait it would have missed the
   landscape tablets that need it just as much — an iPad Air on its side is
   1180, where this pill computes to **38.7** — while a portrait-only rule
   also reaches a tall narrow DESKTOP window, which needs nothing. `hover:
   none` gets both right by construction: no desktop can match it, and every
   tablet does at either orientation.

   Above 768 only: the phone block below has its own designed numbers (a 56
   full-width pill from node 267:41720), and they must not be floored into.

   ONE FLOORED HEIGHT DRIVES ALL THREE PARTS, so the pill, the circle and the
   chevron keep the design's own ratios instead of each carrying a number
   that could drift. `max()` only ever raises, so it is a no-op from the
   width where the design's size overtakes it (about 1342) upward. */
@media (min-width: 768px) and (hover: none) {
  .rdk-cases__btn-group {
    /* AND IT SITS 8 LOWER, because the pill grew but its distance from the
       client's name did not (client, 2026-09-17: the buttons are "too close
       to the client name"). Measured on the desktop reference: a 24 gap under
       a 52.5 pill, i.e. 0.457 of its height. At the floored 44 that is 20,
       where `123 * --u` was leaving 12. Stated as a floor so it only ever
       applies where the pill is floored too — above about 577 wide the
       scaled value is the larger and takes over on its own. */
    /* 83, up from 71 (client, 2026-09-17: "more room between the client name
       and view project button"). Gap 20 -> 32. The taller closed row below is
       what pays for it — the button still clears the artwork by ~89. */
    top: max(83px, calc(123 * var(--u)));
    --btn-h: max(44px, calc(52.465 * var(--u)));
  }

  .rdk-cases__btn {
    height: var(--btn-h);
    font-size: max(15px, calc(17.968 * var(--u)));
    /* THE PADDING HAD TO BE FLOORED WITH THE HEIGHT, and leaving it out is
       what made the pill stubby (client, 2026-09-17: "the buttons are too
       narrow"). Flooring the height to 44 while the sides stayed at
       `24.332 * --u` — 12.5 at 820 — gave a 44-tall pill with half the
       padding it is drawn with. Derived from `--btn-h` rather than floored
       on its own, so the two cannot come apart again at any width; the
       `--grow` term rides along, so the shove still opens it by the same
       proportion. */
    padding: 0 calc(var(--btn-h)
      * (24.332 + var(--grow-travel, 7) * var(--grow, 0)) / 52.465);
    /* THE STROKE AND THE RADIUS GO THROUGH `--btn-h` TOO, and leaving them on
       `--u` is a bug I introduced with the height floor (client, 2026-09-17:
       "the stroke around view project looks different than the stroke around
       the arrow"). Exactly so: the CHIP's ring was already derived from
       `--btn-h` and came out 1.91, while the pill's border stayed
       `2.281 * --u` = 1.17 — two halves of one button at different weights.
       THE RADIUS WAS WRONG THE SAME WAY, and less obviously: 39.539 exceeds
       half of the designed 52.465, so the pill is FULLY ROUND by being
       over-specified. At `39.539 * --u` it is 20.3 against a 44 pill that
       needs 22, so the corners were quietly no longer round. */
    border-width: calc(var(--btn-h) * 2.281 / 52.465);
    border-radius: calc(var(--btn-h) * 39.539 / 52.465);
  }

  /* THE CATEGORY PILLS ARE TYPE, AND `--u` HALVED THEM (client, 2026-09-17:
     "the category tags are way too small"). At 820 the 13 rendered 6.7, the
     24 box 12.3 and the 1 ring 0.5 — almost exactly half, the same collapse
     the kicker and the scene had on the About hero. Floored at the design's
     own numbers, which are also close to the phone's designed 12/22.

     THE COLUMN HAS TO BE FLOORED WITH THEM OR THE PILLS BURST IT. It is a
     FIXED 112 rather than `max-content` on purpose — it is what sets the
     pills' shared left edge across all four cards, which the node holds
     identical — so at `112 * --u` (57 here) a 13px "Product Design" would
     overflow the very column that exists to line them up. */
  /* THE CLOSED ROW IS FLOORED SO THE BAND TAKES LONGER TO CROSS IT (client,
     2026-09-17: "the case studies transition from one to the other very
     quickly. Can we slow down?" — then "slightly taller... gentler").

     IT IS NOT THE ANIMATION, and slowing that alone would have made it worse.
     `initScrollOpen` picks on the reconstructed CLOSED layout, and at 820 a
     closed row is 142 tall — so the band crossed a card every 142px and all
     four in 426, about a third of a 1180 screen. At 1.05s of open, a card was
     still opening when the next took over, which is the cascade being
     reported: more duration there means MORE cards mid-transition, not fewer.

     104 -> a floored 111 makes the closed row 200 and four cards ~600px of
     scroll. The client took this over the 260/780 version as "slightly
     taller"; it also opens up the room the floored button needed.
     The `24 * --open` term is left scaling, so only the CLOSED height moves
     and an open card is exactly as it was. */
  .rdk-cases__row {
    /* 172, AND THE NUMBER IS SOLVED FROM THE MEASURED RESULT RATHER THAN
       FROM THE DECLARATION. `min-height` was not what set the closed row to
       begin with — the meta column's three-line description is taller than
       `104 * --u` — so a first pass floored it to the 111 the arithmetic
       suggested and moved the row 142 -> 169, not the 200 intended.
       142 gave 200; 172 gives 230, which is the client's "a tad" more on
       2026-09-17: four cards now take ~690px of scroll against the 426 that
       started this. */
    min-height: calc(max(172px, calc(104 * var(--u)))
      + 24 * var(--open) * var(--u));
  }

  .rdk-cases__facts {
    width: max(112px, calc(112 * var(--u)));
    gap: max(24px, calc(24 * var(--u)));
  }

  .rdk-cases__tags {
    gap: max(4px, calc(4 * var(--u)));
  }

  .rdk-cases__tags li {
    height: max(24px, calc(24 * var(--u)));
    padding: 0 max(8px, calc(8 * var(--u)));
    /* 12 ON TABLET PORTRAIT (client, 2026-09-17: "lets try 12px in all
       places on the category or service pills and see"). The `--u` term is
       kept, so desktop is untouched and the floor stops applying once the
       design's own size overtakes it. */
    font-size: max(12px, calc(13 * var(--u)));
    border-width: max(1px, calc(1 * var(--u)));
  }

  .rdk-cases__btn-arrow,
  .rdk-cases__btn-arrow svg {
    width: calc(var(--btn-h) * 52 / 52.465);
    height: var(--btn-h);
  }

  .rdk-cases__btn-arrow {
    border-width: calc(var(--btn-h) * 2.281 / 52.465);
  }
}
