/* Work index — design node 314:55811.

   Geometry is read from the frame, not eyeballed: the tile list is 1421 wide
   at x=88 (so 88u side padding inside the panel), every tile is 1421 x 625.5
   with a 22 gap, and inside each one the artwork is 982.4 x 593.5 against a
   366.6 text column, inset 16 from the tile's edge.

   THE SEVEN TILES ARE ALL THE SAME SIZE. The comp looks inconsistent because
   two of them put the image on the left and five put it on the right — that is
   the only thing that varies, and it is a modifier here rather than seven
   different components. */

/* ---------- hero (node 314:55812) ----------
   381u, not a full screen: this page's job is the work, so the hero is a band
   rather than an entrance. */
.rdk-work-hero {
  position: relative;
  height: calc(381 * var(--u));
  background: var(--rdk-green);
  overflow: clip;
}

/* The four flocked objects that used to sit here came out on 2026-09-02
   (client: they work better on the services page). The band is the green
   and the title; the markup, these rules and the entrance in js/work.js all
   went together. */
/* Display/XL — 104 / 0.94 / -5 (a percentage, so -5.2 here), the site's
   largest size, as on the services hero (client, 2026-09-02; it was the 88).
   The top drops by the difference in line box so the baseline stays where
   the frame puts it. */
.rdk-work-hero__title {
  position: absolute;
  left: calc(89.32 * var(--u));
  top: calc(221 * var(--u));
  margin: 0;
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(104 * var(--du));
  line-height: 0.94;
  letter-spacing: calc(-5.2 * var(--du));
  color: var(--rdk-white);
}

/* ---------- the white panel ----------
   Same shoulders and the same cover-reveal the intro uses on the homepage, so
   the two pages open the same way. */
/* WHITE, with green ONLY behind the shoulders.

   The panel's rounded corners cut into this section and what shows through
   them has to be the hero's green continuing under the white. Painting the
   WHOLE section green did that — and made every sub-pixel seam further down
   the page show as a green hairline, because the panel's bottom edge lands on
   a fractional pixel (4804.09) and neither box covers it fully. That was the
   "divider line" (client, 2026-08-31). A strip behind the corners is all the
   green that was ever needed. */
.rdk-work {
  position: relative;
  z-index: 1;
  background: var(--rdk-white);
}

.rdk-work::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  /* Taller than the 93.5 radius so the arc is fully backed. */
  height: calc(120 * var(--u));
  background: var(--rdk-green);
}

/* Positioned, so it paints ABOVE the ::before — both are positioned, and DOM
   order then decides. */
.rdk-work__panel {
  position: relative;
}

.rdk-work__panel {
  border-radius: calc(93.5 * var(--u)) calc(93.5 * var(--u)) 0 0;
  background: var(--rdk-white);
  /* 160 below the last tile (client, 2026-08-31). ALL of the space lives here
     rather than being split with the footer's own top padding, so there is no
     colour join in the gap at all — the footer's white begins exactly where
     the green CTA band does, and the band hides the seam. */
  padding: calc(24 * var(--u)) calc(88 * var(--u)) calc(160 * var(--u));
}

/* ---------- the bar (node 314:55859) ---------- */
.rdk-work__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(24 * var(--u));
  min-height: calc(117 * var(--u));
  border-bottom: 1px solid rgba(0, 35, 0, 0.1);
}

.rdk-work__filter,
.rdk-work__display {
  display: flex;
  align-items: center;
  gap: calc(16 * var(--u));
}

/* Cap-trimmed, like every other small label on the site — Figma's box is 15
   tall on 13px type. */
.rdk-work__bar-label {
  font-family: var(--rdk-font-body);
  font-weight: 700;
  font-size: calc(13 * var(--u));
  line-height: calc(15 * var(--u));
  color: var(--rdk-dark);
}

.rdk-work__chips {
  display: flex;
  flex-wrap: wrap;
  gap: calc(8 * var(--u));
}

/* 36 tall in the frame; the widths (67..114) come from the label, so they are
   padding-driven rather than stated — the same reason the CTA pills are. */
/* An author `display` beats the UA's `[hidden] { display: none }`, so a
   chip carrying `hidden` renders as normal without this. Motion's does. */
.rdk-work__chip[hidden] { display: none; }

