/* Project page — design node 314:62643 (1600 x 14035).

   This is a TEMPLATE: every project is the same page with different images and
   copy. So there are exactly four things here — a hero, an intro panel, a
   repeatable image block, and the shared footer — and a project supplies data,
   not markup.

   Geometry measured off the frame render: the hero is 1600 x 801 (2:1), the
   intro panel opens at 801 with the same rounded shoulders the homepage intro
   uses, and the image blocks run 1520 wide (40 inset each side) with a 32 gap,
   at heights between 700 and 2612. */

/* ---------- hero ----------
   Full-bleed brand image with the client's logo centred on it as its own SVG.
   The image is the SAME master the index tile crops (see CLAUDE.md), which is
   what the tile -> hero View Transition morphs between. */
/* 897 tall, NOT the 801 a first measurement suggested — 801 is where the intro
   panel's rounded shoulders begin RIDING UP OVER the hero, the same overlap the
   homepage uses. The hero image node itself renders 1600 x 897. */
.rdk-project-hero {
  position: relative;
  aspect-ratio: 1600 / 897;
  background: var(--project-bg, var(--rdk-black-green));
  overflow: clip;
}

.rdk-project-hero__shot {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}

/* The tile's designed width scaled by 1600/982.418 — see css/work.css for
   where `--logo-w` comes from. Same mark, same designed proportion, bigger
   frame. */
.rdk-project-hero__logo {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  width: calc(var(--logo-w, 280) * 1.6286 * var(--u));
  max-width: 46%;
  height: auto;
}

/* ---------- intro ----------
   Headline left, body right, service chips beneath — on the page ground, with
   the shoulders that make it ride up over the hero exactly as the homepage
   intro does over its own. */
/* ---------- the introduction, node 314:64862 ----------
   Two flush columns: a fixed 705 on the left with the crumb and the headline,
   the remaining 600 on the right with the description and the service tags.
   705 + 600 = 1305, which is exactly the row's width once its own 88/32 is
   taken off 1425 — so `space-between` leaves no gap and the node's numbers
   close. */
.rdk-project__intro {
  position: relative;
  z-index: 1;
  /* The panel still rides up over the hero's last 96 — that is what puts its
     rounded top at the frame's 801 — but the padding that used to clear it was
     104, which pushed the content 8 past where the node puts it. 96 lands the
     crumb on the node's 897. */
  margin-top: calc(-96 * var(--u));
  padding: calc(96 * var(--u)) calc(88 * var(--u)) calc(96 * var(--u));
  background: var(--rdk-black-green);
  border-radius: calc(93.5 * var(--u)) calc(93.5 * var(--u)) 0 0;
}

/* The row carries ITS OWN 88/32 inside the section's 88, so the columns start
   176 in. Not a mistake in the node — it is what pulls the text away from the
   panel's rounded corner. */
.rdk-project__lede {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding-left: calc(88 * var(--u));
  padding-right: calc(32 * var(--u));
}

.rdk-project__col {
  flex: none;
  width: calc(705 * var(--u));
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(32 * var(--u));
}

/* THE TAGS LIVE IN HERE, not under the whole row — which is the entire reason
   they line up with the description (client, 2026-09-01: "left-aligned with
   the description text"). `align-items: flex-start` in one column beats any
   offset arithmetic against the other, and it survives the column widths
   changing. */
.rdk-project__aside {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(80 * var(--u));
  padding-top: calc(56 * var(--u));
}

/* ---------- the way back to the work index ----------
   No uppercase: the node sets none, which also settles the question of what to
   do with a client's own casing — nuHarbor, BlueDolphin and Arctic Wolf all
   keep their marks as drawn. */
.rdk-project__crumb {
  display: flex;
  align-items: center;
  gap: calc(8 * var(--u));
  margin: 0;
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(18 * var(--u));
  line-height: 1.4;
  letter-spacing: calc(0.18 * var(--u));
}

.rdk-project__crumb a {
  display: flex;
  align-items: center;
  gap: calc(16 * var(--u));
  color: var(--rdk-green);
  text-decoration: none;
  transition: color 0.24s ease;
}

.rdk-project__crumb a:hover,
.rdk-project__crumb a:focus-visible {
  color: var(--rdk-lime);
}

