/* ===================================================================
   DESIGN DIRECTIVE — "SPECIMEN 11/11"

   LAYOUT APPROACH
   The entire page — header, hero, eleven directives, two cards, the
   01–11 index column, and the vertical rail — is ONE CSS
   Grid with explicit grid-template-columns, explicit
   grid-template-rows, and a named grid-template-areas map. Every
   element is placed with `grid-area`, not with margins or
   top/left offsets. Padding is used only for the internal
   breathing room of a cell, never to fake a cell's position.
=================================================================== */

/* -------------------------------------------------------------
   DESIGN TOKENS
------------------------------------------------------------- */
.dd-root{
  --dd-bg:            #fff;
  --dd-line:          rgba(9,9,9,0.14);
  --dd-line-bright:   rgba(9,9,9,0.32);
  --dd-fg:            #111;
  --dd-fg-dim:        rgba(17,17,17,0.62);
  --dd-fg-faint:      rgba(17,17,17,0.38);
  --dd-mono:          "IBM Plex Mono","JetBrains Mono","SFMono-Regular",Consolas,monospace;
  --dd-sans:          "Neue Haas Grotesk Display","Helvetica Neue",Helvetica,Arial,sans-serif;
  --dd-pad:           28px;
  --dd-transition:    420ms cubic-bezier(.16,.8,.24,1);
  /* A touch slower than --dd-transition on purpose: that token is for quick
     UI feedback (colour, outline, arrow), but the directive-change slide is
     a much larger, more visible move and reads as rushed at the same
     duration. Same curve as --dd-transition, though, not a gentler one —
     an ease-out with a fast start is what makes the motion read
     immediately; a slow-start curve here made the two frames look like
     they were sitting still and overlapping for most of the transition,
     which is worse than just being quick. */
  --dd-slide-transition: 560ms cubic-bezier(.42,0,.58,1);

  /* How long one directive plate takes to cross-fade into the next while the
     card cycles a gallery. It is both the fade's duration and how long the
     outgoing plate is held underneath it, so the two can never drift apart. */
  --dd-plate-fade:    1040ms;

  /* How far below the top of the viewport the pinned card column comes to
     rest. Now that the header (.dd-area-nav) is sticky too, this has to
     equal its height exactly — anything less and the pin would travel up
     underneath the header instead of stopping flush against its bottom
     border, which is the whole point of the header staying on screen. */
  --dd-pin-top:       var(--dd-row-nav);

  /* explicit column tracks — match the reference's 4 vertical bands:
     directives | cards | index numbers | vertical rail */
  --dd-col-directives: 1.55fr;
  --dd-col-cards:      1fr;
  --dd-col-index:      0.55fr;
  --dd-col-rail:       36px;

  /* explicit row tracks — header, hero, eleven equal directive rows.
     The directive rows are deliberately tall enough for the longest copy in
     the list: every row has to be the same height, because the card column's
     line layer divides its own height into equal parts to match them. */
  --dd-row-nav:    76px;
  --dd-row-hero:   220px;
  /* Fixed, not viewport-based, by design: a directive row's height used
     to be a clamp() tied to viewport height, meant to fit three rows in
     whatever space was left below the hero. That formula predates
     content-driven rows and no longer matches what's actually here —
     every directive's copy was normalized to three sentences specifically
     so all twelve rows (eleven directives + Brands) render at very close
     to the same natural height (124–184 characters across all of them).
     Given that, using ONE fixed height for every row — generous enough to
     comfortably fit the longest copy — is what actually simplifies the
     whole pin/cards system: every height below (line cells, the pin's
     overlay margin, the main and CTA cards) becomes a plain CSS calc()
     from this one number, with no JS measurement step required at all.
     190px matches the old clamp's own maximum, which was already
     validated through extensive testing to comfortably fit this copy. */
  --dd-row-dir:    190px;

  /* Re-coupled to --dd-row-hero (MANIFESTO's own box height) — a
     decoupled, independent target (400px, unrelated to MANIFESTO) was
     tried here to make the photo bigger on its own, but that broke the
     thing that actually matters: the main card's total height no longer
     equalled "MANIFESTO's box + the directive rows it spans." Matching
     it back to --dd-row-hero exactly is what restores that. Breakpoints
     where the cards column does not reach into the hero row set this
     to 0, same as before. */
  --dd-card-lead:  var(--dd-row-hero);

  position:relative;
  background:var(--dd-bg);
  color:var(--dd-fg);
  font-family:var(--dd-sans);
  -webkit-font-smoothing:antialiased;
  width:100%;
  box-sizing:border-box;
}
.dd-root *,
.dd-root *::before,
.dd-root *::after{ box-sizing:border-box; }
.dd-root a{ color:inherit; text-decoration:none; }
.dd-root ul{ list-style:none; margin:0; padding:0; }

/* The sideways clip lives on the root element, not on .dd-root: setting
   overflow-x on a wrapper forces its overflow-y to compute to auto, which
   makes that wrapper the nearest scrollport and freezes any sticky element
   inside it. On <html> it propagates to the viewport instead, so the
   viewport stays the scroll container and .dd-cards__pin can still pin. */
/* Block-based directive scrolling: native CSS scroll-snap, paired with
   scroll-snap-align on .dd-area-hero and each [data-directive] row
   further down this file. mandatory (not proximity) so a scroll always
   settles on a directive rather than resting partway between two —
   that's the "clean, structured" feel that was asked for. scroll-behavior
   here would apply to every scroll on the page including anchor-link
   jumps, which is exactly what's wanted: one scrolling system, not a
   separate CSS default fighting a separate JS-driven glide. */
html{
  overflow-x:hidden;
  scroll-snap-type:y mandatory;
  scroll-behavior:smooth;
}
/* Toggled by initResizeStability() in main.js for the duration of an
   active window resize — scroll-snap-type re-evaluates continuously
   while the window is being dragged, which is what caused the
   directive stack to visibly jump between snap points mid-drag.
   Disabling it entirely during the drag, then re-enabling and
   realigning once afterward, is what makes the resize itself feel
   stable. */