.rdk-work__chip {
  display: inline-flex;
  align-items: center;
  gap: calc(7 * var(--u));
  height: calc(36 * var(--u));
  /* ASYMMETRIC ON PURPOSE — 18 right against 14 left (client, 2026-09-01), so
     the icon+label pair sits optically centred rather than geometrically.

     Two things push the content right of where it looks centred. The icon
     carries its own inset: every one of these is a 1.333-stroke line drawing
     inside a 16 box, and the painted extent stops ~1.3 short of the box edge
     (the check circle spans 2..14, so 1.333..14.667 once the stroke is
     counted) — so the left already has ~15.3 of air where the right has 14.
     And the label is Satoshi 700, far denser than a hairline icon, so the
     pair's centre of MASS sits right of its centre of area.

     +4 on the right moves the content 2 left, which corrects both. The tile
     tags are NOT given this: they have no icon, so they are symmetric text
     and already centred. */
  padding: 0 calc(18 * var(--u)) 0 calc(14 * var(--u));
  font-family: var(--rdk-font-body);
  font-weight: 700;
  font-size: calc(13 * var(--u));
  line-height: 1;
  color: var(--rdk-dark);
  background: rgba(0, 35, 0, 0.06);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.rdk-work__chip:hover {
  background: rgba(0, 35, 0, 0.12);
}

.rdk-work__chip.is-on {
  color: var(--rdk-white);
  background: var(--rdk-dark);
}

/* The filter icons are the SERVICES icons — the same six flocked objects the
   services section uses as its cursor. Drawn as a mask so one file serves both
   the dark and the light state without a second export. */
.rdk-work__chip::before {
  content: "";
  flex: none;
  width: calc(16 * var(--u));
  height: calc(16 * var(--u));
  background: currentColor;
  mask: var(--chip-icon, none) no-repeat center / contain;
  -webkit-mask: var(--chip-icon, none) no-repeat center / contain;
}

/* ONE MAPPING FOR ALL THREE. The filter chips key off `data-filter`; the tile
   tags and the project pages' service tags both key off `data-service`, so a
   bare `[data-service="..."]` covers both without naming either — it only ever
   sets a custom property, and nothing reads `--chip-icon` unless it also draws
   the masked `::before`.

   The vocabularies are the same five services, verified against the markup.
   Pairing them here means adding a service is one line and no two places can
   end up on different icons. `all` is filter-only: there is no such thing as
   an "all" service. */
.rdk-work__chip[data-filter="all"]                                          { --chip-icon: url("../assets/img/work/icons/all.svg"); }
.rdk-work__chip[data-filter="brand"],     [data-service="brand"]     { --chip-icon: url("../assets/img/work/icons/brand.svg"); }
.rdk-work__chip[data-filter="uiux"],      [data-service="uiux"]      { --chip-icon: url("../assets/img/work/icons/uiux.svg"); }
.rdk-work__chip[data-filter="web"],       [data-service="web"]       { --chip-icon: url("../assets/img/work/icons/web.svg"); }
/* Product is node 930:144962 (client, 2026-09-18) — a panel with a left
   sidebar divider. Its source draws a 13 x 11 group with the rect's
   border INSIDE via a <mask>, which is what stack.svg already had to be
   converted out of: centre the group in the 16 box and inset the path by
   half the stroke. Verified against Figma's own 4x render — ink bbox
   x 1.50..14.50 / y 2.50..13.50 on both. */
.rdk-work__chip[data-filter="product"],   [data-service="product"]   { --chip-icon: url("../assets/img/work/icons/product.svg"); }
/* Motion's chip is `hidden` in the markup and no project carries the tag, so
   this line draws nothing today. It stays with the icon file so bringing the
   category back is one attribute — see the note on SERVICE_LABELS. */
.rdk-work__chip[data-filter="motion"],    [data-service="motion"]    { --chip-icon: url("../assets/img/work/icons/motion.svg"); }
.rdk-work__chip[data-filter="marketing"], [data-service="marketing"] { --chip-icon: url("../assets/img/work/icons/marketing.svg"); }
/* Both redrawn 2026-09-17: Stack is node 925:140051 (two stacked bars, where
   it was the old multi-media mark) and Grid became List, node 925:140098's
   IconListBullets. `grid.svg` is unreferenced from here on. */
.rdk-work__chip[data-display="stack"]    { --chip-icon: url("../assets/img/work/icons/stack.svg"); }
.rdk-work__chip[data-display="list"]     { --chip-icon: url("../assets/img/work/icons/list.svg"); }

/* ---------- intro ---------- */
  /* Heading/L (40). Was 38 -- see the display ladder in css/tokens.css. 38 and
     40 were two steps 1.05x apart, where every other rung of the ladder is
     1.10-1.22x, and the work index used BOTH on one page. Merged onto 40
     (client, 2026-09-09). Tracking is the ladder's, not the old -1.14 -- see
     the ramp in css/tokens.css.

     THE MEASURE IS 960, NOT THE 920 IT WAS (client, 2026-09-09). The ramp
     loosened Heading/L from -4% to -2.27%, which pushed this headline from 3
     lines to 4 -- its copy needs 2.85 lines at 920, i.e. it sat on the wrap
     boundary and a word landed wrong. 960 restores 3. There is 489u of unused
     room in the 1409u panel, so this costs nothing and no other element moves.
     If the copy changes, re-check the line count rather than assuming. */
.rdk-work__intro {
  max-width: calc(960 * var(--u));
  margin: calc(96 * var(--u)) 0 calc(72 * var(--u));
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(40 * var(--du));
  line-height: 1.15;
  letter-spacing: calc(-0.91 * var(--du));
  color: var(--rdk-dark);
}

/* ---------- the tiles ---------- */
.rdk-work__list {
  display: flex;
  flex-direction: column;
  gap: calc(22 * var(--u));
  margin: 0;
  padding: 0;
  list-style: none;
}

.rdk-work__item {
  /* The client's brand colour, set per item in the markup. */
  background: var(--tile-bg, var(--rdk-black-green));
  border-radius: calc(24 * var(--u));
  overflow: clip;
}

/* Node 314:55909 — 1421 x 689.544, 16 padding, 40 gap.
   THE GAP IS WHAT SIZES THE TEXT COLUMN, which is why it moved with the
   height. The node fixes the body at 366.582 and lets the artwork flex; this
   file does the reverse (artwork fixed at 982.4, body flexes), so the body's
   width is whatever the gap leaves it: 1421 - 32 padding - 982.4 - gap. At the
   old 16 that came to 390.6 and the copy wrapped differently from the design;
   at 40 it lands on 366.6, which is the node's column to a tenth. */
.rdk-work__tile {
  display: flex;
  align-items: stretch;
  gap: calc(40 * var(--u));
  height: calc(689.544 * var(--u));
  padding: calc(16 * var(--u));
  color: var(--rdk-white);
  text-decoration: none;
}

/* ---------- hover ----------
   The artwork SHRINKS BACK slowly, revealing more of itself (client,
   2026-08-31). It rests slightly enlarged, so `cover` is cropping harder than
   it needs to; easing it down to 1 uncrops it rather than moving anything.
   That is why the resting state is the scaled one and hover is 1 — the other
   way round would need the image to grow past its box and there would be
   nothing extra to reveal.

   Slow on purpose: 1.1s, so it reads as the picture opening up rather than as
   a zoom. `scale` is a standalone property on `.rdk-work__shot`, an element
   GSAP never touches — the entrance and the card animate the `<li>`, so the
   inline `translate: none` GSAP writes there cannot reach this. */
.rdk-work__shot {
  scale: 1.09;
  /* 2.4s, not 1.1 (client, 2026-08-31: slower). Long enough that it reads as
     the picture settling rather than as a hover state changing — at this
     length the reader sees it move without ever catching it start or stop. */
  transition: scale 2.4s cubic-bezier(0.22, 1, 0.36, 1);
}

@media (hover: hover) and (pointer: fine) {
  .rdk-work__item:hover .rdk-work__shot { scale: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .rdk-work__shot { transition: none; }
}

/* ---------- the "View project" cursor ----------
   The case studies' drag pill, relabelled: same glass plate, same geometry
   (node 114:15246), following the pointer while it is over a tile. It is the
   affordance the tile otherwise lacks — the whole card is a link, but nothing
   says so.

   POINT + PILL, exactly as the drag pill is built: `__cursor` is zero-size and
   GSAP moves it; `__cursor-pill` centres itself on it with a standalone
   `translate`. That keeps `transform` entirely GSAP's, and means the pill can
   be resized here without anyone recomputing a hard-coded half-width. */
.rdk-work__cursor {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 40;
  width: 0;
  height: 0;
  pointer-events: none;
  opacity: 0;
}

.rdk-work__cursor-pill {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  /* 172.05 x 56 in the node: 32 inline padding, 12 gap, and a FIXED 56 height.
     The 32 is a real change from the 18.551 this carried before.

     THE HEIGHT HAS TO BE STATED, not left to the padding. The node's 12.174
     block padding does not add up to 56 from any text box — Figma's text layer
     is 11 tall (cap height, `text-box-trim: trim-both`), so 11 + 24.35 = 35.4.
     The frame simply has a fixed 56 with its contents centred. Left to padding
     this rendered 39.3 — a third short — which is what it had always been, and
     only showed up once there was a node to measure against. */
  gap: calc(12 * var(--u));
  height: calc(56 * var(--u));
  padding: 0 calc(32 * var(--u));
  justify-content: center;
  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-black-green);
  /* A SOLID WHITE PILL NOW (Figma 362:65117, client 2026-09-01) — it replaces
     the drag pill's 30% tint over a 30 blur. The old plate deliberately picked
     up the photography behind it; this one deliberately does not, so the label
     reads the same over every project's brand colour.

     The 15.248 backdrop blur is kept from the node even though the fill is
     opaque: it softens what shows through the shadow's edge, and it is what the
     design specifies.

     Five shadow layers, verbatim from the node. The top two are all but
     invisible on their own (0 and 0.01 alpha at 118 and 76 down) and exist to
     stretch the falloff — dropping them for "the three that show" collapses
     the gradient into a hard edge. Expressed as `box-shadow`, not
     `filter: drop-shadow`: the pill is a solid rounded rectangle so the two are
     equivalent here, and box-shadow neither creates a containing block nor
     fights the backdrop-filter on the same element. */
  background: var(--rdk-white);
  -webkit-backdrop-filter: blur(calc(15.248 * var(--u)));
  backdrop-filter: blur(calc(15.248 * var(--u)));
  border-radius: calc(30.145 * var(--u));
  box-shadow:
    0 calc(118 * var(--u)) calc(16.5 * var(--u)) rgba(0, 0, 0, 0),
    0 calc(76 * var(--u))  calc(15 * var(--u))   rgba(0, 0, 0, 0.01),
    0 calc(43 * var(--u))  calc(13 * var(--u))   rgba(0, 0, 0, 0.05),
    0 calc(19 * var(--u))  calc(9.5 * var(--u))  rgba(0, 0, 0, 0.09),
    0 calc(5 * var(--u))   calc(5 * var(--u))    rgba(0, 0, 0, 0.1);
}

.rdk-work__cursor-pill svg {
  flex: none;
  width: calc(5.878 * var(--u));
  height: calc(8.928 * var(--u));
}

/* `cursor: none` only once JS has confirmed a fine pointer AND put a pill
   there — in the stylesheet unconditionally it would take a touch or no-JS
   user's cursor away and give nothing back. */
.rdk-work--cursor .rdk-work__tile { cursor: none; }

@media (prefers-reduced-motion: reduce) {
  .rdk-work__item,
  .rdk-work__shot { transition: none; }
}

/* Five of the seven put the text first; the other two are this modifier.
   One component either way. */
.rdk-work__item--image-first .rdk-work__tile {
  flex-direction: row-reverse;
}

.rdk-work__art {
  position: relative;
  flex: none;
  width: calc(982.4 * var(--u));
  margin: 0;
  border-radius: calc(16 * var(--u));
  overflow: clip;
}

/* ONE flattened export per project (client, 2026-08-31), and the SAME file is
   the project page's hero — the client chose one master, cropped per placement,
   over two exports.

   The master is the HERO's aspect (1600 x 801, i.e. 2:1) because it is the
   wider of the two; this tile is 982.4 x 593.5 (1.655), so `cover` trims about
   17% off the sides. Cropping down from the larger canvas costs nothing, where
   exporting at the tile's aspect and stretching it to a full-bleed hero would.

   It also makes the tile -> hero transition as smooth as it can be: both ends
   are crops of ONE image, so the morph reframes a single picture rather than
   cross-fading two. Note the two aspects differ, so the image DOES reframe
   during the morph — it reads as zooming out to full bleed. If the client ever
   wants identical framing at both ends, that is a Figma change (make the two
   boxes the same aspect), not a code one. */
.rdk-work__shot {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}

/* The logo rides ON the image as its own SVG, so it stays crisp and can be
   swapped without re-exporting the photograph. */
/* EACH LOGO IS DRAWN AT ITS DESIGNED WIDTH, not one shared size. `--logo-w`
   is the width Figma gives it inside a tile's 982.418 artwork; the project
   hero multiplies it by 1600/982.418 so the same mark keeps its designed
   proportion in the bigger frame.

   The numbers come from each SVG's intrinsic size, which IS the size of the
   node it was exported from — four came from index tiles and are used as-is,
   Everpure and BlueDolphin came from hero nodes and are divided by that same
   ratio. Measuring them off the renders does NOT work: the artwork carries
   white grid lines and dots and the tiles carry white body text, so a
   "find the white ink" pass returns the whole frame. Tried twice. */
.rdk-work__logo {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  width: calc(var(--logo-w, 280) * var(--u));
  max-width: 62%;
  height: auto;
}

/* THE PER-PROJECT WIDTHS USED TO LIVE HERE as `[data-project=…]` rules and
   are gone (2026-09-15). They were a second copy of a number `PROJECTS` in
   tools/projects.py already owns, and the copy listed only the original six —
   so Exabeam, Metro, Radiant, Secureworks and The Container Store silently
   took the 280 default and then the `max-width: 62%` clamp above. Radiant is
   200 in the data and drew 297 on the tile. The generated tile now carries
   `--logo-w` inline, as the project hero and the related-projects card always
   have, so there is one source and a new project needs no rule here. */
[data-project="datascan"] { --logo-w: 280; }

/* 24 ALL ROUND (node 314:55981). It was 40/24/24 — the 40 was doing the job
   of the node's inner group, which carries its own 40 top pad INSIDE the 24.
   That 40 now lives on the name below, so the name sits 64 from the top of the
   column where it used to sit 40, and the tags keep their designed 24 off the
   bottom. */
.rdk-work__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: calc(24 * var(--u));
}