/* The exported chevron (asset box 7.087 x 10.982, path 3.896 x 7.792), sized
   to its PATH box because that is the 3.896 the node lays out against —
   3.896 + 16 gap + 44 label = the 63.896 group width exactly. `overflow:
   visible` keeps the 2.256 stroke from being clipped by that tighter box.
   Flipped rather than redrawn: the design applies rotate-180 + scale-y-100,
   which composes to a plain horizontal flip. */
.rdk-project__crumb-arrow {
  flex: none;
  width: calc(3.89577 * var(--u));
  height: calc(7.79154 * var(--u));
  overflow: visible;
  scale: -1 1;
  transition: translate 0.28s cubic-bezier(0.33, 1, 0.68, 1);
}

.rdk-project__crumb a:hover .rdk-project__crumb-arrow,
.rdk-project__crumb a:focus-visible .rdk-project__crumb-arrow {
  translate: calc(3 * var(--u)) 0;   /* +3 because the glyph is mirrored */
}

.rdk-project__crumb i {
  font-style: normal;
  color: var(--rdk-white);
  opacity: 0.2;
}

.rdk-project__crumb b {
  font-weight: 700;
  color: var(--rdk-white);
}

/* 46, not the 52 this carried before. */
.rdk-project__headline {
  margin: 0;
  max-width: calc(506 * var(--u));
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(46 * var(--du));
  line-height: 1;
  letter-spacing: calc(-1.15 * var(--du));
  color: var(--rdk-white);
}

/* 18/1.4/+0.18 at 80%. Was 19/1.5, no tracking, 85%. */
.rdk-project__body {
  margin: 0;
  font-family: var(--rdk-font-body);
  font-weight: var(--rdk-body-weight);
  font-size: var(--rdk-body-m);
  line-height: var(--rdk-body-lh);
  letter-spacing: var(--rdk-body-track);
  color: var(--rdk-white);
  opacity: 0.8;
}

/* ---------- service tags ----------
   The work index's chips at 1.5556x: icon 16 -> 24.889, padding 16/10 ->
   24.889/15.556, gap 4 -> 6.222, radius 32 -> 49.778. The LABEL does not
   follow that scale — 13 -> 16, not 20.2 — so it is taken literally.

   Same `--chip-icon` mask the chips use, mapped in css/work.css against
   `data-service`, so one icon file still serves the filter bar, the tiles and
   these. */
/* The pill is css/pill.css, shared with the services page's "What you get"
   lists. What is left here is this page's own: the tags sit on one baseline
   with the fallback labels' icon. */
.rdk-project__tags {
  align-items: center;
}

/* Only the high-level FALLBACK tags carry an icon, so only they want the gap
   the shared pill does not set. */
.rdk-project__tags li[data-service] {
  gap: calc(6.222 * var(--u));
}

.rdk-project__tags li::before {
  content: "";
  flex: none;
  /* Sized to the 40 pill, not the old 56 — at 24.889 it filled it edge to
     edge. Only the high-level fallback tags have one. */
  width: calc(17 * var(--u));
  height: calc(17 * var(--u));
  background: currentColor;
  mask: var(--chip-icon, none) no-repeat center / contain;
  -webkit-mask: var(--chip-icon, none) no-repeat center / contain;
}

/* A GRANULAR TAG HAS NO ICON, AND MUST NOT RESERVE ONE. The icon comes from
   `--chip-icon`, keyed off `data-service` in css/work.css — which only the
   high-level labels carry. Without this the ::before is an empty 24.889 box
   plus its gap, i.e. ~31u of phantom space before every label. Node 119:4791
   draws these as plain text pills, so dropping it is also what the design
   wants, and the padding goes to that node's own 25.6. */
.rdk-project__tags li:not([data-service])::before { display: none; }
/* No icon, so no gap to reserve — the padding is the pill's own. */

.rdk-project__blocks {
  display: flex;
  flex-direction: column;
  gap: calc(32 * var(--u));
  margin: 0;
  /* No bottom padding: `.rdk-project__more` follows and owns the gap to
     the footer (node 544:53695 puts 140 above its title). */
  padding: 0 calc(40 * var(--u));
  background: var(--rdk-black-green);
  list-style: none;
}