html.dd-resizing{
  scroll-snap-type:none;
}
body{ margin:0; background:#fff; }

/* -------------------------------------------------------------
   THE GRID — single source of truth for layout
------------------------------------------------------------- */
.dd-grid{
  display:grid;
  position:relative;
  grid-template-columns:
    var(--dd-col-directives)
    var(--dd-col-cards)
    var(--dd-col-index)
    var(--dd-col-rail);
  grid-template-rows:
    var(--dd-row-nav)
    var(--dd-row-hero)
    repeat(12, var(--dd-row-dir));
  grid-template-areas:
    "nav    nav    nav    rail"
    "hero   cards  count  rail"
    "dir1   cards  idx1   rail"
    "dir2   cards  idx2   rail"
    "dir3   cards  idx3   rail"
    "dir4   cards  idx4   rail"
    "dir5   cards  idx5   rail"
    "dir6   cards  idx6   rail"
    "dir7   cards  idx7   rail"
    "dir8   cards  idx8   rail"
    "dir9   cards  idx9   rail"
    "dir10  cards  idx10  rail"
    "dir11  cards  idx11  rail"
    "brands cards  idx12  rail";
  width:100%;
}

/* Every cell gets its position exclusively from grid-area.
   Borders act as the visible grid lines from the reference. */
.dd-cell{
  border-bottom:1px solid var(--dd-line);
  border-right:1px solid var(--dd-line);
  padding:26px var(--dd-pad);
  min-width:0;
  min-height:0;
}

/* -------------------------------------------------------------
   AREA ASSIGNMENTS (grid-area only — no margins, no top/left)
------------------------------------------------------------- */
.dd-area-nav{
  grid-area:nav; display:grid; grid-template-columns:1fr auto; align-items:center;
  border-bottom:1px solid var(--dd-line); padding:0 var(--dd-pad);
  /* Sticks at the very top, same idea as .dd-hero-card / .dd-count-card /
     .dd-cards__pin below it — a sticky grid item just needs an area taller
     than itself to travel in, and row 1 is exactly its own height, so it
     simply never leaves. z-index has to clear every other sticky/absolute
     layer that scrolls underneath it (hero/count at 3, the card media's own
     internal layers at up to 3) without reaching the cursor (100) or the
     gallery overlay (90) above it. Opaque background, same reason the pin
     is opaque: the directive rows scrolling past must never show through. */
  position:sticky; top:0; z-index:10; background:var(--dd-bg);
}
/* MANIFESTO and 11 use the same travel principle as the project-card pin:
   each transparent wrapper spans from its hero cell to brands' own bottom
   edge (brands-end, a line name CSS Grid generates automatically from the
   named area), matching exactly how far "cards" itself now spans — see
   the comment above .dd-area-cards for why that reaches through brands
   too. Kept in sync deliberately: if these wrappers stopped at dir11
   while the card column's travel area reached further, MANIFESTO and the
   count card would detach and start scrolling normally a full row before
   the pinned card did, breaking the sense that they're all moving
   together. Generated grid-line names keep the spans aligned when the
   tablet area map moves count left. */
.dd-area-hero{
  grid-area:hero-start / hero-start / brands-end / hero-end;
  min-width:0;
  pointer-events:none;
  z-index:3;
  /* Snap target for the top of the page — not sticky itself (only its
     child .dd-hero-card is), so this is safe from the sticky+scroll-snap
     interaction quirks that can happen when the two land on the same
     element. scroll-margin-top matters here specifically: without it,
     scroll-snap-align:start aligns this element flush with y:0 of the
     scroll container, which is directly behind the sticky header
     (.dd-area-nav, position:sticky, top:0) — not below it. That's what
     was cutting off the first ~var(--dd-row-nav) pixels of whatever
     snapped to the top of the page. */
  scroll-snap-align:start;
  scroll-margin-top:var(--dd-row-nav);
  /* A position:sticky element "unsticks" once its OWN containing block
     runs out of room — the available travel is containingBlockHeight
     minus the sticky element's own height. .dd-hero-card (this wrapper's
     child) is only var(--dd-row-hero) tall, much shorter than
     .dd-cards__pin (roughly --dd-card-lead + 3 rows). Both wrappers
     spanned the exact same grid area (hero-start to brands-end), which
     meant the shorter hero card had MORE travel room than the taller
     pinned card — so near the bottom of the page, the pinned card ran
     out of room and unstuck first, while Manifesto was still stuck a
     moment longer. That's what caused the visible drift between them.
     align-self:start plus this explicit height reduces THIS wrapper's
     effective containing block to exactly what gives .dd-hero-card the
     same travel room as .dd-cards__pin has — the formula is the general
     form of "match the pinned card's travel room", not simplified to
     assume --dd-card-lead always equals --dd-row-hero, since that's not
     true at every breakpoint (it's 0 on tablet). */
  align-self:start;
  height:calc(2 * var(--dd-row-hero) - var(--dd-card-lead) + var(--dd-row-dir) * 9);
}
.dd-area-count{
  grid-area:count-start / count-start / brands-end / count-end;
  min-width:0;
  pointer-events:none;
  z-index:3;
  /* Same fix, same reasoning as .dd-area-hero above — .dd-count-card is
     the same height as .dd-hero-card and has the identical mismatch. */
  align-self:start;
  height:calc(2 * var(--dd-row-hero) - var(--dd-card-lead) + var(--dd-row-dir) * 9);
}
.dd-hero-card,
.dd-count-card{
  position:sticky;
  top:var(--dd-pin-top);
  height:var(--dd-row-hero);
  background:var(--dd-bg);
  pointer-events:auto;
}
.dd-hero-card{ display:flex; flex-direction:column; justify-content:flex-end; padding-bottom:40px; }
.dd-count-card{ display:grid; grid-template-columns:auto 1fr; gap:18px; align-content:start; }

.dd-area-dir1{    grid-area:dir1;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir2{    grid-area:dir2;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir3{    grid-area:dir3;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir4{    grid-area:dir4;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir5{    grid-area:dir5;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir6{    grid-area:dir6;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir7{    grid-area:dir7;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir8{    grid-area:dir8;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir9{    grid-area:dir9;   display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir10{   grid-area:dir10;  display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-dir11{   grid-area:dir11;  display:flex; flex-direction:column; justify-content:center; gap:10px; }
.dd-area-brands{  grid-area:brands; display:flex; flex-direction:column; justify-content:center; gap:10px; }
/* Cards own their whole grid cell: zero padding, zero inner inset, so the
   cell's border lines ARE the card's frame. The main card spans the hero row
   and two directive rows and holds media + caption in one frame, so its
   media takes the leftover space (1fr) — it starts hard against the card's top
   edge and grows into whatever the caption leaves behind. */
.dd-card--main{ display:grid; grid-template-rows:1fr auto; padding:0; overflow:hidden; }
.dd-card--cta{  display:grid; grid-template-rows:1fr; padding:0; overflow:hidden; }

/* THE PINNED CARD COLUMN
   Directive rows are content-height now (auto), not a shared fixed
   token — Brands' three-line copy and Contact's one-line content no
   longer have to pretend to be the same height.

   `cards` shares its grid cell with .dd-cards__lines (12 empty divider
   cells, one per dir1–11 and brands) and .dd-cards__pin (the element
   that actually sticks) stacked inside it. Everything about how tall
   each of these is — the twelve line cells, .dd-area-cards itself, and
   the pin's own position and size — is set directly by main.js's
   syncCardsColumn(), not inferred by CSS. See that function for the
   full reasoning; short version: CSS subgrid and various
   absolute-positioning approaches were each tried here first and each
   failed in a different, hard-to-predict way, so this now measures the
   real DOM instead of asking the grid to infer it. */
.dd-area-cards{
  grid-area:cards;
  /* Position:absolute with subgrid and various in-flow approaches were
     all tried here while rows were content-height (auto) — see the git
     history / earlier notes if that context is ever needed again. None
     of that complexity is necessary anymore: with every row now a fixed,
     known height (--dd-row-dir), this area's total height is just
     calc(hero + 12 rows) — a plain number, not something that needs
     measuring off the real DOM or protecting from grid auto-sizing
     inflation, because there's no auto-sizing happening here at all
     anymore. Back to a normal in-flow block. */
  height:calc(var(--dd-row-hero) + var(--dd-row-dir) * 12);
}
.dd-cards__lines{
  display:flex;
  flex-direction:column;
}
/* Every line cell is the same fixed row height — one CSS rule instead
   of main.js measuring and setting each of the twelve individually. */
.dd-cards__lines .dd-cell{ height:var(--dd-row-dir); flex-shrink:0; }
.dd-cards__pin{
  position:sticky;
  align-self:start;              /* shorter than its area — this is the travel */
  /* Rests --dd-pin-top below the viewport edge instead of flush against it, so
     there is always open background above the cards while they are pinned.
     The offset is subtracted from the travel, never added to the height, or the
     pin would grow past the bottom of its area and stop sticking. */
  top:var(--dd-pin-top);
  display:flex;
  flex-direction:column;
  background:var(--dd-bg);
  /* Pulls the pin up to overlay from the very top of .dd-area-cards
     (hero's position) instead of stacking below .dd-cards__lines in
     normal flow. This used to require main.js to measure the real DOM
     and set it every frame, because rows were content-height and this
     number could only be known after render. Now that every row is a
     fixed, known value, it's the exact same formula as .dd-area-cards'
     own height above — just written directly as CSS instead of being
     computed and written by JS. */
  margin-top:calc(-1 * (var(--dd-row-hero) + var(--dd-row-dir) * 12));
  height:calc(var(--dd-card-lead) + var(--dd-row-dir) * 3);
}
.dd-card--main{ height:calc(var(--dd-card-lead) + var(--dd-row-dir) * 2); flex-shrink:0; }
.dd-card--cta{  height:var(--dd-row-dir); flex-shrink:0; }
.dd-area-idx1{    grid-area:idx1; }
.dd-area-idx2{    grid-area:idx2; }
.dd-area-idx3{    grid-area:idx3; }
.dd-area-idx4{    grid-area:idx4; }
.dd-area-idx5{    grid-area:idx5; }
.dd-area-idx6{    grid-area:idx6; }
.dd-area-idx7{    grid-area:idx7; }
.dd-area-idx8{    grid-area:idx8; }
.dd-area-idx9{    grid-area:idx9; }
.dd-area-idx10{   grid-area:idx10; }
.dd-area-idx11{   grid-area:idx11; }
.dd-area-idx12{   grid-area:idx12; }
.dd-area-rail{    grid-area:rail; border-left:1px solid var(--dd-line); display:flex; flex-direction:column; justify-content:space-between; align-items:center; padding:20px 0; }

/* -------------------------------------------------------------
   HEADER CONTENT
------------------------------------------------------------- */
.dd-logo{ font-size:13px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; }
.dd-header__meta{ display:flex; align-items:center; justify-content:flex-end; gap:20px; }
.dd-est{ font-family:var(--dd-mono); font-size:11px; color:var(--dd-fg-dim); letter-spacing:.03em; }

/* Hamburger menu button — three bars, no border, sized to be an easy
   click/tap target without looking heavy next to the thin est. text. */
.dd-menu-btn{
  display:flex; flex-direction:column; justify-content:center; gap:4px;
  width:28px; height:22px; padding:0; border:none; background:none; cursor:pointer;
}
.dd-menu-btn__bar{
  display:block; height:1px; width:100%; background:var(--dd-fg);
  transition:transform var(--dd-transition), opacity var(--dd-transition);
}
/* Open state morphs the three bars into an X — same three elements,
   no separate icon swap needed. */
.dd-menu-btn[aria-expanded="true"] .dd-menu-btn__bar:nth-child(1){ transform:translateY(5px) rotate(45deg); }
.dd-menu-btn[aria-expanded="true"] .dd-menu-btn__bar:nth-child(2){ opacity:0; }
.dd-menu-btn[aria-expanded="true"] .dd-menu-btn__bar:nth-child(3){ transform:translateY(-5px) rotate(-45deg); }

/* Dropdown panel — fixed overlay below the header, same pattern as
   .dd-gallery: deliberately outside the grid flow since it's a
   transient UI layer, not page content that needs a grid-area. */
.dd-menu-panel{
  position:fixed; top:0; right:var(--dd-pad); z-index:95;
  margin-top:var(--dd-row-nav);
  display:flex; flex-direction:column;
  min-width:180px;
  background:var(--dd-bg);
  border:1px solid var(--dd-line);
  border-top:none;
  opacity:0; transform:translateY(-6px);
  pointer-events:none;
  transition:opacity var(--dd-transition), transform var(--dd-transition);
}
.dd-menu-panel.is-open{ opacity:1; transform:translateY(0); pointer-events:auto; }
.dd-menu-panel__link{
  padding:14px 18px;
  font-family:var(--dd-mono); font-size:12px; letter-spacing:.03em;
  color:var(--dd-fg-dim);
  border-bottom:1px solid var(--dd-line);
  transition:color var(--dd-transition), background var(--dd-transition);
}
.dd-menu-panel__link:last-child{ border-bottom:none; }
.dd-menu-panel__link:hover{ color:var(--dd-fg); background:rgba(9,9,9,0.03); }

/* -------------------------------------------------------------
   HERO
------------------------------------------------------------- */
/* Sized off .dd-count__number, not off the viewport: the heading and the "11"
   share the hero row and the same sans stack, so the same font-size with the
   same line-height:1 makes the two blocks exactly one line box tall. Change
   both together or they stop matching. */
.dd-manifesto{ font-size:56px; line-height:1; font-weight:800; letter-spacing:-0.02em; margin:0 0 22px 0; }
.dd-manifesto__sub{ max-width:520px; font-size:15px; line-height:1.5; color:var(--dd-fg-dim); font-family:var(--dd-mono); margin:0; }
.dd-count__number{ font-size:56px; font-weight:700; line-height:1; }
.dd-count__copy{ font-family:var(--dd-mono); font-size:11px; line-height:1.6; color:var(--dd-fg-dim); margin:4px 0 0 0; max-width:180px; }

/* -------------------------------------------------------------
   DIRECTIVES
   Each directive is an <a> to its own page and doubles as the
   control for the pinned card column. Only colour and transform
   respond to hover, so travelling down the list never reflows a
   cell or nudges the card column.

   Two states read differently on purpose: `is-active` is the
   directive the card is currently showing (it stays lit after the
   pointer leaves, because the card stays on that directive), while
   hover/focus adds the lean and the arrow.
------------------------------------------------------------- */
.dd-directive__head{ display:flex; align-items:baseline; justify-content:space-between; gap:16px; }
.dd-directive__eyebrow{ font-family:var(--dd-mono); font-size:11px; letter-spacing:.05em; color:var(--dd-fg-faint); text-transform:uppercase; transition:color var(--dd-transition); }
.dd-directive__title{ font-size:26px; font-weight:700; letter-spacing:-0.01em; margin:0; color:var(--dd-fg-dim); transition:color var(--dd-transition), transform var(--dd-transition); }
.dd-directive__copy{ font-size:14px; line-height:1.55; color:var(--dd-fg-dim); max-width:380px; margin:0; }
.dd-directive__arrow{
  font-family:var(--dd-mono); font-size:12px; line-height:1; color:var(--dd-fg-faint);
  opacity:0; transition:opacity var(--dd-transition), transform var(--dd-transition), color var(--dd-transition);
}
.dd-root a.dd-directive{ cursor:pointer; }
/* Rows are content-height now, not a shared fixed token — this is the
   floor, not the ceiling. Brands' three-line copy sizes its row well
   past this on its own; a one-line directive (if one ever existed)
   would otherwise collapse to bare text height, which is what this
   prevents. .dd-directive already covers both the eleven directives and
   Brands (Brands carries the same class), so one rule covers all twelve. */
.dd-directive{ min-height:var(--dd-row-dir); scroll-snap-align:start; scroll-margin-top:var(--dd-row-nav); }
.dd-directive.is-active .dd-directive__title{ color:var(--dd-fg); }
.dd-directive.is-active .dd-directive__arrow{ opacity:.55; }
.dd-directive:hover .dd-directive__title,
.dd-directive:focus-visible .dd-directive__title{ color:var(--dd-fg); transform:translateX(6px); }
.dd-directive:hover .dd-directive__eyebrow,
.dd-directive:focus-visible .dd-directive__eyebrow{ color:var(--dd-fg-dim); }
.dd-directive:hover .dd-directive__arrow,
.dd-directive:focus-visible .dd-directive__arrow{ opacity:1; color:var(--dd-fg); transform:translate(3px,-3px); }
/* Same "pop" the pinned card gives its own hovered/focused state — an
   inset ring that brightens within the cell's own edge, not a transform.
   A directive row is a grid cell like the card is, so it has the exact
   same border-registration constraint AGENTS.md calls out for the card:
   scaling or lifting the row itself would pull its border out of line
   with the grid. box-shadow paints inside the existing border without
   moving anything, which is exactly how .dd-card__ring does it too. */
.dd-directive{ box-shadow:inset 0 0 0 1px transparent; transition:box-shadow var(--dd-transition); }
.dd-directive:hover{ box-shadow:inset 0 0 0 2px var(--dd-line-bright); }
.dd-directive:focus-visible{ box-shadow:inset 0 0 0 2px var(--dd-fg); outline:none; }

/* -------------------------------------------------------------
   CARDS
   The whole card is the link, and the card is the frame: no inner
   margins, no inset, no divider between media and caption. The
   only visible border is the cell border on the grid boundary, which
   brightens on hover via a dedicated ring layer (.dd-card__ring below) —
   not the card's own outline or box-shadow, since either of those would
   paint underneath the WebGL canvas whenever a gallery directive's plate
   layer is live, making the ring invisible over the photo.
------------------------------------------------------------- */
.dd-root [data-card]{
  display:grid;
  position:relative;
  cursor:pointer;
}
/* THE HOVER RING — a dedicated layer, not the card's own outline or
   box-shadow. Both of those belong to the card's own background/border paint
   step, which happens BEFORE its positioned descendants — so on directive 01,
   where the WebGL canvas (z-index:2) actually goes live over the media, an
   outline on the card itself was getting painted first and then covered by
   the canvas, invisible everywhere except the caption (which the canvas never
   touches). This layer's z-index:3 is explicitly higher than the canvas's, so
   it always paints on top regardless of which directive or layer is showing.
   inset:0 matches the card's own edge exactly, so it reads as the card's own
   frame, and pointer-events:none keeps it out of the way of hover/click. */
.dd-card__ring{
  position:absolute; inset:0; z-index:3;
  pointer-events:none;
  box-shadow:inset 0 0 0 1px transparent;
  transition:box-shadow var(--dd-transition);
}
.dd-root [data-card]:hover .dd-card__ring{ box-shadow:inset 0 0 0 2px var(--dd-line-bright); }
.dd-root [data-card]:focus-visible .dd-card__ring{ box-shadow:inset 0 0 0 2px var(--dd-fg); }

/* MEDIA — edge to edge, no fade, gradient, vignette or overlay. No inset of
   any kind at the top: the image meets the card's own frame line.

   The media sits on the page background, not a light plate: the directive
   images are cut out against transparency so the page's own black is what
   surrounds the figures. A background of any other colour would print itself
   into every gap in the artwork. Nor is the image filtered — these are colour
   boards and the colour is the content. */
.dd-card__media{ position:relative; overflow:hidden; background:var(--dd-bg); min-height:0; margin:0; padding:0; height:100%; }

/* The media holds two stacked layers, not one image, so a gallery directive's
   plates cross-fade into one another instead of cutting. The incoming layer
   fades in on top while the one it replaces is held at full opacity beneath it
   and only cut away afterwards — that hold is the 0ms-with-a-delay transition
   on the resting rule. Fading the two in opposite directions at the same time
   would let the page background show through the middle of every swap, and
   these plates are cut out against that background.
   main.js only moves .is-showing between the layers; the timing is all here. */
.dd-card__img{
  position:absolute; inset:0;
  width:100%; height:100%; object-fit:contain; display:block;
  opacity:0;
  --dd-img-scale:1;
  transform:scale(var(--dd-img-scale));
  transition:opacity 0ms linear var(--dd-plate-fade),
             transform 900ms cubic-bezier(.16,.8,.24,1);
}
.dd-card__img.is-showing{
  opacity:1; z-index:1;
  transition:opacity var(--dd-plate-fade) ease,
             transform 900ms cubic-bezier(.16,.8,.24,1);
}
/* A change of directive is always an instant, invisible swap on these two
   layers now — main.js sets this while the whole pair is already fully
   opaque/covered by the tube overlay above them (z-index:3), so no cross-fade
   is needed here and none would even be seen. The VISIBLE transition for a
   directive change lives entirely on .dd-card__tube below; these two layers
   are just the resting truth of what the card shows underneath it. */
.dd-card__media.is-cut .dd-card__img{ transition:none; }
.dd-root [data-card]:hover .dd-card__img{ --dd-img-scale:1.03; }

/* DIRECTIVE-CHANGE TUBE
   A directive change is a rotation, not a flat slide or a fade: the card's
   image is sliced into thin horizontal strips (see runTube() in main.js),
   and the outgoing and incoming frames' strips together read as one
   continuous band wrapped around a shared cylinder — the outgoing frame's
   last strip and the incoming frame's first strip sit at adjacent angles on
   that same cylinder, so the seam between them is continuous rather than
   two separate objects crossing paths. Rotating the whole tube by one
   frame's arc brings the incoming set to the front and carries the outgoing
   set around to the back.

   This lives entirely on its own overlay (.dd-card__tube, z-index:3 — above
   both the flat <img> pair and the WebGL plate layer) rather than on the
   card's own resting layers: main.js updates those instantly and invisibly
   underneath (.is-cut, above) the moment a directive changes, and the tube
   is what the eye actually sees for the length of the rotation before
   hiding again to reveal the — by then already-correct — flat layers.

   perspective lives on the tube itself, not on .dd-card__media, since it
   needs to apply to the strips' shared vanishing point, not to the flat
   layers (which no longer rotate at all). Each strip's own transform reads
   --tube-rot from this element, which is what lets one write from main.js
   move every strip at once: the container's custom property inherits down
   to every strip, and each strip's calc() only differs by its own
   --tube-strip-angle. backface-visibility:hidden keeps a strip from
   flashing its mirrored back face as it rotates past edge-on. .is-snap is
   the transition-free instant runTube() uses to place both sets of strips
   at their starting angles without animating the jump there. */
.dd-card__tube{
  position:absolute; inset:0; z-index:3;
  visibility:hidden;
  pointer-events:none;
  background:var(--dd-bg);
  perspective:4000px;
}
.dd-card__tube.is-live{ visibility:visible; }
.dd-card__tube__strip{
  position:absolute; left:0; width:100%;
  background-repeat:no-repeat;
  backface-visibility:hidden;
  transform:rotateX(calc(var(--tube-strip-angle, 0deg) - var(--tube-rot, 0deg))) translateZ(var(--tube-radius, 300px));
  transition:transform var(--dd-slide-transition);
}
.dd-card__tube.is-snap .dd-card__tube__strip{ transition:none; }
@media (prefers-reduced-motion: reduce){
  .dd-card__tube__strip{ transition-duration:1ms; }
  /* Not every browser reliably auto-disables scroll-behavior:smooth for
     reduced-motion on its own — set explicitly rather than assume.
     scroll-snap-type stays on: the block-to-block landing itself isn't
     motion, only the glide between blocks was, and that's what this
     turns off. */
  html{ scroll-behavior:auto; }
}

/* WEBGL PLATE LAYER
   A third layer over the two above, holding the canvas the displacement effect
   draws into (see the vendored hover-effect in /vendor and the plate layer in
   main.js). It melts one plate into the next along a noise map instead of
   cross-fading them, and it exists only where that can be seen: main.js
   creates it for a fine hovering pointer on a viewport wide enough for the
   card to be pinned, with motion left alone and WebGL available.

   It is layered over the <img> pair rather than replacing it, so everything
   below stays the truth of what the card shows and the fallback is simply this
   layer not being there. Both draw the same plate, cut the same way, on the
   same background — which is what makes the layer safe to reveal and drop.
   Hidden with visibility rather than display: the effect measures this box to
   size its camera and geometry, so the box has to keep its layout even while
   nothing is drawn into it. */
.dd-card__gl{
  position:absolute; inset:0; z-index:2;
  visibility:hidden;
  pointer-events:none;
  /* The same lift as the plate underneath, so the hover scale is one movement
     rather than two layers disagreeing. */
  transition:transform 900ms cubic-bezier(.16,.8,.24,1);
}
.dd-card__gl.is-live{ visibility:visible; }
/* The effect writes the canvas size inline, once, from the box it was built
   with; this keeps it covering the media between a resize and the rebuild
   main.js does in response. */
.dd-card__gl canvas{ display:block; width:100% !important; height:100% !important; }
.dd-root [data-card]:hover .dd-card__gl{ transform:scale(1.03); }
/* While the melt is on screen the <img> pair beneath it is only the state of
   the card, not the picture anyone is looking at — so it changes frame with no
   animation at all rather than cross-fading behind an opaque canvas. */
.dd-card__media.is-gl{ --dd-plate-fade:0ms; }

/* CAPTION — full card width, compact, no divider rule */
.dd-card__caption{
  display:grid;
  grid-template-columns:1fr auto;
  grid-template-areas:
    "name arrow"
    "meta arrow";
  align-content:center;
  align-items:center;
  row-gap:3px;
  column-gap:12px;
  padding:12px var(--dd-pad) 14px;
  min-width:0;
}
.dd-card__caption--bottom{ align-self:end; }
.dd-card__caption--top{ align-self:start; }
.dd-card__name{ grid-area:name; font-size:16px; font-weight:700; line-height:1.15; margin:0; }
.dd-card__meta{ grid-area:meta; display:flex; align-items:baseline; gap:8px; }
.dd-card__meta--stack{ flex-direction:column; align-items:flex-start; gap:3px; }
.dd-card__tags{ font-family:var(--dd-mono); font-size:10px; line-height:1.3; color:var(--dd-fg-dim); margin:0; }
.dd-card__index{ font-family:var(--dd-mono); font-size:10px; line-height:1.3; color:var(--dd-fg-faint); }
.dd-card__arrow{
  grid-area:arrow; justify-self:end; align-self:center;
  font-family:var(--dd-mono); font-size:12px; line-height:1; color:var(--dd-fg-faint);
  transition:transform var(--dd-transition), color var(--dd-transition);
}
.dd-root [data-card]:hover .dd-card__arrow{ transform:translate(4px,-4px); color:var(--dd-fg); }

/* TEXT PLATE (Directive 11 — Retail Buyer Introductions)
   Sits over the (blank) photo layers inside .dd-card__media. Each line
   starts pushed 160px right and invisible; .is-active pulls every line
   back to place, but not together — each line's own --dd-line-i (set
   inline in the markup) becomes a transition-delay via calc(), so
   SHOWROOMS moves first with no pause at all (--dd-line-i:0), then the
   rule, then the four cities land one after another, 320ms apart, each
   taking 1400ms to arrive. main.js only ever toggles .is-active; the
   sequencing is entirely this delay math, the same "CSS owns the
   timing" split the tube and the caption fade both use. Sizing matches
   .dd-manifesto — same weight, tracking and scale as the page's own
   MANIFESTO line. */
.dd-card__textplate{
  position:absolute; inset:0; z-index:2;
  display:flex; flex-direction:column; justify-content:flex-start;
  padding:44px clamp(24px,6%,56px) 0;
  pointer-events:none;
}
.dd-card__tline{
  display:block; color:var(--dd-fg);
  font-family:var(--dd-sans); font-weight:800; letter-spacing:-0.02em;
  font-size:clamp(28px,4.6vw,56px); line-height:1.15; margin:0 0 10px 0;
  transform:translateX(calc(100% + 24px)); opacity:0;
  transition:transform 1700ms cubic-bezier(.16,1,.3,1), opacity 1700ms ease;
  /* A shared 400ms pause before anything moves, then 380ms between each
     line starting — SHOWROOMS gets the same wait-then-glide treatment as
     every city instead of jumping immediately, so it doesn't read as a
     different kind of motion from the rest of the sequence. */
  transition-delay:calc(400ms + var(--dd-line-i, 0) * 380ms);
}
.dd-card__textplate.is-active .dd-card__tline{ transform:translateX(0); opacity:1; }
.dd-card__tline--rule{
  height:1px; font-size:0; line-height:0;
  background:var(--dd-fg); width:100%; margin:0 0 22px 0;
}
@media (prefers-reduced-motion: reduce){
  .dd-card__tline{ transition-duration:1ms; transition-delay:0ms; }
}

/* HOVER SWAP (caption)
   When a different directive takes over, the caption cross-fades rather than
   cutting: title, category and index carry .dd-card__fade and fade as one
   group, so the words are never briefly a mix of two projects. main.js drops
   opacity to 0, rewrites the text while it is invisible, then lets it come
   back; the 240ms here is the value it waits for, so the two must be changed
   together. The media is deliberately NOT in this group any more — it has
   its own rotating-tube transition instead (see .dd-card__tube above),
   so the image and the caption change at the same moment but never the same
   way. Opacity is the only animated property here, and it is animated on the
   caption rather than on the card itself so the pinned frame and its border
   lines never flicker. */
.dd-card__fade{ transition:opacity 240ms ease; }
.dd-card--main.is-swapping .dd-card__fade{ opacity:0; }
@media (prefers-reduced-motion: reduce){
  .dd-card__fade{ transition-duration:1ms; }
  /* Zeroing the one token turns the plate cross-fade into a plain change of
     image — the layers still alternate, they just no longer animate. */
  .dd-card__media{ --dd-plate-fade:0ms; }
}

/* -------------------------------------------------------------
   INDEX COLUMN (01–11)
   The number opposite the directive currently in the card column
   lights up with it, tying the two columns together. Selected on
   the data attribute rather than on eleven dd-area-idx* classes:
   those carry position only, this carries appearance.
------------------------------------------------------------- */
.dd-root [data-index-cell]{
  font-family:var(--dd-mono); font-size:11px; color:var(--dd-fg-faint); padding-top:20px;
  transition:color var(--dd-transition);
}
.dd-root [data-index-cell].is-active{
  color:var(--dd-fg);
}

/* -------------------------------------------------------------
   FOOTER
------------------------------------------------------------- */

/* -------------------------------------------------------------
   RAIL
------------------------------------------------------------- */
.dd-rail__label{
  writing-mode:vertical-rl; transform:rotate(180deg);
  font-family:var(--dd-mono); font-size:10px; letter-spacing:.08em;
  color:var(--dd-fg-faint); white-space:nowrap; text-transform:uppercase;
}
/* The rail itself spans the full page height and isn't sticky — only its
   two labels are anchored (top/bottom) via the flex space-between on
   .dd-area-rail. That means the top label sits at the very top of the
   page and scrolls out of view like any other content the moment you're
   past it, which reads as the whole rail having disappeared, since the
   border-left line alone doesn't carry the word "Directives". Sticking
   just this label — same top offset as the header and the pinned card —
   keeps it on screen for as long as the rail column itself is in view. */
.dd-rail__label--top{ position:sticky; top:var(--dd-pin-top); }

/* -------------------------------------------------------------
   COMING SOON PAGE STYLE (for /directives /work /about)
------------------------------------------------------------- */
.dd-coming-soon{
  background:#fff; color:#111; min-height:100vh;
  display:flex; align-items:center; justify-content:center;
  font-family:"Helvetica Neue",Arial,sans-serif;
  font-size:clamp(32px,6vw,64px); font-weight:800; letter-spacing:-0.02em;
}

/* -------------------------------------------------------------
   INNER PAGES (/directives, /directives/<slug>, /work/<project>)
   The destination every directive and the card column point at.
   These pages put .dd-root on <body> so they inherit the same
   tokens, borders and type as the landing page — arriving from a
   card should not feel like leaving the site. Layout is one grid
   with a named area map, as everywhere else.
------------------------------------------------------------- */
.dd-project{
  min-height:100vh;
  display:grid;
  grid-template-columns:1fr;
  grid-template-rows:var(--dd-row-nav) 1fr auto;
  grid-template-areas:
    "pnav"
    "pbody"
    "pfoot";
}
.dd-project__nav{ grid-area:pnav; display:flex; align-items:center; justify-content:space-between; gap:20px; padding:0 var(--dd-pad); border-bottom:1px solid var(--dd-line); }
.dd-project__body{ grid-area:pbody; display:flex; flex-direction:column; justify-content:center; gap:20px; padding:64px var(--dd-pad); }
.dd-project__eyebrow{ font-family:var(--dd-mono); font-size:11px; letter-spacing:.05em; text-transform:uppercase; color:var(--dd-fg-faint); }
.dd-project__title{ font-size:clamp(40px,8vw,92px); font-weight:800; letter-spacing:-0.02em; line-height:.95; margin:0; }
.dd-project__meta{ display:flex; flex-wrap:wrap; gap:16px; font-family:var(--dd-mono); font-size:11px; letter-spacing:.03em; color:var(--dd-fg-dim); }
.dd-project__note{ font-family:var(--dd-mono); font-size:12px; line-height:1.6; color:var(--dd-fg-dim); max-width:420px; margin:0; }
.dd-project__foot{ grid-area:pfoot; border-top:1px solid var(--dd-line); padding:22px var(--dd-pad); display:flex; justify-content:space-between; gap:20px; }
.dd-project__foot a,
.dd-project__nav a{ font-family:var(--dd-mono); font-size:12px; color:var(--dd-fg-dim); transition:color var(--dd-transition); }
.dd-project__foot a:hover,
.dd-project__nav a:hover{ color:var(--dd-fg); }
.dd-project__nav .dd-logo{ font-family:var(--dd-sans); font-size:13px; color:var(--dd-fg); }

/* The directives index (/directives) reuses the same page shell, but a
   list of eleven runs past the fold, so it starts at the top instead of
   sitting centred. Each row is one line of the same specimen table as the
   landing page: number, name, discipline. */
.dd-project__body--list{ justify-content:flex-start; }
.dd-index-list{ display:grid; border-top:1px solid var(--dd-line); margin-top:12px; }
.dd-index-list a{
  display:grid; grid-template-columns:44px 1fr auto; gap:24px; align-items:baseline;
  padding:22px 0; border-bottom:1px solid var(--dd-line);
}
.dd-index-list__num{ font-family:var(--dd-mono); font-size:11px; color:var(--dd-fg-faint); transition:color var(--dd-transition); }
.dd-index-list__name{ font-size:20px; font-weight:700; letter-spacing:-0.01em; color:var(--dd-fg-dim); transition:color var(--dd-transition), transform var(--dd-transition); }
.dd-index-list__cat{ font-family:var(--dd-mono); font-size:10px; color:var(--dd-fg-faint); text-align:right; }
.dd-index-list a:hover .dd-index-list__name{ color:var(--dd-fg); transform:translateX(6px); }
.dd-index-list a:hover .dd-index-list__num{ color:var(--dd-fg-dim); }
@media (max-width:640px){
  .dd-index-list a{ grid-template-columns:32px 1fr; gap:14px; }
  .dd-index-list__cat{ grid-column:2; text-align:left; }
  .dd-index-list__name{ font-size:17px; }
}

/* -------------------------------------------------------------
   FADE-IN ON LOAD/SCROLL
------------------------------------------------------------- */
.dd-fade{ opacity:0; transform:translateY(10px); transition:opacity 700ms ease, transform 700ms ease; }

.dd-fade.is-visible{ opacity:1; transform:translateY(0); }

/* -------------------------------------------------------------
   CUSTOM CURSOR
   Like the mobile nav, this is a fixed overlay that deliberately
   sits outside the grid flow — it never participates in the
   .dd-grid area map. It stays hidden until main.js confirms a
   fine pointer and sets .dd-cursor-on on <html>, so touch
   devices keep their native behaviour untouched.

   Position is written by JS (translate3d on .dd-cursor__dot and
   .dd-cursor__ring); every state change — expand, colour — is CSS
   on .dd-cursor__shape, so the two never fight over the
   same property.
------------------------------------------------------------- */
.dd-cursor{ display:none; }
html.dd-cursor-on{ cursor:none; }
html.dd-cursor-on .dd-root *{ cursor:none; }
html.dd-cursor-on .dd-cursor{ display:block; }

/* The dot and ring are each fixed in their own right rather than
   living in a fixed wrapper: a fixed ancestor would form a
   stacking context, which both isolates the difference blend from
   the page and drops the cursor underneath the transformed
   portfolio cards. */
.dd-cursor__dot,
.dd-cursor__ring{
  position:fixed; top:0; left:0; z-index:100; pointer-events:none;
  opacity:0; transition:opacity 240ms ease;
  will-change:transform;
}
/* Opacity lives on the children, not the wrapper: a parent below
   full opacity would isolate the group and kill the blend below. */
.dd-cursor.is-active .dd-cursor__dot,
.dd-cursor.is-active .dd-cursor__ring{ opacity:1; }

.dd-cursor__dot{
  width:6px; height:6px; margin:-3px 0 0 -3px;
  border-radius:50%; background:var(--dd-fg);
  mix-blend-mode:difference;
}

.dd-cursor__shape{
  display:flex; align-items:center; justify-content:center;
  width:34px; height:34px; margin:-17px 0 0 -17px;
  border:1px solid var(--dd-fg); border-radius:50%;
  transition:background-color 300ms ease, border-color 300ms ease;
}
.dd-cursor__ring{ mix-blend-mode:difference; }
.dd-cursor.is-hover .dd-cursor__shape{ background:rgba(244,244,242,0.10); }

@media (prefers-reduced-motion: reduce){
  .dd-cursor__dot,
  .dd-cursor__ring,
  .dd-cursor__shape{ transition-duration:1ms; }
}

/* -------------------------------------------------------------
   GALLERY OVERLAY
   Opened by a directive card that has a gallery instead of
   following its href. It is deliberately NOT part of .dd-grid:
   it is a fixed sheet over the whole viewport, so it is the one
   place in the page that is positioned rather than placed in a
   cell. Everything inside it is still a grid — stage above,
   thumbnail strip below — and it keeps the page's hairlines and
   mono type so it reads as the same specimen.

   It sits just under the custom cursor's z-index (100) so the
   cursor still draws over it.

   Clicks anywhere that is not an image or a thumbnail close it;
   main.js decides that from the event target, which is why the
   backdrop is this element itself rather than a separate layer
   that would have to be kept on top.
------------------------------------------------------------- */
.dd-gallery{
  position:fixed; inset:0; z-index:90;
  display:grid;
  background:rgba(255,255,255,0.96);
  opacity:0;
  transition:opacity 220ms ease;
}
.dd-gallery[hidden]{ display:none; }
.dd-gallery.is-open{ opacity:1; }

.dd-gallery__inner{
  display:grid;
  grid-template-rows:1fr auto;
  gap:20px;
  width:100%; height:100%;
  padding:64px var(--dd-pad) var(--dd-pad);
  min-height:0;
}

/* STAGE — the large frame. The image is contained, never cropped:
   the overlay is where the whole picture is finally visible, unlike
   the card, which covers its cell. */
.dd-gallery__stage{
  display:grid; place-items:center;
  min-height:0; min-width:0;
}
.dd-gallery__img{
  display:block;
  max-width:100%; max-height:100%;
  width:auto; height:auto;
  border:1px solid var(--dd-line);
  opacity:0;
  transition:opacity 200ms ease;
}
.dd-gallery__img.is-loaded{ opacity:1; }

/* THUMBS — one per image, centred under the stage. */
.dd-gallery__thumbs{
  display:flex; justify-content:center; align-items:flex-end;
  gap:12px;
  min-height:0;
}
.dd-gallery__thumb{
  display:block;
  width:64px; height:80px;
  padding:0; margin:0;
  background:none;
  border:1px solid var(--dd-line);
  cursor:pointer;
  opacity:0.5;
  transition:opacity var(--dd-transition), border-color var(--dd-transition);
}
.dd-gallery__thumb:hover,
.dd-gallery__thumb:focus-visible{ opacity:0.85; border-color:var(--dd-line-bright); }
.dd-gallery__thumb.is-active{ opacity:1; border-color:var(--dd-fg); }
.dd-gallery__thumb-img{ width:100%; height:100%; object-fit:cover; display:block; }

/* CLOSE — top right, on the same hairline grammar as the rail labels. */
.dd-gallery__close{
  position:absolute; top:18px; right:var(--dd-pad);
  width:40px; height:40px;
  display:grid; place-items:center;
  padding:0;
  background:none;
  border:1px solid var(--dd-line);
  color:var(--dd-fg);
  font-family:var(--dd-mono); font-size:20px; line-height:1;
  cursor:pointer;
  transition:border-color var(--dd-transition), color var(--dd-transition);
}
.dd-gallery__close:hover,
.dd-gallery__close:focus-visible{ border-color:var(--dd-fg); }

@media (prefers-reduced-motion: reduce){
  .dd-gallery,
  .dd-gallery__img{ transition-duration:1ms; }
}

@media (max-width:640px){
  .dd-gallery__inner{ padding:56px 16px 16px; gap:14px; }
  .dd-gallery__close{ top:12px; right:16px; }
  .dd-gallery__thumb{ width:52px; height:66px; }
}

/* While the overlay is open the page underneath must not scroll. main.js
   also sets body{overflow:hidden} inline, which is the flag the smooth
   scroll loop already checks before easing the wheel. */
html.dd-scroll-lock{ overflow:hidden; }

/* ===================================================================
   RESPONSIVE
   Each breakpoint redefines the SAME grid — new
   grid-template-columns, grid-template-rows, and
   grid-template-areas — rather than overriding individual
   elements' positions. The area names stay identical, so no
   element needs its grid-area reassigned per breakpoint.
=================================================================== */

/* Laptop: same 4-column structure, tighter tracks. The directive title
   steps down too — the column narrows faster than the row grows, and a
   title that wrapped to two lines would push its cell past the fixed
   row height every other row has to share. MANIFESTO needs no override
   here: at 56px the single word stays inside the directives column at
   every width, so it can never spill over the card column. */
@media (max-width:1280px){
  .dd-root{
    --dd-pad:28px;
    --dd-row-hero:190px;
  }
  .dd-directive__title{ font-size:22px; }
}

/* Short viewports: the pinned column (card + CTA) is taller than the gap
   left for it once the header takes its fixed slice off the top. Since the
   header is sticky now, --dd-pin-top can never shrink below --dd-row-nav
   without the pin sliding in underneath it — so this reclaims the room by
   shrinking the header itself instead. --dd-pin-top is defined as
   var(--dd-row-nav), so it follows automatically; nothing else needs to
   change for the pin to stay flush against the header's new, shorter edge. */
@media (max-height:800px){
  .dd-root{ --dd-row-nav:56px; }
}

/* Wide-but-short viewports (a resized browser window, a small popup) can hit
   this without ever crossing the ≤960px width breakpoint that normally zeroes
   --dd-card-lead. Below 600px of height, drop it here too, for the same
   reason: it hands the --dd-row-dir formula above its full budget instead of
   competing with it, which matters more once there's this little height to
   work with. */
@media (max-height:600px){
  .dd-root{ --dd-card-lead:0px; }
}

/* Tablet: collapse to 3 columns, drop the index column,
   index numbers hidden, rail still spans full height.
   Rows grow: the directives column is narrower here, so the longest
   copy takes an extra line and every row has to allow for it. */
@media (max-width:960px){
  .dd-root{
    --dd-row-hero:180px;
    /* Larger than desktop's 190px fixed value: the directives column is
       narrower here, so the same three-sentence copy needs an extra
       line to fit. Fixed, not a viewport clamp, for the same reason as
       the desktop value — see that declaration for the full reasoning. */
    --dd-row-dir:250px;
    /* The count block, not the cards, sits beside the hero here, so the card
       column starts at the first directive row and has no lead to fill. */
    --dd-card-lead:0px;
  }

  .dd-grid{
    grid-template-columns:
      1fr
      1fr
      var(--dd-col-rail);
    grid-template-rows:
      var(--dd-row-nav)
      var(--dd-row-hero)
      repeat(12, var(--dd-row-dir));
    grid-template-areas:
      "nav    nav    rail"
      "hero   count  rail"
      "dir1   cards  rail"
      "dir2   cards  rail"
      "dir3   cards  rail"
      "dir4   cards  rail"
      "dir5   cards  rail"
      "dir6   cards  rail"
      "dir7   cards  rail"
      "dir8   cards  rail"
      "dir9   cards  rail"
      "dir10  cards  rail"
      "dir11  cards  rail"
      "brands cards  rail";
  }
  .dd-root [data-index-cell]{ display:none; }
  .dd-directive__copy{ font-size:13px; }
}

/* Mobile: single column, rail moves to a horizontal strip at
   the top of the flow via its own grid row */
@media (max-width:640px){
  .dd-grid{
    grid-template-columns: 1fr;
    grid-template-rows:
      var(--dd-row-nav)
      36px
      repeat(16, auto);
    grid-template-areas:
      "nav"
      "rail"
      "hero"
      "count"
      "dir1"
      "card1"
      "dir2"
      "dir3"
      "dir4"
      "dir5"
      "dir6"
      "card4"
      "dir7"
      "dir8"
      "dir9"
      "dir10"
      "dir11"
      "brands";
  }
  .dd-area-rail{
    flex-direction:row; justify-content:space-between;
    border-left:none; border-top:1px solid var(--dd-line); border-bottom:1px solid var(--dd-line);
    padding:10px var(--dd-pad);
  }
  .dd-rail__label{ writing-mode:horizontal-tb; transform:none; }
  .dd-manifesto{ font-size:clamp(40px,14vw,64px); }
  .dd-root [data-index-cell]{ display:none; }

  /* The hero cards return to the single-column document flow on mobile, so
     they never cover the directive rows beneath them on a short viewport. */
  .dd-area-hero{ grid-area:hero; pointer-events:auto; z-index:auto; }
  .dd-area-count{ grid-area:count; pointer-events:auto; z-index:auto; }
  .dd-hero-card,
  .dd-count-card{ position:static; height:auto; }

  /* Single column: nothing scrolls past the cards, so the pin is dropped
     entirely. The column wrapper and the pin both go display:contents so the
     two cards become direct grid items again and keep their own rows in the
     stack, and the column's line cells are no longer needed. */
  .dd-area-cards, .dd-cards__pin{ display:contents; }
  .dd-cards__pin{ position:static; }
  .dd-cards__lines{ display:none; }

  /* Card rows are auto-height here, so the 70% media track has nothing to
     resolve against — the media keeps its dominance via an aspect ratio. */
  .dd-card--main{ grid-area:card1; grid-template-rows:auto auto; }
  .dd-card--cta{  grid-area:card4; min-height:150px; }
  .dd-card__media{ aspect-ratio:3/2; height:auto; }
}