/* `40 0 24`: the 40 is the node's head-group top padding (314:55982, `pt-40`)
   expressed on the first child, and the 24 is its `gap` down to the
   description — 16 before. */
.rdk-work__name {
  /* THE ONE DISPLAY RULE HELD OFF `--du`, and the reason is the column.
     The work tile's copy column is whatever is left after a FIXED
     `982.4 * --u` artwork, which at 820 is **174px** — and floored to
     27.2 the longest single-word name, "Secureworks", is 157 wide in a
     159 box and overflows by 7 with nothing to break on. Forcing a break
     mid-brand is worse than leaving the name scaled.
     The real answer is that this tile wants its own tablet layout — a
     174px column is cramped at any type size — which is a separate piece
     of work. Raised, not silently bodged. */
  margin: calc(40 * var(--u)) 0 calc(24 * var(--u));
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(40 * var(--u));
  line-height: 1;
  letter-spacing: calc(-0.91 * var(--u));
}

/* Node 314:55984 — Satoshi Medium 16 / 1.4 / +0.16 tracking / 90% opacity.
   Was 17/1.35 with no tracking; the client asked for the 16 (2026-09-01) and
   the other two come with it, since they are the same text style in the node
   and moving the size alone would leave it half-matched. */
.rdk-work__desc {
  margin: 0;
  font-family: var(--rdk-font-body);
  font-weight: var(--rdk-body-weight);
  font-size: var(--rdk-body-s);
  line-height: var(--rdk-body-lh);
  letter-spacing: var(--rdk-body-track);
  opacity: 0.9;
}

/* Pinned to the bottom of the column, as the frame has them. */
.rdk-work__tags {
  display: flex;
  flex-wrap: wrap;
  gap: calc(8 * var(--u));
  margin: auto 0 0;
  padding: 0;
  list-style: none;
}

/* THE TAGS CARRY THE SAME ICONS AS THE FILTER BAR (client, 2026-09-01), so a
   service reads identically whether you are filtering by it or seeing it on a
   project. Only the colour differs: the chips sit on the page ground, these sit
   on white over the tile's brand colour.

   Same mechanism as the chips — a `::before` masked by `--chip-icon`, painted
   in `currentColor` — so one icon file serves both and neither can drift. */