.rdk-project__block {
  margin: 0;
  /* --h is the block's height in design units, straight off the frame. Stated
     as an aspect so the block scales with --u instead of being pinned. */
  aspect-ratio: 1520 / var(--h, 730);
  background: var(--block-bg, rgba(255, 255, 255, 0.04));
  border-radius: calc(40 * var(--u));
  overflow: clip;
}

/* A block's artwork may be a <video> (see tools/projects.py) — same box, same
   crop, so the two share one rule rather than drifting. */
.rdk-project__block img,
.rdk-project__block video {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}

/* A TWO-UP ROW (2026-09-14). Some projects pair two blocks side by side; the
   halves are 744 wide, which is `(1520 - 32) / 2` — the block measure less the
   blocks' own gap. The row inherits that same 32 gap rather than restating it,
   so the horizontal rhythm and the vertical one can never drift apart.

   THE GAP AND THE CORNERS ARE OURS, NOT THE ARTWORK'S. Datascan's first export
   arrived as one image per row with the gutter and the inner corner radii baked
   in — which pins them to one width and one breakpoint, and puts a transparent
   stripe down the middle that WebP conversion turns into an opaque black bar.
   Two files plus this rule keeps all of it in CSS. See CLAUDE.md. */
.rdk-project__row {
  display: flex;
  gap: calc(32 * var(--u));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* `flex: 1 1 0` and not `50%` — the basis has to ignore the intrinsic image
   width, or a half whose file is a different size takes a different share. */
.rdk-project__row > .rdk-project__block {
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 744 / var(--h, 860);
}

/* A block with no artwork yet is the comp's own empty card rather than a gap —
   the design has one of these too. */
.rdk-project__block--empty {
  background: #1a1a1a;
}

@media (max-width: 767px) {
  /* 453, not 420 — 420 is where the intro's shoulders START, exactly as 801
     is on desktop, and the extra 33 is what they ride up over. The visible
     hero is unchanged; the image simply grows underneath the panel (client,
     2026-09-01: "the image either needs to be taller or we need to move the
     black section up"). */
  .rdk-project-hero { aspect-ratio: 390 / 453; }
  .rdk-project-hero__logo { width: 62%; max-width: 62%; }

  .rdk-project__intro {
    /* THE OVERLAP IS TIED TO THE RADIUS, and that is the whole fix. The
       shoulder is a 32 arc; it used to sit on 24u of hero, which is 5.6px at
       375 — so the arc's outer corners fell off the bottom of the image and
       left a blue nick at each end of the seam, which is the "gap". Desktop
       has the same relationship the right way round (96 of overlap to a 93.5
       radius, 1.03x), so this is 33 to the 32 radius.

       Straight px, not `--u`: the radius it answers to is a fixed 32 here, so
       a proportional overlap would drift out of step across the 320..767
       range and the nick would come back at the narrow end. */
    margin-top: -33px;
    padding: 56px 20px 40px;
    border-radius: 32px 32px 0 0;
  }
  /* The row's inner 88/32 goes: at 390 it would leave the text 120 wide. */
  .rdk-project__lede { flex-direction: column; padding-left: 0; padding-right: 0; }
  .rdk-project__col { width: 100%; gap: 14px; }
  .rdk-project__aside { padding-top: 28px; gap: 28px; }
  .rdk-project__crumb { font-size: 15px; gap: 7px; letter-spacing: 0.15px; }
  .rdk-project__crumb a { gap: 12px; }
  .rdk-project__crumb-arrow { width: 3.5px; height: 7px; }
  /* `balance` HERE, not the `pretty` the body sets (client, 2026-09-01).
     Measured at 375: the headline greedily breaks 330 / 120 / 318 in a 335 box
     — a hole in the middle line — and `pretty` cannot touch it, because it only
     reworks the LAST line to kill an orphan and this last line is already the
     longest of the three. `balance` evens the whole rag: 251 / 199 / 318.

     Scoped to this one heading and to mobile. It is not moved up to `body`
     alongside `pretty` because `balance` is free to re-break any heading it
     applies to, and the homepage intro headline's four rows are designed ones
     — see the note in css/base.css. */
  .rdk-project__headline { font-size: 34px; letter-spacing: -1.02px; max-width: none;
                           text-wrap: balance; }
  /* Size and gap are css/pill.css's; only the icon gap is this page's. */
  .rdk-project__tags li[data-service] { gap: 5px; }
  .rdk-project__tags li::before { width: 15px; height: 15px; }

  .rdk-project__blocks { gap: 16px; padding: 0 12px 72px; }
  .rdk-project__block { border-radius: 20px; }
  /* STACKED BELOW 767 (client, 2026-09-14). Side by side the halves are ~170px
     on a 390 screen, which no screenshot survives. Each then fills the measure
     and simply renders taller — the aspect is unchanged. */
  .rdk-project__row { flex-direction: column; gap: 16px; }
}

/* ---------- "Let's talk", knocked out of its own pill ----------
   Client, 2026-09-01: black on the project pages, or better, cut out so the
   background shows through the letters. It is the second one — the letterforms
   are a genuine hole and you can see the hero's gradient and striations moving
   behind them.

   THE BLEND GOES ON THE NAV, NOT ON THE BUTTON, and that is the whole trick.
   `mix-blend-mode` resolves against the backdrop inside the nearest ancestor
   STACKING CONTEXT, and `.rdk-nav` is one (`position: fixed` + `z-index: 100`).
   Put `screen` on the pill and it blends against the nav's own empty backdrop,
   so black stays black — built that way first, and the text just went black.
   Put it on the NAV and the nav blends against ITS parent, which is the root,
   so the hero is in scope.

   `screen` is the right operator because it is a no-op on white:
   screen(white, x) = white, so the pill, the wordmark and the links are all
   untouched; screen(black, x) = x, so pure black becomes exactly the backdrop.
   The ink has to be #000 and not `--rdk-black-green` — anything above zero
   lightens the hole by that much and the illusion goes with it.

   THE TEXT IS BLACK AT EVERY SCROLL POSITION, not only at the top, and that is
   deliberate. The blend can only be on or off — it cannot tween — so pairing
   it with a colour that also changes gives a black flash at the hand-off:
   the blend cuts out instantly while `transition: color` is still part way
   through. Black throughout means only the blend changes, and away from the
   hero the button lands on the client's first suggestion anyway.

   This file is loaded ONLY by the project pages, so it needs no page scoping.
   The breakpoint is doing real work though: below 768 the CTA is hidden and
   the Menu pill takes its place — same treatment, its own numbers, in the
   second query below.

   NOT WHILE THE MENU IS OPEN. The overlay is a sibling that covers the page
   and the nav sits on top of it, so `screen` would resolve the letters
   against the overlay's green rather than the hero — and on mobile it would
   also wash out the lime close disc the pill morphs into. The blend cannot
   tween, so it is simply off in that state. */
@media (min-width: 768px) {
  .rdk-nav__cta {
    color: #000;
  }

  /* ONLY WHERE THE HERO IS DARK ENOUGH BEHIND THE BUTTON. A hole is exactly
     as legible as what is behind it, and `screen` can only ever LIGHTEN — the
     operator that would darken (`multiply`) erases the white pill along with
     the letters, so there is no way to rescue a pale backdrop.

     Measured per project at the CTA's box, contrast against the white pill:
     arcticwolf 19.65, collibra 16.78, bluedolphin 5.81, nuharbor 3.53 —
     all clear the 3:1 the 20px bold label needs. Datascan is 2.81 and
     Everpure 1.71, which is effectively invisible, so those two keep plain
     black. `tools/projects.py` owns the list and stamps `rdk-cutout` on the
     body; the numbers are recorded there. */
  body.rdk-cutout .rdk-nav--at-top:not(.rdk-nav--menu-open) {
    mix-blend-mode: screen;
  }
}

/* ---------- and the same for Menu, which is the only pill below 768 ----------
   Client, 2026-09-01. It used to be the nav's default GREEN on the white pill,
   which is 2.49:1 — it does not pass at any size. Black is 21:1, and where the
   hero can carry it the label is knocked out of the pill exactly as "Let's
   talk" is on desktop.

   THE BAR IS 4.5:1 HERE, NOT 3:1, and that is why the eligible set is smaller.
   The desktop label is 20px bold, which WCAG counts as large text; node
   1:1814's Menu label is 15px, which is not (large starts at 18.66px bold), so
   it is scored as normal text.

   Sampled the hero where the pill actually sits — which on mobile is NOT the
   desktop region, because `object-fit: cover` on a 390 x 453 box crops a
   1600 x 897 master to its middle 48%. The pill's box maps to source
   x 991..1162, y 24..104; average colour there, contrast against the white
   pill:

     arcticwolf   rgb(10,21,59)      17.75   knockout
     collibra     rgb(26,27,34)      17.16   knockout
     bluedolphin  rgb(6,105,166)      5.87   knockout
     nuharbor     rgb(1,139,240)      3.53   PLAIN BLACK
     everpure     rgb(176,137,121)    3.13   PLAIN BLACK
     datascan     rgb(8,152,254)      3.03   PLAIN BLACK

   nuHarbor is the one that splits: it clears 3:1 for the desktop CTA and is
   listed in `CUTOUT`, and it misses 4.5:1 here. So the two sets are stamped
   separately — `tools/projects.py` owns both, and the numbers are recorded
   there too. */
@media (max-width: 767px) {
  .rdk-nav__menu {
    color: #000;
  }

  body.rdk-cutout-sm .rdk-nav--at-top:not(.rdk-nav--menu-open) {
    mix-blend-mode: screen;
  }
}

/* ==========================================================================
   "MORE WHERE THAT CAME FROM" — the related-projects module (node 544:53695,
   2026-09-14). Sits between the image blocks and the footer on every project
   page, carrying the PREVIOUS and NEXT project in the list (client: "maybe the
   next and previous projects? So they're different on every page"), wrapping
   round at both ends so no page is ever short a card.

   THE BUTTON IS `.rdk-about__cta-group`, NOT A NEW ONE. The node is the shared
   pill + chip at its `On Dark` surface — same 80 height, 4 border, 56 padding
   and the same 6.769 x 13.538 caret the About CTA already documents — so this
   file links css/about.css for it and overrides one thing, the ink. A second
   implementation would drift, and the shove lives in the shared one.
   ========================================================================== */
.rdk-project__more {
  /* The blocks list above pays no bottom padding now; this owns the gap. */
  padding: calc(140 * var(--u)) calc(88 * var(--u)) calc(160 * var(--u));
  background: var(--rdk-black-green);
}

.rdk-project__more-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: calc(40 * var(--u));
  margin-bottom: calc(80 * var(--u));
}