.rdk-work__tags li {
  display: inline-flex;
  align-items: center;
  gap: calc(7 * var(--u));
  height: calc(36 * var(--u));
  /* 18 right against 14 left, the same optical correction the chips carry and
     for the same two reasons: the icon has ~1.3 of built-in inset inside its 16
     box, and the label is far denser than a hairline drawing, so the pair's
     centre of mass sits right of its centre of area. These pills did NOT have
     this before — they had no icon, so symmetric padding was correct. */
  padding: 0 calc(18 * var(--u)) 0 calc(14 * var(--u));
  font-family: var(--rdk-font-body);
  font-weight: 700;
  font-size: calc(13 * var(--u));
  line-height: 1;
  color: var(--rdk-dark);
  background: var(--rdk-white);
  border-radius: 999px;
}

.rdk-work__tags li::before {
  content: "";
  flex: none;
  width: calc(16 * var(--u));
  height: calc(16 * var(--u));
  background: currentColor;
  mask: var(--chip-icon, none) no-repeat center / contain;
  -webkit-mask: var(--chip-icon, none) no-repeat center / contain;
}

/* ---------- filtering ----------
   Hidden by attribute rather than by removing the node, so the browser keeps
   the layout position and the View Transition below has something to match. */
.rdk-work__item[hidden] { display: none; }

/* ---------- LIST display (node 900:117544, 2026-09-17) ----------
   Grid is gone; List replaces it. The client's frame draws Display with two
   chips and no third, so this is a REPLACEMENT rather than an addition —
   `.rdk-work__list--grid` and its two-column layout came out with it.

   IT IS THE SAME COMPONENT, RE-LAID. The row needs the name and the service
   tags, both of which the tile already carries, so this is a modifier on
   `.rdk-work__list` exactly as the grid was — no second markup, and
   `tools/projects.py` writes one tile that serves both displays.

   The class is `--rows` where the chip says `list`: the chip is the reader's
   word for it, the class describes what it draws, and `--list` on
   `.rdk-work__list` would read as a tautology.

   Row: 1420 wide at a 2 gap (the node's 110 pitch less its 108 row), radius
   24, 32 of side padding, contents centred. The height is STATED rather than
   left to the padding — 32 + the 36-tall content + 32 is 100, and the node
   draws 108, so the padding alone does not produce it.

   `--row-h` IS 150, NOT THE NODE'S 108 (client, 2026-09-17: taller, then
   their own tuning pass to 150 on 2026-09-18). It is a
   property rather than a literal because it is what js/trail-tuner.js drives —
   the row and the trail are one thing to look at, so they tune together. */
.rdk-work__list--rows {
  --row-h: 150;
  gap: calc(2 * var(--u));
  counter-reset: rdk-row;
}

/* The numbers are a COUNTER, not a field on the project. That is what keeps
   them contiguous under a filter: a hidden item is `display: none`, which does
   not increment, so filtering to Motion renumbers 01.. rather than leaving
   gaps where the other projects were. */
.rdk-work__list--rows .rdk-work__item {
  counter-increment: rdk-row;
  background: var(--rdk-black-green);
  /* The brand colour arrives on hover instead — see below. `--tile-bg` still
     carries it, unchanged, for the stack display. */
  transition: background-color 0.35s ease;
}

.rdk-work__list--rows .rdk-work__tile {
  align-items: center;
  gap: 0;
  height: calc(var(--row-h) * var(--u));
  padding: 0 calc(32 * var(--u));
}

/* Everything the row does not draw. The artwork is the heavy one — a hidden
   `<img loading="lazy">` is never fetched, so switching to List costs nothing
   in requests. */
.rdk-work__list--rows .rdk-work__art,
.rdk-work__list--rows .rdk-work__desc,
.rdk-work__list--rows .rdk-work__go { display: none; }

/* `width: 100%` rather than `flex: 1`, because the tile's only other child is
   now display:none and the body is the row. */
.rdk-work__list--rows .rdk-work__body {
  display: flex;
  /* The stack's body is a COLUMN — restate it, or the row lays out vertically
     and everything below (the baseline, the auto margin) is measuring the
     wrong axis. */
  flex-direction: row;
  /* CENTRE, WITH THE NUMBER BOTTOM-ALIGNED — which is the node's own
     construction, not an approximation of it. Its name is cap-trimmed
     (`text-box-edge: cap alphabetic`), so the name's box IS its cap box and
     `items-end` bottom-aligns the number's box with it; our name's box is the
     cap box too (line-height 0.711, below), so bottom-aligning here
     reproduces the same relationship.

     CSS `align-items: baseline` was the obvious reading and is 2.6 out. It
     aligns TEXT baselines, so the number's descender depth joins the group's
     height — measured, the body came out 41.3 against the name's 36.3 and
     that extra 5 pushed the whole row's ink 2.6 up off the design. Centring
     on the name alone keeps the body exactly the name's box. */
  align-items: center;
  gap: calc(32 * var(--u));
  width: 100%;
  padding: 0;
}

/* THE NUMERAL IS THE BRAND FACE NOW (node 919:49150, re-read 2026-09-18) —
   Redonk Avant Garde Bold 20 / 0.94 / -1 at 20%, where it was a Satoshi Bold
   14 label at 50% with 10% tracking and uppercase. It also stopped being
   bottom-aligned: the node's left group went `items-end` -> `items-center`, so
   the `align-self` that put it on the name's baseline is gone with it.

   The fixed 22 width went too — the node lets it size to its own ink now (23
   for "01"), and every value the counter can produce is two digits, so the
   name's left edge is steady regardless. */
.rdk-work__list--rows .rdk-work__body::before {
  content: counter(rdk-row, decimal-leading-zero);
  flex: none;
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(20 * var(--du));
  line-height: 0.94;
  letter-spacing: calc(-1 * var(--du));
  opacity: 0.2;
}

/* Display/S 51. Cap-trimmed the way every trimmed run on this site is — the
   face's cap is 0.711em, so a line-height of 0.711 makes the box the cap box
   and the baseline alignment above lands where the node draws it.

   TRACKING IS THE LADDER'S -2.69%, NOT THE NODE'S -2.04px (-4%). At -4% the
   51 rung would be tracked tighter than the 62 above it, which is exactly the
   inversion the 2026-09-09 pass took out of the ladder; -1.37 is what the
   case-study client name at the same size already uses. Flagged, not applied. */
.rdk-work__list--rows .rdk-work__name {
  margin: 0;
  font-size: calc(51 * var(--du));
  line-height: 0.711;
  letter-spacing: calc(-1.37 * var(--du));
  white-space: nowrap;
}

.rdk-work__list--rows .rdk-work__tags {
  align-self: center;
  flex-wrap: nowrap;
  gap: calc(4 * var(--u));
  margin: 0 0 0 auto;
}