/* Display/L. THE NODE SAYS 89.451 / -5.3671 (-6%) AND BOTH ARE IGNORED: the
   ladder's rung is 88 at -4%, and -6% would track this tighter than the 104
   above it, which is the exact inversion the 2026-09-09 pass removed. Same
   artefact the cases title carried; see CLAUDE.md. */
.rdk-project__more-title {
  max-width: calc(792 * var(--u));
  margin: 0;
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(88 * var(--du));
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--rdk-off-white);
}

/* The pill + chip on a DARK surface: the component's ink is the page ground,
   which is invisible here. Both halves, since the chevron is currentColor. */
.rdk-project__more .rdk-about__cta,
.rdk-project__more .rdk-about__cta-chip {
  color: var(--rdk-white);
}

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

/* THE FOOTER'S OWN 144 OF PAGE GROUND COMES OFF HERE. css/footer.css puts it
   above every footer on the site, and the node measures 160 from the cards to
   the green — so left alone the two stack to 304. This section owns the gap,
   the same way it took the blocks list's bottom padding.

   The footer is a sibling of <main>, not of the section, so this is `:has()`
   on main rather than a sibling combinator — and that also scopes it: only a
   page that actually carries the module loses the margin. */
main:has(.rdk-project__more) + .rdk-footer {
  margin-top: 0;
}