/* ---------- the row hover (node 908:23832) ----------
   THE ONLY THING THAT CHANGES IS THE GROUND. Compared against the resting row
   node by node, the hover state differs in `background` alone — the ink, the
   tag pills, the radius and the padding are all identical — so this is one
   declaration and not a state. `--tile-bg` is already on every item for the
   stack display, so no new data is needed either. */
@media (hover: hover) and (pointer: fine) {
  .rdk-work__list--rows .rdk-work__item:hover,
  .rdk-work__list--rows .rdk-work__item:focus-within {
    background: var(--tile-bg, var(--rdk-black-green));
  }
}

/* TOUCH KEEPS THE RESTING ROW, DARK — it does NOT get the colour for free.
   Painting all eight in their brand colours was the first pass and is a
   different design from the one that was drawn: node 900:117544 draws the list
   dark and the colour belongs to node 908:23832, which is a HOVER state. This
   is the call the case studies already made when their
   `@media (hover: none) { --open: 1 }` came out — on touch, show what the
   frame shows at rest.

   WHAT TOUCH GETS INSTEAD IS THE TAP (client, 2026-09-18: "when the user
   clicks an item, change to its hover color and then switch to the project
   page"). Same one declaration, reached by a class rather than by `:hover`,
   so a finger gets the state a pointer gets — just at the moment it commits
   rather than while it hovers.

   NOT GATED ON `hover: none` HERE. The class is only ever added by
   js/work.js, which IS gated, so a media query would only be a second place
   to keep that decision. Quicker than the hover's 0.35 because it has to
   land before the page goes: `tapColour()` holds the navigation to match. */
.rdk-work__list--rows .rdk-work__item.is-tapped {
  background: var(--tile-bg, var(--rdk-black-green));
  transition-duration: 0.18s;
}

@media (prefers-reduced-motion: reduce) {
  .rdk-work__list--rows .rdk-work__item { transition: none; }
}

/* ---------- the cursor image trail ----------
   Images from the project's own portfolio page, dropped along the pointer's
   path through its row (client, 2026-09-17, after originkit's cursor image
   trail). `js/work.js` owns the motion; this is the box it moves.

   A FIXED LAYER, not a child of the row. The image is 334 x 246 against a
   108-tall row and the mockup hangs it well above and below — parented to the
   row it would be cut by the row's own `overflow: clip`, which is what draws
   the 24 corners. Fixed also means viewport coordinates, so nothing has to be
   re-derived when the page scrolls under a held pointer.

   z-index 40 is the "View project" pill's, which never runs at the same time:
   the pill belongs to the stack display and the trail to List.

   `--trail-w` IS 156, NOT THE MOCKUP'S 333.671 (client: smaller, then 156 in
   their own 2026-09-18 pass — under half what the mockup draws).
   ONE number drives it: the height comes from `--trail-ar`, the mockup's own
   245.776/333.671, so a size change cannot squash the crop. js/work.js reads
   `--trail-w` back off this element to derive the sampling step, so the
   spacing follows the size without a second control to keep in step. */
.rdk-work__trail {
  --trail-w: 156;
  --trail-ar: 0.7366;
  --trail-r: 16;
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  overflow: clip;
}

/* Centred on the pointer with a standalone `translate`, so `transform` stays
   entirely GSAP's — the same split the cursor pill and the drag pill are built
   on. */
.rdk-work__trail img {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(var(--trail-w) * var(--u));
  height: calc(var(--trail-w) * var(--trail-ar) * var(--u));
  translate: -50% -50%;
  object-fit: cover;
  border-radius: calc(var(--trail-r) * var(--u));
  box-shadow: 0 calc(18 * var(--u)) calc(48 * var(--u)) rgba(0, 20, 7, 0.35);
  opacity: 0;
  will-change: transform, opacity;
}

/* ---------- the page transition ----------
   MOVED OUT. It is global now (client, 2026-09-01: "I'd want this as a global
   page transition as well. Not just for the projects."), so the keyframes and
   the `root` rules live in css/page-transition.css and the opt-in is inline in
   every page's <head>. Nothing about the transition is work-specific any more.

   What is left below is the SUPERSEDED tile -> hero morph, kept inert. */

/* ---------- THE TILE -> HERO MORPH, CURRENTLY INERT ----------
   The previous transition: the tile's artwork lifted off the card, grew, and
   settled as the project hero, with the client's logo travelling alongside it.

   IT IS OFF, NOT DELETED. Every rule below is scoped to a
   `view-transition-name` that nothing carries any more — the names were
   removed from the inline <style> in tools/projects.py and work/index.html,
   and from `claim()` in js/work.js. With no element holding a name, no such
   group is ever created and none of this matches.

   TO BRING IT BACK: put the four `view-transition-name` declarations back in
   those two inline blocks, restore the body of `claim()`, and swap the two
   `root` rules above for a fade pair. Nothing else has to change.

   Keeping it costs nothing and it is a real fallback — the notes in CLAUDE.md
   for it (the paint-order trap especially) were expensive to work out. */
::view-transition-group(work-shot)     { z-index: 1; }
::view-transition-group(work-logo)     { z-index: 2; }
::view-transition-group(project-intro) { z-index: 3; }
::view-transition-group(rdk-nav)       { z-index: 4; }

::view-transition-group(work-shot),
::view-transition-group(work-logo) {
  animation-duration: 1s;
  animation-timing-function: cubic-bezier(0.83, 0, 0.17, 1);
}

::view-transition-old(work-shot),
::view-transition-new(work-shot) {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

::view-transition-old(work-logo),
::view-transition-new(work-logo) {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

::view-transition-image-pair(work-shot),
::view-transition-image-pair(work-logo),
::view-transition-image-pair(rdk-nav) { isolation: auto; }

::view-transition-old(work-shot),
::view-transition-old(work-logo),
::view-transition-old(rdk-nav) { animation: none; opacity: 0; }

::view-transition-new(work-shot),
::view-transition-new(work-logo),
::view-transition-new(rdk-nav) { animation: none; opacity: 1; }

/* ---------- the footer's ground on this page ----------
   `.rdk-footer` is deliberately transparent so the CTA band's 96u rounded
   shoulders reveal whatever is behind them, and its 144u top margin is a
   band of that same ground. On the homepage that ground is `--rdk-black-green`
   and the section above the footer is dark too, so it reads as continuous.

   THE WORK INDEX ENDS IN A WHITE PANEL, so the dark ground became a hard black
   stripe between the panel and the green CTA (client, 2026-08-31). Painting
   the footer white puts the same colour behind the shoulders as the panel
   above them, and the join disappears.

   Gated on a body class, NOT on this stylesheet — css/work.css is loaded by
   the project pages too, and those DO end in dark blocks, so they must keep
   the dark ground. */
body.rdk-footer-on-light .rdk-footer {
  background: var(--rdk-white);
  /* THE 144 HAS TO BECOME PADDING, NOT STAY A MARGIN. A margin is outside the
     box, so the footer's own background cannot paint it and the body's dark
     ground still showed through — which was the stripe, not the shoulders.
     As padding it sits inside the box, takes the white, and keeps the exact
     same 144u of breathing room above the CTA band. */
  margin-top: 0;
  /* Zero, not 144: the panel above now carries the whole gap. */
  padding-top: 0;
}

/* ---------- two elements that exist only for the phone ----------
   Both live in the markup at every width and are hidden here, because the
   alternative is duplicating six tiles' worth of markup or building them in
   JS on a resize listener. The mobile block below turns them back on.

   `.rdk-work__go` is the card's "View project" pill: desktop has no button
   there — the cursor pill is the affordance — so it would be a second, static
   copy of a control that already follows the pointer.

   `.rdk-work__toggle` is the filter/display disclosure: above 767 the chips
   are always visible, so a control that opens them has nothing to do. */
.rdk-work__go,
.rdk-work__toggle {
  display: none;
}

/* ============================================================
   MOBILE — node 367:65763 "work — Mobile 391"
   ============================================================
   The work index had NO mobile block at all before this; it was the desktop
   layout squeezed. Straight px throughout, not `--u`: below 768 the design
   unit collapses (see the note at the top of this file), so 16u at 390 is 3.9px
   and every number here would be a quarter of what the design asks for. The
   phone frame has its own numbers and gets them directly. */
@media (max-width: 767px) {

  /* ---------- hero ---------- */
  .rdk-work-hero {
    height: 289px;
  }

  /* `top: auto` IS THE FIX, not the nowrap. The desktop rule sets `top` in
     `--u`; adding `bottom` here left BOTH set, and an absolutely positioned
     element with both is stretched between them — the box came out 204 tall
     (289 - 54.5 - 30) and the one line of text sat at the top of it, which read
     as a headline floating in the middle of the hero. Releasing `top` lets the
     box be its own height and `bottom` do the positioning.

     `nowrap` is still needed: the node sets it, and at 68px the headline
     otherwise breaks across three lines. The hero is `overflow: clip`, so a
     phone narrower than the 390 frame crops the full stop rather than
     reflowing — which is what the design does. */
  .rdk-work-hero__title {
    top: auto;
    left: 20px;
    bottom: 30px;
    font-size: 68px;
    line-height: 0.94;
    letter-spacing: -2.72px;
    white-space: nowrap;
  }

  /* ---------- the white panel ---------- */
  .rdk-work {
    /* The desktop strip behind the panel's shoulders is 120u; at this size the
       panel meets the hero square, so it is only the corner radius that shows
       green. */
    --panel-radius: 32px;
  }

  .rdk-work::before {
    height: 60px;
  }

  .rdk-work__panel {
    padding: 0 0 64px;
    border-radius: 32px 32px 0 0;
  }


  /* ---------- intro ---------- */
  .rdk-work__intro {
    /* THE DESKTOP `max-width` HAS TO BE CLEARED, and this is the collapsing
       design unit again: it is set in `--u`, which at 375 resolves to 0.234, so
       a 960u measure comes out as 225px — the paragraph would be two words wide. */
    max-width: none;
    padding: 40px 24px;
    font-size: 27px;
    line-height: 1.1;
    letter-spacing: -0.675px;
  }

  /* ---------- the list ---------- */
  .rdk-work__list {
    gap: 16px;
    padding: 0 16px;
  }

  .rdk-work__tile {
    flex-direction: column;
    align-items: stretch;
    gap: 20px;
    height: auto;
    padding: 16px 16px 24px;
  }

  .rdk-work__item {
    border-radius: 24px;
  }

  .rdk-work__art {
    width: 100%;
    /* 326 x 218.347 in the node — the card's inner width at 390. Expressed as a
       ratio so it holds at every phone width rather than only at 390. */
    aspect-ratio: 326 / 218.347;
    border-radius: 10.619px;
  }

  .rdk-work__logo {
    width: 144.824px;
  }

  /* THE ORDER CHANGES, and `order` is the whole reason this needs no second
     markup. Desktop reads name -> description -> tags beside the artwork;
     the phone reads artwork -> TAGS -> name -> description -> button. */
  .rdk-work__body {
    display: flex;
    flex-direction: column;
    gap: 40px;
    padding: 0 8px;
  }

  .rdk-work__tags { order: 1; gap: 4px; }
  .rdk-work__name { order: 2; margin: 0; font-size: 35px; line-height: 1;
                    letter-spacing: -1.05px; }
  .rdk-work__desc { order: 3; margin: -28px 0 0;
 opacity: 0.9; }
  .rdk-work__go   { order: 4; }

  /* The name and description are one 12-gap pair inside the 40-gap stack
     (node 367:65872). A negative margin is the cheap way to get a second gap
     out of one flex container without wrapping them in another element:
     40 - 28 = 12. */

  .rdk-work__go {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 56px;
    padding: 0 20px;
    font-family: var(--rdk-font-brand);
    font-weight: 700;
    font-size: 15.272px;
    line-height: 1;
    color: var(--rdk-white);
    border: 2px solid var(--rdk-white);
    border-radius: 999px;
  }

  /* Tag pills keep the desktop chip's own numbers here — the node gives them
     the same 16/10, 16 icon, 13px it gives the desktop tile. */
  .rdk-work__tags li {
    gap: 4px;
    height: auto;
    padding: 10px 16px;
    font-size: 13px;
    letter-spacing: -0.13px;
    border-radius: 8px;
  }

  .rdk-work__tags li::before { width: 16px; height: 16px; }

  /* ---------- the LIST display on a phone — node 905:48765 ----------
     It WAS invented (900:117544 is desktop only); the client supplied this
     frame on 2026-09-18 and it is close to what was guessed, so this is a
     re-spec rather than a rebuild. Straight px, like every other number in
     this block.

     Card 358 x 184 at a 8 gap in a 390 frame, so the list's own 16 of side
     padding is already right. Radius 24 comes from `.rdk-work__item` above. */
  .rdk-work__list--rows { gap: 8px; }

  /* CONTENT HEIGHT, NOT 184. The frame draws 184 for a row whose pills fit on
     one line and 224 for Secureworks, whose four wrap to two — and the gap
     between the name and the pills is 56 in BOTH. So the height is a
     CONSEQUENCE of a fixed gap, not a fixed box: 32 + 28 + 56 + 36 + 32 = 184
     and 32 + 28 + 56 + 76 + 32 = 224, exactly.

     Built first as `min-height: 184` with `align-content: space-between`,
     reading the node's `justify-between` literally. That gives 184 for both,
     because a wrapped pill row still fits inside the minimum and the gap just
     squeezes — measured 184 where Secureworks wants 224. The gap is the
     designed value; the height follows it. */
  .rdk-work__list--rows .rdk-work__tile {
    flex-direction: column;
    align-items: stretch;
    height: auto;
    padding: 32px 24px;
  }

  /* One wrapping row: number and name on the first line, the tags forced onto
     a second. 56 between those lines, 8 between the number and the name. */
  .rdk-work__list--rows .rdk-work__body {
    flex-wrap: wrap;
    align-items: center;
    gap: 56px 8px;
  }

  /* THE NUMERAL DOES NOT FOLLOW THE DESKTOP HERE. 919:49150 moved its own to
     the brand face at 20/0.94/-1 at 20%; this frame keeps the Satoshi Bold 14
     label at 50% with 10% tracking and uppercase — which is what the desktop
     used to be. Deliberate in the file, so they are set separately. */
  .rdk-work__list--rows .rdk-work__body::before {
    width: 22px;
    font-family: var(--rdk-font-body);
    font-weight: 700;
    font-size: 14px;
    line-height: 1.2;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    opacity: 0.5;
  }

  /* 28 / 1 / -0.84 (-3%), and `nowrap` is kept: the frame's longest name,
     "The Container Store", is 249 in a 310 content measure, so nothing needs
     to break.

     `order` HAS TO BE RESET. The STACK's phone block reorders the card to
     artwork -> tags -> name -> description (node 367:65783) with `order` on
     three of the four, and those numbers are still in force in the row — which
     put the tags above the name and the number on a line of its own. A row
     reads number, name, tags, so it wants DOM order back. */
  .rdk-work__list--rows .rdk-work__name {
    order: 0;
    font-size: 28px;
    line-height: 1;
    letter-spacing: -0.84px;
    white-space: nowrap;
  }

  .rdk-work__list--rows .rdk-work__tags {
    order: 0;
    flex-wrap: wrap;
    gap: 4px;
    margin: 0;
    /* The tags go to their OWN line: the body wraps, and a full-width item
       after two auto-width ones is what forces the break. */
    flex: 1 0 100%;
  }

  /* THE SAME SIZE PILL AS EVERYWHERE ELSE (client, 2026-09-18), which is what
     the frame draws too — 36 tall, 16/10, a 16 icon and Satoshi Bold 13, i.e.
     the desktop tile tag's own numbers. The phone block above already sets all
     of that for the STACK card; the one thing it does differently is a 8
     radius, and the frame gives these the full 32 (round at this height). Only
     the radius is restated, and only for the rows. */
  .rdk-work__list--rows .rdk-work__tags li { border-radius: 999px; }

  /* No cursor pill on a touch pointer — js/work.js already gates on
     `(hover: hover) and (pointer: fine)`, and the card carries a real button
     here instead. */
}

/* ---------- THE FILTER BAR IS SHARED BY THE PHONE AND TABLET PORTRAIT -----
   (client, 2026-09-17: "filter tags are way too big now. Is there a way to
   make them larger but fit on one line? Can also use the mobile pattern here
   if that's not possible.")

   IT IS NOT POSSIBLE AT A SIZE WORTH HAVING, and that is measured rather than
   asserted: at 820 the six filter chips only stay on one row at 30px tall or
   less — at 32 the group wraps. 30 is under the 44 iOS asks and the 48 Android
   does, on a device operated by a thumb, and this file already refuses that
   trade once: the phone's chips are 48 "because 36 is under every platform
   minimum". Taking it here would contradict the rule two breakpoints apart.

   So tablet portrait takes the client's own second option — the phone's
   disclosures. Each group collapses behind a 64-tall pill and the chips open
   underneath at 48, which is one line for the bar AND a proper target.

   SHARED RATHER THAN COPIED, the call `.rdk-pill` and the About wall already
   made. These rules were inside the phone block; they are the same rules, and
   two copies of a disclosure is two things to keep in step.

   IT NEEDED NO JAVASCRIPT. `disclosures()` in js/work.js has no width check
   at all — the pills are in the markup at every width and CSS is what hides
   them above 767 — so widening the query is the whole change. That is worth
   knowing before anyone goes looking for a breakpoint in the JS. */
@media (max-width: 767px),
       (min-width: 768px) and (orientation: portrait) {
  /* ---------- filter bar ---------- */
  .rdk-work__bar {
    display: flex;
    gap: 12px;
    align-items: stretch;
    min-height: 0;
    padding: 24px 16px;
    border-bottom: 0;
  }

  .rdk-work__filter,
  .rdk-work__display {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    display: block;
  }

  /* "Filter" / "Display" are the desktop labels; the pill carries the wording
     on this breakpoint ("Filter by type" / "Display by"), so the label would
     be the same word twice. */
  .rdk-work__bar-label {
    display: none;
  }

  .rdk-work__toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 64px;
    padding: 0 20px;
    font-family: var(--rdk-font-brand);
    font-weight: 700;
    font-size: 15px;
    line-height: 1.2;
    color: var(--rdk-black-green);
    background: var(--rdk-off-white);
    border: 0;
    border-radius: 999px;
    cursor: pointer;
  }

  /* px-40 in the node, but "Filter by type" at 15px does not fit inside 40+40
     at 390 across two columns — it wraps or overflows. 20 keeps the label on
     one line, which is what the design shows. */

  /* The panel is the SAME chip group the desktop uses, so filtering runs
     through one code path and only its visibility changes. */
  .rdk-work__chips {
    position: absolute;
    z-index: 5;
    top: calc(100% + 8px);
    left: 0;
    right: 0;
    display: none;
    flex-wrap: wrap;
    gap: 8px;
    padding: 16px;
    background: var(--rdk-white);
    border-radius: 20px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.06), 0 16px 28px rgba(0, 0, 0, 0.14);
  }

  /* THE PANEL SPANS THE BAR, NOT ITS OWN COLUMN. Each trigger is half the
     width, and six filter chips inside 166px wrap into a tall thin stack. A
     dropdown is allowed to be wider than the control that opened it. */
  .rdk-work__filter .rdk-work__chips {
    right: auto;
    width: calc(100vw - 32px);
  }

  .rdk-work__display .rdk-work__chips {
    left: auto;
    right: 0;
    width: auto;
  }

  .rdk-work__filter.is-open .rdk-work__chips,
  .rdk-work__display.is-open .rdk-work__chips {
    display: flex;
  }

  /* THE CHIPS THEMSELVES NEED RE-SIZING, and this is the collapsing design
     unit at its worst: left on `--u` they measured 8.4px tall with 3px type —
     legible to nobody, and easy to miss because the panel is closed by default
     and looks fine until you open it. Same numbers as the tile tags, since
     they are the same component. */
  /* 48, NOT THE DESIGN'S 36. A chip in a dropdown is a touch target, and 36
     is under every platform minimum (44 iOS, 48 Android). The design draws the
     desktop chip; at the size a thumb actually has to hit it, the height is the
     accessibility floor rather than the drawing. Padding grows with it so the
     pill still looks like a pill and not a stretched one. */
  .rdk-work__chip {
    gap: 8px;
    height: 48px;
    padding: 0 18px;
    font-size: 14px;
    letter-spacing: -0.14px;
    border-radius: 999px;
  }

  .rdk-work__chip::before { width: 18px; height: 18px; }

  /* The trigger needs to say whether it is open. The node draws no chevron —
     it only ever shows the closed state — but a disclosure with no affordance
     is a pill that looks inert until you happen to tap it. Masked so it takes
     `currentColor`, and it turns over when the panel opens. */
  .rdk-work__toggle::after {
    content: "";
    flex: none;
    width: 12px;
    height: 12px;
    margin-left: 10px;
    background: currentColor;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain;
    transition: rotate 0.24s cubic-bezier(0.33, 1, 0.68, 1);
  }

  .rdk-work__toggle[aria-expanded="true"]::after { rotate: 180deg; }

  .rdk-work__toggle[aria-expanded="true"] {
    background: var(--rdk-dark);
    color: var(--rdk-white);
  }

  @media (prefers-reduced-motion: reduce) {
    .rdk-work__toggle::after { transition: none; }
  }
}