@media (max-width: 767px) {
  /* Invented — the node is desktop only. Straight px, like every other phone
     block here: `--u` collapses to a quarter of its design value at 390. */
  /* THE BUTTON GOES UNDER THE TILES (client, 2026-09-18). On the desktop the
     head is a row with the title at one end and the CTA at the other, so the
     button reads as belonging to the heading; stacked on a phone it sat
     between the heading and the work it introduces, which is the wrong way
     round — you offer "all projects" AFTER showing two of them.

     `display: contents` ON THE HEAD IS WHAT MAKES `order` REACH IT. `order`
     only sorts SIBLINGS, and the list is outside the head — so dissolving the
     head's box promotes the title and the CTA to direct children of the
     section alongside the list, and the three can then be ordered. The head is
     a plain wrapper with no semantics of its own, so it costs nothing: the
     heading is still an `<h2>` and the section still names itself through
     `aria-labelledby`.

     ITS `gap` AND `margin` GO WITH THE BOX, which is the catch — a
     `display: contents` element has no box to space anything with. Both are
     restated on the items that now carry them. */
  .rdk-project__more {
    display: flex;
    flex-direction: column;
    padding: 72px 20px 80px;
  }
  .rdk-project__more-head { display: contents; }

  .rdk-project__more-title {
    order: 0;
    max-width: none;
    margin-bottom: 32px;
    font-size: 46px;
    letter-spacing: -1.84px;
  }

  .rdk-project__more-list { order: 1; }

  /* FULL WIDTH (client, 2026-09-18). The column's own `stretch` does it, so
     this is the `align-self: flex-start` that was here coming back off — but
     the pill has no stated width (the styleguide's rule: it is sized by its
     padding), so without `flex: 1` below it keeps its natural size and the
     group's extra width opens as a gap between it and the chip. */
  .rdk-project__more .rdk-about__cta-group {
    order: 2;
    margin-top: 40px;
  }

  /* The chip keeps its 64 square; the pill takes everything else.

     IT DOES STOP THE SHOVE WORKING HERE, and that costs nothing: the shove
     grows the pill's PADDING, and a `flex: 1` item's outer width is resolved
     by the flex algorithm, so the padding eats its own content box instead of
     pushing the chip. Measured — at `--grow: 1` the pill stays 290 and the
     chip moves 0. It is a HOVER effect (js/btn-shove.js binds `mouseenter`)
     and this rule only exists below 767, where `hover` is `none`, so nothing
     that could have fired is lost. Desktop is untouched and still measures
     +9.5 per side. */
  .rdk-project__more .rdk-about__cta { flex: 1; }
}

/* The intro body's designed break (node 119:4791) is desktop-only — at the
   phone's ~350 measure a hard break mid-paragraph reads as a mistake rather
   than as ragging. The markup keeps a space before it, so the two halves do
   not butt together once it is hidden.

   NOTHING USES IT AT THE MOMENT: Datascan's was the only one and it came out
   on client QA (bug tracker row 51). Kept so a break added later is already
   right on the phone. */
@media (max-width: 767px) {
  .rdk-project__body-break { display: none; }
}

/* ============================================================
   TABLET PORTRAIT — THE LEDE IS ONE COLUMN (client, 2026-09-17: "can you put
   the body copy under the headline? so headline, body and pills would be one
   column").

   THE PHONE ALREADY DOES THIS, so this is the phone's own arrangement rather
   than a new one — `.rdk-project__lede` goes to a column and both halves take
   the full measure. Only the STACKING is shared: the phone's block also
   restates the crumb and headline at phone sizes, which a tablet does not
   want, so those are left where they are.

   THE BODY KEEPS A MEASURE, and that is the part stacking makes necessary.
   Side by side the aside is 584 at an 18px body — 32.4em a line. Released to
   the lede's full 715 at the floored 17px it would be 42em, twice what the
   rest of the site runs at. `584em / 18` is the design's measure over the
   design's font size, so it is 32.4em at whatever size the type resolves to:
   551 here, and the desktop's own 584 wherever the type is still `--u`-scaled.

   28 between the blocks is the phone's, not a new number — the body is 17px
   on both breakpoints, so it is the same relationship to the type. */