/* ============================================================
   TABLET PORTRAIT (client, 2026-09-17) — three things, and all three are the
   collapsing unit in a different costume.

   1. THE FILTER CHIPS were floored here first and that is SUPERSEDED — see
      the shared filter-bar block above, which gives tablet portrait the
      phone's disclosures instead. Floored in place they were legible but
      wrapped to two rows, and the largest that stays on one row is 30px,
      which is under every platform's touch minimum.

   2. THE INTRO ("can take more of the available real-estate"). Measured on a
      real tablet it was FIVE lines, not the design's three: the type is
      floored to 27.2 by `--du` while the measure stayed `960 * --u` = 492, so
      a line went from the design's 24em to 18.1em. Stated in the type's own
      units it is 653 here and back to three — the same fix, and the same
      reasoning, as the case-study description's `418em / 20`. It is a NO-OP
      wherever the type is still `--u`-scaled, so it cannot reach a desktop.

   3. THE TILE ("image slightly smaller, text and pills slightly wider"). The
      art is a FIXED `982.4 * --u` and the body takes what is left, which at
      820 is 174px — a figure this file already calls cramped at any type size
      and flags as wanting its own tablet layout. This is that layout.
      NOTHING WAS PROPORTIONALLY WRONG: 503/174 is 72%/25% of the tile against
      the design's 71%/26%. The proportion survived the smaller canvas and the
      TYPE did not, which is why the copy column has to take more than its
      designed share here.
      TAKING 10% OFF THE IMAGE GIVES THE TEXT 29%, because the body is the
      small remainder — that asymmetry is the point, and it is why "slightly"
      on the image is not slight on the copy.

   Scoped to portrait as asked. The chips would read just as small on a
   LANDSCAPE tablet (9.6px at 1180) — say the word and the gate moves to
   `hover: none`, which is where the same floors in css/cases.css sit. */