@media (min-width: 768px) and (orientation: portrait) {
  /* THE COPY LINES UP WITH THE NAV WORDMARK (client: "copy should be
     left-aligned with the site nav logo"). It sat at 90 against the
     wordmark's 55.

     THE TWO DRIFT APART BECAUSE THE NAV'S INSETS ARE FLOORED AND THE PANEL'S
     ARE NOT. The nav is `max(45px, 72 * --u)` plus a `max(10px, 16 * --u)`
     plate pad — 45 + 10 = 55 at 820, both floored — while the lede sits at
     `88 * --u` and adds another `88 * --u`. So a fixed nudge would only be
     right at one width: at 768 the lede's own box is 42.2 and at 1024 it is
     56.3, against a nav inset of 55 and 56.3. The padding is the DIFFERENCE
     between the two, computed from the nav's own numbers, so they track.
     `max(0px, …)` because above about 1024 the floors stop binding and the
     two expressions meet exactly. */
  .rdk-project__lede {
    flex-direction: column;
    padding-left: max(0px, calc(
      max(45px, calc(72 * var(--u))) + max(10px, calc(16 * var(--u)))
      - 88 * var(--u)));
  }

  .rdk-project__col {
    width: 100%;
  }

  .rdk-project__aside {
    width: 100%;
    padding-top: 28px;
    /* 71, NOT THE 28 THIS BLOCK FIRST BORROWED FROM THE PHONE (client: "add
       more space between the body copy and pills"). The DESKTOP's own gap here
       is 80 against an 18px body — 4.4em — so 28 was not merely tight, it was
       tighter than the design at a breakpoint where everything else had grown.
       `80 * --rdk-body-m / 18` is that gap in the type's own units: 71 here,
       and the desktop's 80 wherever the body is 18. */
    gap: calc(80 * var(--rdk-body-m) / 18);
  }

  /* THE HEADLINE TAKES THE BODY'S MEASURE, EXACTLY (client: "should be same
     width as the body copy"). Both read the same expression, so they cannot
     drift: `584 * --rdk-body-m / 18` is the design's aside measure over the
     design's body size — 519 here, 584 wherever the body is still 18. It has
     to be written against the TOKEN rather than in `em`, because `em` resolves
     against each element's OWN font size and the headline's is 46, not 18. */
  /* 666, up from the design's 584 (client: "I think we can make the body copy
     wider too"), which is 592 here against 519 — the room the alignment above
     just freed on the left. Still the same one expression for both, so the
     headline and the body cannot come apart. */
  .rdk-project__body,
  .rdk-project__headline {
    max-width: calc(666 * var(--rdk-body-m) / 18);
  }

  /* AND IT IS FLOORED AT ITS DESIGNED 46 (client: "make the headline bigger").
     `46 * --du` is 31.3 on a device; at 46 in the widened 519 measure the line
     is 11.3em, which IS the desktop's own 11em — so the floor and the measure
     together reproduce the design's proportion rather than inventing one.

     THE TRACKING IS `em` AND THAT IS DELIBERATE: -1.15 over 46 is exactly
     -0.025em, so it follows the font size through the floor with nothing to
     keep in step. A `max()`/`min()` pair on a NEGATIVE length is the kind of
     thing that reads correct and floors the wrong way. */
  .rdk-project__headline {
    /* 38.5 — the midpoint of the 31.3 this was and the 46 it went to
       (client: "now the headline is too big. Can we split the difference?").
       The tracking needs no partner change, being `em`. */
    font-size: max(38.5px, calc(46 * var(--du)));
    letter-spacing: -0.025em;
  }

  /* THE CRUMB KEEPS ITS DESKTOP SIZE (client: "the breadcrumbs are much too
     small... they can probably stay the same size as desktop"). `18 * --u` is
     9.2 here. Floored at 18 with its gap, and its tracking moved to `em` for
     the reason above — 0.18 over 18 is 0.01em. */
  .rdk-project__crumb {
    font-size: max(18px, calc(18 * var(--u)));
    gap: max(8px, calc(8 * var(--u)));
    letter-spacing: 0.01em;
  }

  /* THE PILLS ARE FLOORED AT THE COMPONENT'S OWN NUMBERS (client: "pills need
     to be larger"). `40 * --u` is 20.5 at 820 with an 8.2px label — half the
     drawing, the same collapse the case-study and filter chips had.

     FLOORED HERE AND NOT IN css/pill.css, WHICH IS THE UNCOMFORTABLE CHOICE.
     That file exists so the project tags and the services page's "What you
     get" pills cannot drift — the client's instruction was to "make them
     actually share one rule" — so flooring one caller is exactly the thing it
     was written to prevent, and on this breakpoint the two now differ.

     The reason is measured, not a preference: the services pills sit in a
     FIXED 530u column, which is 272 here. Floored, the widest is 224, so only
     one fits per row and the list goes from ~4 rows to EIGHT — those cards
     grow by roughly 270px each. That is a layout change to a page the client
     did not ask about, and it needs a column-width decision rather than a
     silent side-effect. Flooring the services pills is the same four lines
     moved back into css/pill.css, once `--pill-col` has an answer. */
  .rdk-project__tags {
    gap: max(6px, calc(6 * var(--u)));
  }

  /* ---------- "More where that came from" ----------
     THE TITLE WAS THREE LINES ON A DEVICE AND TWO IN THE PANE (client: "we
     need to get the headline onto two lines... looks good in the pane but on
     device looks off"), which is the `--du` split again: 88 * --du is 45.1
     where the pane falls back to `--u` and 59.8 where a tablet floors it,
     against a `max-width` of `792 * --u` = 406 that does NOT float with it.

     THE CAP IS WHAT BINDS, NOT THE FLEX ROW — worth stating, because the
     obvious read is that the "All projects" button is stealing the width.
     Released, the title has the section's full 805, and at 59.8 one line
     needs about 819, so it breaks to a clean two.

     THE HEAD STAYS A ROW (client: "I'd rather the headline and button be on
     the same row if it's possible? It looked good in the pane"). It is: with
     the cap gone the title shares the row with the button and takes what is
     left — 805 less the button's 244 and a 20.5 gap is 540, and at 59.8 that
     is comfortably the same two lines, since one line would need 819. The
     stacked version was a precaution against a squeeze that does not happen.

     THE CARD BLURB LOSES ITS CAP TOO (client: "the copy needs to go edge to
     edge. It's breaking too early causing a weird gap") — `467 * --u` is 239
     inside a 347 card, so it was wrapping ~108px short of the edge. */
  .rdk-project__more-title {
    max-width: none;
  }

  /* AND THE TITLE IS CAPPED AT 55, not floored. `88 * --du` is 59.8 on a
     device, and beside the button the title gets 540 at 820 but only 405 at
     768 — where "that came from" measures 410 and tips to a THIRD line. Five
     pixels short.

     CAPPING THE TYPE RATHER THAN CLOSING THE GAP, because the gap fix is a
     5px margin and this is not: at 55 that line is 377, which clears 405 by
     28, and one line would still need 753 against the 540 available at 820,
     so it cannot collapse to one either. `min()` and not `max()` — it lowers
     the floored device value and leaves the pane's own 45.1 alone, which is
     the rendering the client approved. */
  .rdk-project__more-title {
    font-size: min(55px, calc(88 * var(--du)));
    /* THE GAP WAS THE LEFTOVER, NOT A GAP (client: "just reduce the space
       between headline and button"). The title has no flex sizing, so it
       shrinks to its CONTENT — 377 — and `justify-content: space-between`
       then pushes the button to the far edge, turning the unused 93 into
       what reads as an enormous gap. Given the slot with `flex: 1 1 auto`
       there is no leftover, and the space between them is the row's own
       `gap` and nothing else: 20.5 at 820 against 93.

       IT ALSO MAKES THE TWO LINES DETERMINISTIC, which is the other half of
       the report. Shrink-to-fit means the title wraps against a width the
       browser derives from the text itself; filling the slot it wraps against
       a known 470 at 820 and 424 at 768, both of which clear the 377 that
       "that came from" measures at the capped 55. */
    flex: 1 1 auto;
  }

  

  /* 0.9 OF THE COMPONENT'S OWN NUMBERS (client: "can we make the pills
     slightly smaller? Are these the same size as desktop?" — they were
     exactly desktop's, the floor being the pill's designed 40 / 16 / 24,
     which is what `--u` = 1 gives). One scale across all three so the pill
     keeps its proportions rather than becoming a different shape: 36 / 14.4 /
     21.6, which sits between desktop's 40 / 16 / 24 and the phone's
     36 / 13 / 16. The floors only ever raise, so a width where the scaled
     value is already larger is untouched. */
  .rdk-project__tags li {
    height: max(36px, calc(40 * var(--u)));
    padding: 0 max(21.6px, calc(24 * 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(16 * var(--u)));
  }
}