@media (min-width: 768px) and (orientation: portrait) {
  /* THE LIST ROW IS SIZED IN THE TYPE'S OWN UNIT HERE, NOT THE LAYOUT'S.
     Same half-finished-`--du` trap the body scale already records: `--du` is
     floored to 0.68 on a touch tablet while `--u` is 0.512 at 820, so a row at
     `140 * --u` is 72 holding a name whose cap box is 24.7 and tag pills
     floored at their designed 36 — half the row, against a quarter of it on
     desktop. Nothing clips; it just reads cramped, which is the whole of that
     lesson.

     `--du` keeps the row and what sits in it in step BY CONSTRUCTION rather
     than by a number chosen to look right: 95 at 820, and identical to the
     desktop's wherever `--du` is still `--u`. The trail does not run at this
     breakpoint (it is gated on a fine pointer), so this is the row alone. */
  .rdk-work__list--rows .rdk-work__tile {
    height: calc(var(--row-h) * var(--du));
  }

  /* THE HERO IS HALF AGAIN AS TALL (client, 2026-09-17: doubled first, then
     "a bit too much, can we split the difference?"). 381 -> 571.5 design
     units, the midpoint of the two, which is 195 -> 293 at 820.

     THE TITLE MOVES DOWN BY THE WHOLE OF THE INCREASE, so the extra height
     opens up ABOVE it rather than stranding it near the top with empty green
     underneath. 221 + (571.5 - 381) = 411.5 holds it exactly as far from the
     hero's foot as the design puts it — the same call the homepage's
     bottom-pinned stage makes for a taller-than-designed window. */
  .rdk-work-hero {
    height: calc(571.5 * var(--u));
  }

  .rdk-work-hero__title {
    top: calc(411.5 * var(--u));
  }

  /* The node's 960 measure over the node's 40px type. */
  .rdk-work__intro {
    max-width: calc(960em / 40);
  }

  /* 884.16 is 982.4 less 10%. (This used to say the Grid display overrode it
     at a higher specificity — Grid went on 2026-09-17, and List hides the
     artwork outright, so nothing competes for this any more.) */
  .rdk-work__art {
    width: calc(884.16 * var(--u));
  }

  /* THE TILE TAGS ARE FLOORED AT THEIR OWN DESIGNED NUMBERS (client,
     2026-09-17: "make the pills larger in the project tiles. Similar size
     (I know the style is different) to the pills in the featured case study
     section on the homepage").

     NOTHING HAD TO BE RE-PROPORTIONED, WHICH IS THE USEFUL PART: the tile
     tag is ALREADY 13px type at design, exactly what the homepage's case
     pill is, so "similar size" is what these two have whenever neither is
     collapsed. At 820 this one rendered **6.66px in a 19.6 box** — the
     unfloored `--u` again, and the case pills only look bigger there because
     they were floored on 2026-09-17 and these were missed.

     So every length takes its own design value as the floor and the box
     keeps the tile's own shape: 36 tall against the case pill's 24, because
     this one carries a 16 icon and the 14/18 optical padding that the case
     pill has no equivalent of. That IS the style difference the client
     named; matching the HEIGHT as well would mean dropping the icon.

     `--u`-SCALED TERMS ARE KEPT INSIDE EVERY `max()` so the rule is a no-op
     the moment the window is wide enough to overtake the floor — above about
     1600 the design's own value is the larger and this stops applying,
     rather than the boundary becoming a cliff. Same shape as the body scale.

     THE ICON AND THE GAP FLOOR WITH THE TYPE, or the pill grows around an
     icon that stays half-size and the label drifts off the optical centre
     the asymmetric padding exists to correct. */
  .rdk-work__tags li {
    gap: max(7px, calc(7 * var(--u)));
    height: max(36px, calc(36 * var(--u)));
    padding: 0 max(18px, calc(18 * var(--u)))
             0 max(14px, calc(14 * 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)));
  }

  .rdk-work__tags li::before {
    width: max(16px, calc(16 * var(--u)));
    height: max(16px, calc(16 * var(--u)));
  }

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

  /* THE FILTER CHIPS COME WITH THEM, AND THEY NEED THEIR OWN RULE RATHER
     THAN AN EDIT IN PLACE. Their 14px lives in the block the phone SHARES
     with tablet portrait — `(max-width: 767px), (min-width: 768px) and
     (orientation: portrait)` — so editing that declaration would have taken
     the phone to 12 as well, which was not the ask. This overrides it for
     portrait only; the phone keeps 14.

     ONLY THE TYPE MOVES. The 48 height is the tap-target floor (44 iOS / 48
     Android) and the 18 padding is what keeps it reading as a pill rather
     than a stretched one, so both stay — a 12px label in a 48 box is the
     trade this experiment is asking to look at. */
  .rdk-work__chip {
    font-size: 12px;
    letter-spacing: -0.12px;
  }
}
