/* ═══ /design ═════════════════════════════════════════════════════════════════
   The page is an EXHIBIT, so everything in here is in service of one rule: the
   artwork is the content and the chrome around it has to disappear. It rides on
   work.css's .item / .item__lead / .tags idiom rather than inventing a second
   section system — /kova and /sipful already make this move and a third grid
   would drift from those two the first time any of them changed.

   ⚠️ THE ONE THING THAT WILL BITE. work.css caps every direct child of
   `#rail > .item` at 62ch:

       #rail > .item > *:not(.item__lead):not(.rig):not(.lead__grid):not(.vid)…

   A media block that is not in that exclusion list is silently squeezed to a
   text measure — it still renders, it is just wrong, which is the failure mode
   this repo keeps paying for. `.dgrid` is added to that list IN work.css, beside
   the others, rather than fought with a specificity tie from this file. If you
   add a new wide container here, add it there too.
   ------------------------------------------------------------------------- */

/* ─── THE LEAD PAIR ───────────────────────────────────────────────────────────
   Two lids, not one. A single lid reads as "a lid"; the pair reads as a line
   with a system behind it, which is what the section is about. They overlap
   slightly so they read as one object rather than two thumbnails in a row. */
.dlids {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 1.4vw, 16px);
}
/* 210px read as thumbnails of a product rather than as the product. The lead
   artifact is the reason the page exists, so it takes the width the lead__grid
   already has spare — measured, not guessed, at 390/768/1032/1440/1920. */
.dlids picture { display: block; width: min(44vw, 320px); }
/* 🟢 THE LIDS ARE MASKED TO CIRCLES, AND THIS CORRECTS THE NOTE THAT USED TO SIT
   HERE. That note said `border-radius: 50%` "cut the corners off the 30G PROTEIN
   and 200 CALORIES badges" and concluded "a round DIE does not make the ARTWORK
   round." **Measured Sep 2 2026 and it is not true of these files.** A circular
   alpha mask was applied to lid-vanilla with ffmpeg and composited over grey to
   look at: both badges are whole, the wordmark is whole, and the result matches
   `die-proof-1200.png` — the printer's own 3.5in round proof — almost exactly.
   The only thing the circle removes is the bear's body running off the bottom,
   which the real lid also cuts.
   ⇒ The artwork IS composed for the circle. The badges sit at about a 6% inset
   and clear the inscribed circle; only the extreme square corners fall outside,
   and those are flat field colour.
   ⚠️ The pixel count alone said the opposite and would have talked me out of it:
   46% of the outside-the-circle area reads as "not the corner colour", because
   the lid is TWO-TONE and the whole cream lower half differs from the brown
   top-left corner I sampled. **A background test that assumes one background
   colour cannot measure a two-tone asset.** Looking at it settled in one image
   what the statistic got backwards — which is what the old note got right about
   method even though it got the answer wrong. */
.dlids img {
  display: block; width: 100%; height: auto;
  border-radius: 50%;
  box-shadow: 0 18px 44px rgba(0, 0, 0, .5);
}

/* ─── THE MEDIA GRIDS ─────────────────────────────────────────────────────────
   One container class with modifiers, so the 62ch exclusion in work.css needs a
   single entry rather than one per layout.

   ⚠️ minmax(0, 1fr) rather than 1fr: a grid track's default `min-width: auto`
   is the content's intrinsic size, so a wide image refuses to shrink and pushes
   the track past the container instead of scaling down inside it. This file's
   first draft used 1fr and every three-up row overflowed on a phone. */
.dgrid {
  display: grid;
  gap: clamp(12px, 1.6vw, 22px);
  margin: 0 0 20px;
  width: 100%;
}
.dgrid--2     { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 1000px; }
/* ⚠️ 920, NOT 1000. The section index is fixed to the right edge, and at an iPad's
   1032px a 1000px grid put its last caption's right edge at 960 against a rail
   starting at 971 — an 11px gap. Not an overlap, so nothing measured as broken,
   but it reads as text crowding the rail. Measured, then widened to ~90px. */
.dgrid--3     { grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 1200px; }
.dgrid--wide  { grid-template-columns: minmax(0, 1fr);            max-width: 860px; }
.dgrid--phone { grid-template-columns: minmax(0, 1fr);            max-width: 340px; }
/* a PORTRAIT photograph needs a narrower cap than a landscape one, or the same
   860px `--wide` box makes a 3:4 frame 1,147px tall and the section becomes one
   picture. 560 gives a 747px frame, which reads as a plate rather than a wall. */
.dgrid--portrait { grid-template-columns: minmax(0, 1fr);            max-width: 560px; }
/* A LOGO SUITE whose cells are baked into the ASSETS rather than drawn in CSS.
   The Kova marks needed `.dmark` cells because their sources carry no common
   frame. Onword's four states were recomposited onto identical canvases with
   matched mark height and equal clear space, so the artwork already IS the cell
   — and adding a `.dmark` border would pad an already-padded frame twice. Plain
   figures at natural aspect: the plate on `.dfig img` hugs the canvas exactly. */
.dgrid--suite { grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: 1100px; }
@media (max-width: 760px) { .dgrid--suite { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* 🟢 `.dgrid--tight` IS GONE (Sep 3 2026), AND THE ASSETS ARE WHY.
   It existed because the Onword pair was 678x210 and 232x202 native: a common box
   with `object-fit: contain` was the only way to stop them reading as a big
   picture and a small one. But that box was scaling them by DIFFERENT amounts —
   measured in the shipped layout, lines rendered at 400x124 (0.59x) against blocks
   at 218x190 (0.94x), so the SAME three list items were 1.6x bigger in one tile
   than in the other. The modifier was hiding a defect it also caused.
   ⇒ Both sources are now recomposited onto IDENTICAL 730x254 canvases on the
   sampled #191919 field, registered on the bullets rather than the crop edges —
   see tools/design-assets.sh, which carries the measurements. Identical canvases
   mean identical scale, so the pair is now an ordinary two-up row: natural aspect,
   `width: 100%`, and it BLEEDS to the card edge like every other contained row.
   ⚠️ DO NOT REINTRODUCE A COMMON-BOX MODIFIER HERE. If the pair ever looks
   mismatched again, the assets drifted — fix them, not the CSS. That is the
   third time on this page that a sizing modifier turned out to be an asset job
   (Onword's four mark states, the Sipful icon, this). */

/* ─── A ROW OF MIXED ASPECTS ──────────────────────────────────────────────────
   The three rejected frames are two 16:9 landscapes and one 768x1376 portrait.
   Left to stretch, the portrait was nearly twice the height of its neighbours,
   so two captions sat stranded halfway up the row and the third hung far below
   them — the row stopped reading as three comparable items, which is the only
   reason they are side by side.

   ⚠️ `object-fit: cover` is WRONG here even though it would tidy the row
   perfectly. #201's entire point is the headline typed across its top —
   "Delivered free,." with the stray comma — and cover on a portrait in a
   landscape box crops exactly that off. The defect would be invisible in the
   picture the caption is describing. `contain` letterboxes instead, and on this
   background the letterboxing is not visible anyway. */
.dgrid--mixed .dfig { display: flex; flex-direction: column; }
.dgrid--mixed .dfig picture { display: block; }
/* 🔴 `width: auto`, NOT `width: 100%` — THIS IS THE EMPTY-SPACE BUG.
   Juan, Sep 2 2026: the containers "sometimes just have empty space that is
   noticeable ... I just don't think it looks clean." He was right, and the cause
   is the pair of properties rather than either one. `.dfig img` carries a plate
   (`background` + a 1px hairline) so photographs do not float on the phosphor
   with a hard edge. Give that img a full-width box and a fixed height and
   `object-fit: contain` letterboxes the artwork INSIDE the plate — so the plate
   is visible as a lit rectangle around an opaque image, which is exactly the
   Sipful icon (a 1:1 mark in a 489px-wide box) and /design's rejected-frames row
   (a portrait between two landscapes).
   ⇒ With `width: auto` and a definite height, the img's own aspect sets its
   width, so the plate and the hairline hug the artwork and the leftover track is
   page background — invisible. Rows still share a baseline, captions still align.
   ⚠️ A DEFINITE HEIGHT IS LOAD-BEARING. The note on `--tight` below records what
   happens with `max-height` + `width: auto` instead: both dimensions auto throws
   away the aspect-ratio box the width/height attributes reserve, and the row
   collapsed to 25px. Height definite, width auto — never both auto. */
.dgrid--mixed .dfig img {
  height: clamp(260px, 26vw, 380px);
  width: auto; max-width: 100%; margin-inline: auto;
  object-fit: contain; object-position: center top;
  background: rgba(220, 230, 221, .03);
}
.dgrid--mixed .dfig figcaption { margin-top: 0; }

/* ⚠️ A ROW SIZED BY ITS SMALLEST SOURCE, not by the space available.
   `--mixed` gives a row a uniform 260-380px height so members of different aspects
   share a baseline. That is right until one member's only raster is small:
   sipful-mark is 330x330 and nothing larger exists outside the .afdesign file, so
   the standard row renders it at 374 and a flat vector mark upscaled 13% reads
   soft — on a page whose subject is that mark. This caps the row under the
   smallest native asset in it.
   🔴 IT MUST STAY BELOW THE `--mixed` RULE ABOVE. `.dgrid--mixed .dfig img` and
   `.dgrid--mixed.dgrid--cap .dfig img` are (0,3,1) and (0,4,1)... the second wins
   on specificity, but the pattern that bit .dtile twice is exactly this shape, so
   the order is deliberate rather than incidental. */
.dgrid--mixed.dgrid--cap .dfig img { height: clamp(190px, 19vw, 300px); }

/* ⚠️ AND A 3-UP NEEDS A SHORTER ROW THAN A 2-UP, or the empty space comes back
   ROTATED 90 DEGREES. `width: auto` only hugs the artwork while the width it
   computes still fits the track; once `max-width: 100%` binds, `contain`
   letterboxes vertically instead and you are back where you started. Measured on
   /design at 1440: track 385px, widest member 1.786:1, so the row cannot exceed
   385 / 1.786 = 215px — and the shared `--mixed` height was 374. The bound is
   (track width / widest aspect in the row), and because the container caps at
   1200px the track caps too, so a px ceiling is the honest form. */
.dgrid--3.dgrid--mixed .dfig img { height: clamp(120px, 13.8vw, 205px); }

/* ─── THE REJECTED FRAMES: THE ONE PLACE UNEQUAL CELLS ARE RIGHT ──────────────
   🟢 ADDED Sep 3 2026. Juan asked for two things about this row and they turn
   out to be one change: he wanted it to BLEED like the other card rows, and he
   wanted the VERTICAL reject to span two rows against the two horizontal ones.

   ⚠️ It could not bleed as `--mixed`, and that was not a CSS bug. `--mixed` sizes
   its images with `width: auto` so they HUG their own artwork inside the track —
   a hugging image has no edge to bleed to, which is exactly why the twenty-seventh
   pass listed this row as one of the three legitimate insets. The way out is not
   to force the bleed, it is to stop needing the hug: give each frame a cell of its
   own aspect and the artwork fills it.

   🟢 AND THAT IS WHY THE BENTO IS RIGHT HERE SPECIFICALLY. The settled rule is
   that bento LOSES for comparison rows, because unequal tiles break a comparison —
   and it holds, because in every other row on this page the members share an
   aspect and unequal cells would render the same kind of thing at two sizes. This
   row is the exception the rule implies: two 2048x1152 landscapes and one 768x1376
   portrait, three genuinely different shapes. There is no equal-cell layout that
   does not either letterbox the portrait or crop it. The cells are unequal because
   THE ARTWORK IS.

     ┌─────────────┐───────┐
     │   307  16:9   │       │
     ├─────────────┤  201  │   two rows, one column each
     │   292  16:9   │ 9:16  │
     └─────────────┴───────┘

   🔴 THE COLUMN RATIO IS DERIVED, NOT PICKED. Both columns have to come out the
   same height or the span is decorative. Solving it for a 1100px container with a
   16px gap: left height = 2(0.5625 W1) + 2 captions + gap; right height =
   1.7917 W2 + 1 caption. Setting them equal with W1 + W2 = 1084 gives W2 = 432 and
   W1 = 652, a ratio of 1.51 : 1 ⇒ `1.5fr 1fr`. Checked at those widths: left 871px,
   right 877px. The 6px is caption reflow and is invisible.

   🔴 `object-fit: cover` IS SAFE HERE AND IS NOT SAFE ONE BLOCK UP. The `--mixed`
   note bans cover because a portrait in a LANDSCAPE box loses the headline across
   #201's top — "Delivered free,." with the stray comma — which is the entire defect
   the caption describes. Here the cell is PORTRAIT and its aspect was derived to
   match the source, so cover crops a few pixels at most, and `object-position:
   center top` puts every one of them at the BOTTOM, which is scoops. The headline
   cannot be reached. ⚠️ If the caption lengths ever change enough to move the
   column heights, re-derive the ratio — do not switch to `contain`, which puts a
   dark letterbox against this frame's cream field. */
.dgrid--rejects { grid-template-columns: 1.5fr 1fr; max-width: 1100px; }
.dgrid--rejects .dfig { display: flex; flex-direction: column; }
.dgrid--rejects .dfig:nth-child(1) { grid-column: 1; grid-row: 1; }
.dgrid--rejects .dfig:nth-child(2) { grid-column: 1; grid-row: 2; }
.dgrid--rejects .dfig:nth-child(3) { grid-column: 2; grid-row: 1 / span 2; }
/* the two landscapes get their own aspect, so cover is a no-op on them */
.dgrid--rejects .dfig:nth-child(-n+2) picture { display: block; aspect-ratio: 2048 / 1152; }
/* the portrait takes whatever height the two rows leave, hence flex rather than a
   ratio: its cell height is set by the column beside it, not by its own aspect. */
.dgrid--rejects .dfig:nth-child(3) picture { display: block; flex: 1; min-height: 0; }
.dgrid--rejects .dfig img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: center top;
  background: transparent;
}
.dgrid--rejects .dfig figcaption { margin-top: 0; }

/* ⚠️ NARROW: THE SPAN HAS TO GO, and every explicit placement with it. A
   `grid-row: 1 / span 2` left standing in a one-column grid pins the portrait to
   rows 1-2 and the two landscapes stack UNDER it in the wrong reading order — the
   caption order is 307, 292, 201 and the pictures would run 201, 307, 292. The
   `initial` resets are therefore load-bearing, not tidying. */
@media (max-width: 700px) {
  .dgrid--rejects { grid-template-columns: minmax(0, 1fr); }
  .dgrid--rejects .dfig:nth-child(1),
  .dgrid--rejects .dfig:nth-child(2),
  .dgrid--rejects .dfig:nth-child(3) { grid-column: auto; grid-row: auto; }
  .dgrid--rejects .dfig:nth-child(3) picture { flex: initial; aspect-ratio: 768 / 1376; }
}

.dfig { margin: 0; min-width: 0; }
.dfig picture { display: block; }
.dfig img {
  display: block; width: 100%; height: auto;
  border-radius: 5px;
  background: rgba(220, 230, 221, .04);
}
/* the flat vector marks (Bear's Select, Sipful, the lids) are drawn on their own
   coloured fields and need no plate under them; the screenshots and photographs
   do, or they float on the phosphor with a hard edge. One hairline covers both
   cases without a second class. */
.dfig img { box-shadow: 0 0 0 1px rgba(220, 230, 221, .07); }

/* 🔴 TRANSPARENT ARTWORK NEEDS BOTH OF THOSE OFF, and the reason is worth keeping.
   `.dfig img` carries `background: rgba(220,230,221,.04)` plus a hairline, and for
   every OPAQUE image on this site that background is invisible — the pixels cover
   it. The merch graphic was opaque-white until Sep 2 2026; giving it its real
   transparency back **exposed a rule that had only ever worked by accident**, and
   the tint became a pale flat rectangle around the artwork that did not match the
   page's own ground. Juan: "the container blacks doesn't match the rest of the
   website."
   ⇒ A figure whose artwork has real alpha gets `.dfig--alpha`: no plate, no
   hairline, so the phosphor ground shows through exactly and the graphic reads as
   printed on the page instead of pasted onto a swatch.
   ⚠️ NOT applied to the Kova mark suite. Those are also alpha, and they sit inside
   `.dmark` cells whose panel is a DELIBERATE plate — there the frame is the point.
   The difference is whether the artwork was given a container on purpose. */
.dfig--alpha img { background: transparent !important; box-shadow: none !important; border-radius: 0; }

.dfig .vid__cap { margin-top: 8px !important; }

/* ═══ A LID IS A CIRCLE ════════════════════════════════════════════════════════
   Juan: "you put the full square image when I just use it as a circle."
   🟢 THE DIE PROOF ON THIS PAGE IS THE AUTHORITY, and it settles the geometry
   rather than leaving it to taste: `die-proof-1200.png` shows a 3.5in x 3.5in
   dashed square with the lid circle inscribed in it, edge to edge. So the cut
   circle IS the inscribed circle of the square artwork, and `border-radius: 50%`
   on a 1:1 source reproduces the printed object exactly.
   🟢 ONE RULE FIXES BOTH FAMILIES, which is why there is no second class:
   - lid-vanilla / lid-fudge are full-bleed square artwork, and this die-cuts them.
   - the KovaLux lids are ALREADY circles drawn on a black square (they are JPEGs,
     so there is no alpha to carry the corners) — Juan's "empty black space on the
     corners". The same 50% clips that black away exactly at the circle's edge.
   The hairline follows border-radius, so it becomes a circular edge and keeps the
   cream lids from bleeding into the page. */
.lid img { border-radius: 50%; }
/* the lead pair is the page's opening artifact, so it gets the room: two lids on
   a 560px cap read as product, and at 300px they read as thumbnails of product. */
.dgrid--lids { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 1160px; }
@media (max-width: 620px) { .dgrid--lids { grid-template-columns: minmax(0, 1fr); max-width: 340px; } }
/* the KovaLux 2x2: design varies down the column, colourway across the row */
.dgrid--ab { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 820px; }
@media (max-width: 620px) { .dgrid--ab { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: none; } }

/* ─── "GENERATED" IS A LABEL, NOT A DISCLAIMER ────────────────────────────────
   The brief for this page is explicit that generated imagery gets labelled as
   generated. It sits INSIDE the caption rather than over the picture: an overlay
   would cover the artwork it is describing, and a badge floating on an image is
   also the one piece of text a screenshot of the page would carry away without
   its context.

   ⚠️ Text first, then colour. The word "generated" is doing the work; the amber
   is only helping you find it. A coloured dot on its own would announce nothing
   to a screen reader and nothing to anyone who does not already know the key. */
.gen {
  display: inline-block;
  margin-right: 6px; padding: 1px 5px;
  border: 1px solid rgba(240, 168, 60, .5); border-radius: 3px;
  color: var(--amber); background: rgba(240, 168, 60, .08);
  font-size: 9px; letter-spacing: .1em; text-transform: uppercase;
}
.gen--no {
  border-color: rgba(255, 107, 53, .55);
  color: var(--warm); background: rgba(255, 107, 53, .09);
}

/* a heading inside a section, between the prose and a grid. Smaller than
   .item__h so the section still reads as one thing. */
.dsub {
  margin: 26px 0 12px;
  font-size: var(--t-lg); line-height: 1.3;
  letter-spacing: -.008em; font-weight: 700; color: var(--fg);
}

/* ─── THE VERTICAL REEL ───────────────────────────────────────────────────────
   1080x1920, 83 seconds, cut for a phone. It is framed at 9:16 rather than
   letterboxed into a 16:9 slot, because the black bars either side of a
   letterboxed vertical video are not neutral — they say the piece was made for
   a shape it was not made for.

   ⚠️ aspect-ratio on the ELEMENT, not padding-top on a wrapper. The old trick
   needs the child absolutely positioned, and an absolutely positioned <video>
   inside .phosphor picks up the same filtered-containing-block problem the
   section index already had to be moved out of .phosphor to escape. */
.dfig--phone { position: relative; }
.dreel {
  display: block; width: 100%; height: auto;
  aspect-ratio: 9 / 16;
  border-radius: 10px;
  background: #000;
  box-shadow: 0 0 0 1px rgba(220, 230, 221, .1), 0 20px 50px rgba(0, 0, 0, .55);
  object-fit: cover;
}
/* NOTE: there is no custom sound toggle here any more. An earlier version
   autoplayed the reel muted and put an unmute button on the frame; the reel
   turned out to be Juan talking rather than product b-roll, so it now uses the
   browser's own controls and starts with sound when somebody presses play.
   Native controls also carry volume, scrubbing and captions support for free,
   which a hand-rolled button did not. */

/* ─── NARROW ──────────────────────────────────────────────────────────────────
   The three-up rows go to two, then one. Two-up rows stay two for as long as
   they are legible, because both of them are COMPARISONS — a lid against a lid,
   a panel against a panel, one selection model against the other — and a
   comparison stacked vertically stops being one. */
@media (max-width: 860px) {
  .dgrid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .dgrid--3 { grid-template-columns: minmax(0, 1fr); }
  .dgrid--2 { gap: 10px; }
  .dlids picture { width: 40vw; }
}
@media (max-width: 460px) {
  .dgrid--2 { grid-template-columns: minmax(0, 1fr); }
}

/* NOTE ON REDUCED MOTION: nothing to suppress here any more. The reel does not
   move until somebody presses play, which is what the preference asks for in the
   first place — a video the visitor started is not "motion they did not ask
   for". The earlier autoplay version needed a Reduce Motion branch and got one;
   removing the autoplay removed the need for it, rather than the rule being
   dropped by accident.
   ⚠️ Which also means shot.py can photograph this section honestly now. It
   forces Reduce Motion, and while the reel autoplayed that made every screenshot
   of it a different branch from the one visitors saw. It no longer does. */

/* ═══ THE HUB ══════════════════════════════════════════════════════════════════
   ADDED Sep 2 2026, second pass. /design stopped being one 11,000px scroll and
   became an index that links out. The research behind that is in
   site-docs/HANDOFF.md; the short version is that a hub with linked case studies
   beats one long page for the way recruiters scan, and the previous first screen
   said nothing about the page holding four projects.

   ⚠️ THE TILES ARE REAL LINKS TO REAL PAGES. The alternative considered was a
   bento grid whose tiles expand in place, which is the layout 2026 actually
   adopted — and it lost on a measured fact: AI answer engines read visible HTML
   in source order and do not run JS, so content behind a click is content they
   never see. That finding is the whole reason /resume is the entity home and it
   applies here unchanged. Do not "modernise" these into expanders.
   -------------------------------------------------------------------------- */
.lead__copy--wide { max-width: var(--measure); }

/* ═══ THE DRUM — one brand plate at a time, PHONE ONLY ════════════════════════
   Reasoning, and the measurement that scoped it to the phone, is in design.html
   at the markup. Short version: at 1440 the four tiles already render their key
   art at 286x223 with all four above the fold, so desktop needs nothing; below
   621px the same asset is 78x44 and the drum makes it 346x195.

   ⚠️ `display: none` ABOVE THE BREAKPOINT IS WHY index.js GATES ITS TIMER. A
   hidden track has zero width, so every panel's offsetLeft is 0, posOf() returns
   0 for all four and the rotor's tween becomes a no-op — harmless, but the
   auto-advance interval would still fire forever on every desktop visit. The
   media query is declared once in index.js § ROTORS and read there; if this
   breakpoint changes, that string changes with it. Two places, one fact, and the
   only alternative was teardown code for listeners this component never had. */
.drum { display: none; }

@media (max-width: 620px) {
  .drum { display: block; margin: clamp(20px, 3.4vw, 30px) 0 0; }

  .drum__track {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 100%;
    overflow-x: auto;
    /* 🔴 MANDATORY, and NO `overscroll-behavior-x`. Both copied from
       work.css § .rig__track deliberately: mandatory is what forbids resting
       between two plates, and the overscroll line is a tombstone there — it only
       ever existed to stop a swipe spending two history entries while a rail also
       ran a coded page turn, and there is no coded page turn anywhere now. Adding
       it back here would suppress the platform's back gesture on this rail and
       nowhere else, which is the exact asymmetry that got it removed. */
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
    border-radius: 6px;
  }
  .drum__track::-webkit-scrollbar { display: none; }

  .drum__panel {
    scroll-snap-align: center;
    min-width: 0;
    display: block;
    text-decoration: none; color: inherit;
  }
  /* the plate. `aspect-ratio` alone is not enough — a width plus a max-height
     silently beats it, which is the /film crop bug, so nothing else constrains
     the box here. */
  .drum__panel img {
    display: block; width: 100%; height: auto;
    aspect-ratio: 16 / 9;
    border-radius: 6px;
    background: rgba(220, 230, 221, .04);
  }
  .drum__cap {
    display: block; margin-top: 10px;
    font-size: 13.6px; line-height: 1.5; color: var(--fg-dim);
  }
  .drum__cap b {
    display: block;
    font-family: var(--mono); font-size: 10px; letter-spacing: .13em;
    text-transform: uppercase; color: var(--fg-dim); font-weight: 400;
    margin-bottom: 2px;
  }
  .drum__panel:hover .drum__cap, .drum__panel:focus-visible .drum__cap { color: var(--fg); }
  /* the two groups, stacked. See the markup for why they are groups at all. */
  .drum__dots { margin-top: 4px; flex-direction: column; gap: 2px; }
  .drum__jump { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 2px 4px; }
  /* ⚠️ THE COUNTER'S OWN `margin-left: 12px` IS CANCELLED HERE. `.pick__count`
     carries it for /code's flat row, where it separates the counter from the last
     dot; inside a centred three-item stepper it pushes the group off axis by 12px,
     which is small enough to read as sloppy rather than as a bug. */
  .drum__step { display: flex; justify-content: center; align-items: center; gap: 4px; }
  .drum__step .pick__count { margin-left: 0; }

  /* 🔴 AND THE TILES DROP THEIR THUMBNAIL, which is the point of the pairing.
     The drum carries the pictures; leaving the 78x44 art in place would render
     the same four key-art files twice on one screen at two sizes, the larger of
     which exists precisely because the smaller one is illegible. The anchors,
     the numbers, the titles and the descriptions all stay — so what an answer
     engine reads in source order is unchanged, and what the eye gets is one
     index instead of a second gallery. */
  .dtile__art { display: none; }
  .dtile {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "k" "t" "d";
  }

  /* the eyebrow stays on desktop, where it is one 28px line doing a kicker's
     job, and goes here, where it wraps to two lines of wide-tracked mono caps
     and reads LOUDER than the headline it is subordinate to. Measured: 49px on
     a 402px screen against the h1's 61px. */
  .item--intro .eyebrow { display: none; }
}

/* a narrow DESKTOP window is `pointer: fine` and can still see the drum, so it
   needs the same treatment /code's rail gets: engine snap off, and JETRAIL takes
   the wheel and eases to the nearest plate in JS. Mandatory snap on a wheel was
   measured teleporting a whole panel, and a plate here IS a whole panel. */
@media (pointer: fine) and (max-width: 620px) {
  .drum__track { scroll-snap-type: none; }
}

.dtiles {
  display: grid; gap: clamp(10px, 1.4vw, 16px);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: clamp(26px, 4vw, 44px);
  max-width: 1200px;
}
@media (max-width: 620px) { .dtiles { grid-template-columns: minmax(0, 1fr); } }



.dtile {
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr);
  grid-template-areas: "art k" "art t" "art d";
  gap: 2px 14px; align-content: start;
  padding: 14px; text-decoration: none; color: inherit;
  border: 1px solid rgba(220, 230, 221, .12);
  background: rgba(220, 230, 221, .022);
  border-radius: 4px;
}
.dtile:hover, .dtile:focus-visible {
  border-color: rgba(240, 168, 60, .5);
  background: rgba(240, 168, 60, .05);
  outline: none;
}
.dtile__art { grid-area: art; align-self: start; }
.dtile__art img {
  display: block; width: 78px; aspect-ratio: 16 / 9; height: auto;
  object-fit: cover; border-radius: 3px;
  background: rgba(220, 230, 221, .04);
}
.dtile__k {
  grid-area: k;
  font-family: var(--mono); font-size: 10px; letter-spacing: .13em;
  text-transform: uppercase; color: var(--fg-dim);
}
.dtile__t { grid-area: t; font-size: 16.5px; line-height: 1.24; font-weight: 700; color: var(--fg); }
.dtile__d { grid-area: d; margin-top: 4px; font-size: 13.6px; line-height: 1.5; color: var(--fg-dim); }

/* 🔴 THIS BLOCK MUST STAY BELOW THE `.dtile` RULES ABOVE. It first sat directly
   under `.dtiles`, before them, and the four-up grid applied while the CARD
   never re-stacked: `@media { .dtile }` and a bare `.dtile` are both (0,1,0),
   so the later one wins, and the later one was the base. Nothing errored — the
   tiles simply kept the thumbnail-beside-text layout inside a 288px track and
   crushed every title. Equal specificity means SOURCE ORDER decides, which is
   the trap work.css already warns about in § CHOOSE YOUR PLAYER. */
/* ⚠️ AT ≥1000px THE INDEX GOES FOUR-UP AND THE CARD RE-STACKS. Two things made
   this necessary rather than decorative. The index is the most important object
   on the hub — it is the thing the whole restructure exists for — and left at
   two columns it ended up NARROWER than the contact sheets further down the
   page, which reads as the page burying its own map. And a 78px thumbnail
   beside text in a 300px track crushes the title to three lines.
   So on a wide screen the art goes on TOP and runs the full card width, which
   is what turns four links into an index strip you take in at once. */
/* ⚠️ THIS STARTS AT 621px, NOT 1000px, AND THE OLD 1000px FLOOR WAS A GAP.
   The rule above says a 78px thumbnail beside text in a 300px track crushes the
   title — and between 621 and 999 the grid is already TWO columns, so the
   tracks there are 258-450px and had exactly that problem, unfixed, the whole
   time. The art-on-top form now applies wherever there is more than one column;
   only the single-column phone keeps the compact beside-text list. At 768 the
   artwork goes from 78x44 to 294x165. */
@media (min-width: 621px) {
  .dtile {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "art" "k" "t" "d";
    gap: 0;
    /* THE ART IS THE CARD. It used to sit inside 14px of padding on all four
       sides, which is 28px of the card's width spent on framing the one thing
       on this page a visitor actually wants to look at. The padding now belongs
       to the TEXT only and the artwork runs to the card's own border, so the
       brand's colour field is what the eye lands on. `overflow: hidden` is what
       makes the image take the card's 4px radius instead of squaring it off. */
    padding: 0 0 14px;
    overflow: hidden;
  }
  .dtile__art { margin-bottom: 11px; }
  .dtile__art img { width: 100%; height: auto; border-radius: 0; }
  .dtile__k, .dtile__t, .dtile__d { padding-inline: 14px; }
  .dtile__t { margin-top: 3px; }
}

/* the index goes four-up only when there is room for four readable tracks */
@media (min-width: 1000px) {
  /* ═══ THE STRIP IS ASYMMETRIC, AND THE WIDTHS ARE THE HIERARCHY ══════════════
     Sep 2 2026, eighteenth pass, after Juan asked whether the bento he had been
     exploring belonged here. It does — but not as two rows.

     🟢 WHAT THE RESEARCH ACTUALLY SAYS. Portfolios are the strongest case for a
     bento, and for one specific reason: a uniform grid "flattens the distinction
     between a flagship campaign and a side project by forcing both into identical
     cells." That is exactly what this strip was doing. /packaging and /web are
     large bodies of work; /onword is a spec that was never built and /sipful is a
     logo system. Four identical tracks asserted they are the same size, and they
     are not.

     🔴 BUT A TWO-ROW BENTO WAS THE WRONG SHAPE HERE, and the numbers say so
     rather than taste. A 2fr cell in a 1200px container is ~790px wide, and 16:9
     art in it is ~444px tall. Two rows of that is ~1100px of index — on a 900px
     laptop the lede and the through-line both leave the fold, which REGRESSES the
     Sep 2 second-pass restructure whose whole point was that the index is the
     first screen. The `.dgrid--3` comment upstairs already records the same
     arithmetic trap: panel height is a function of column width, so you cannot
     make a wide cell short by wanting it.
     ⇒ Hierarchy goes in the WIDTHS and the strip stays ONE ROW. The two flagships
     get 1.35fr, the two smaller pieces 1fr. The art keeps a single height across
     all four (object-fit: cover crops to the wider box), so the row still reads as
     one band you take in at once — which is what the four-up was built to be.

     🟢 AND THIS COSTS NO SOURCE-ORDER RISK, which is the one real accessibility
     failure mode for bento (WCAG 1.3.2, "Meaningful Sequence" — audits keep
     finding a card placed first visually and seventh in the DOM, so the tab order
     jumps). Nothing is reordered here: the DOM already runs Kova · Web · Onword ·
     Sipful, and the two that get the wide tracks are already the first two. Visual
     order === DOM order === tab order.
     ⚠️ If a fifth project is ever added, keep that property. Re-rank by EDITING
     THE SOURCE ORDER, never by grid-column placement. */
  .dtiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* 🔴 AND THE ASYMMETRY STARTS AT 1200, NOT 1000, BECAUSE IT WAS MEASURED.
   At 1032 (an iPad landscape) the 1.35/1 split gives the two narrow cells
   **180px** — about 22 characters a line, which ran their descriptions to FOUR
   lines. The four-up comment above already records that a ~258px track was tight
   enough to be worth fixing; 180 is well past it. Hierarchy is not worth a
   crushed cell, so below 1200 all four tracks stay equal and the strip simply
   says nothing about scope.
   At 1200 the narrow cells are ~247px and the wide ~333px, which is the same
   text measure the equal four-up had. Measured at 1440: 367 / 272. */
/* 🔴 THE 1.35 SPLIT IS GONE — Juan, Sep 3 2026: "all of the four items are kind
   of awkwardly shown." Measured at 1440 it produced 367 / 367 / 272 / 272, and
   that ratio is the problem rather than the idea. Published guidance is on the
   side of hierarchy for a portfolio — eye-tracking has readers fixating on the
   larger cell first and dwelling ~2.6x longer — but every one of those studies
   is describing a DECISIVE difference, a lead card against compact slots. At
   1.35x the strip is neither: too small to read as emphasis, big enough to read
   as a grid that failed to resolve. Ambiguity is the one thing a four-item index
   cannot afford.
   ⚠️ AND THE RAGGED TEXT WAS THE SAME CAUSE. The narrow cells ran their
   descriptions to THREE lines against the wide cells' two, so four equal-height
   boxes held text blocks that bottomed out at different places. Equal tracks
   give all four the same measure and the raggedness goes with the split — one
   change, both symptoms.
   ⚠️ ORDER IS ALREADY CARRIED, WHICH IS WHY NOTHING IS LOST. Every tile is
   numbered `01 · Kova` through `04 · Sipful`; the sequence is stated in words,
   so width does not have to say it a second time and less well.

   THE OTHER THREE CANDIDATES, AND WHY EACH LOSES HERE — Juan named all of them:
   · MASONRY — ruled out twice over. `columns` reads COLUMN-MAJOR, so a numbered
     01-04 index would be read down and then across, in the wrong order; and this
     repo's own audit found masonry's gate is captions, which every tile has.
   · BENTO — a real one crops by design, and it is what this already was, just
     timidly. Making it decisive would mean one big cell and three small, which
     is a claim about scope that the four descriptions do not support: these are
     four peers, not a flagship and three offcuts.
   · FULL-BLEED SLIDESHOW — loses on a fact already measured on THIS page, in the
     note above .dtiles in design.html: AI answer engines read visible HTML in
     source order and do not run JS, so anything behind a click is content they
     never see. A slideshow hides three of four projects from exactly the readers
     the restructure was for. It is the same reason expanding tiles lost. */
@media (min-width: 1200px) {
  /* ⚠️ THE HEIGHT STAYS, AND ITS REASON HAS CHANGED. It was here because the
     tracks were two different widths, so an aspect-ratio would have given two
     different art heights and a ragged strip. The tracks are equal now, so
     16:9 would in fact hold — but a definite height is still the better rule:
     it fixes the strip's depth independently of how wide the container gets,
     which is what stops the key images swelling into the fold on a very wide
     screen. `cover` crops to fill, and that is safe here because every key image
     is a mark or a field centred
     with room around it, not a diagram that loses meaning at the edges. */
  .dtiles .dtile__art img { aspect-ratio: auto; height: clamp(150px, 15.5vw, 250px); }
}

/* ═══ THE BRAND BAND BREAKS OUT OF THE PROSE COLUMN ════════════════════════════
   Juan, Sep 2 2026: "the hero of this page should be the media we display per
   brand ... taking up a big chunk of the hero section." Measured at 1440x900
   before touching anything, the header owned 566px of a 900px fold and the four
   key images owned 145px of it. The page's hero was a paragraph.

   🔴 AND FOUR 16:9 PANELS IN A ROW CANNOT BE MADE TALL BY WANTING IT. Their
   height is (column width)/4 x 9/16, so at the 1200px prose column the artwork
   is capped at 162px however much fold is free. The only lever that raises it
   without dropping to 2x2 — which puts the second row under the fold on any
   900px laptop and regresses the whole Sep 2 restructure — is a WIDER BAND.
   So above 1400px the band leaves the prose column and runs on a 4vw gutter.
   1440: 1325px wide, art 178px tall. 1920: 1766px, 242px. 2560: 2355px, 322px.

   ⚠️ THE PULL IS IN vw AND px ONLY, NEVER %. `--gut` is
   `max(7vw, calc((100% - 1200px) / 2))`, and that `100%` resolves against
   whatever box the property is used in — on the item it is the viewport, but a
   custom property substitutes as TOKENS, so reusing it in a child's margin
   would silently re-resolve `100%` against the 1200px column and compute a
   zero pull. That is the out-of-scope var() failure in `css-silent-fallbacks`:
   it would not error, it would just quietly do nothing. Written out longhand,
   the pull is 4vw - (100vw - 1200px)/2 = 600px - 46vw.

   ⚠️ IT CANNOT OVERFLOW, and that is arithmetic rather than luck. The pull is
   0.46W - 600 and the gutter it eats into is at least 0.07W, so the band stays
   inside the viewport as long as 0.5W - 600 > 0.46W - 600, i.e. always. The
   1400px floor is where the two gutter terms cross ((W-1200)/2 = 7vw at
   W = 1395), so nothing below it moves by a pixel and no laptop width changes. */
/* 🔴 THE BREAK-OUT IS GONE — Juan, Sep 3 2026: "I still don't like how they're
   an exception to the width... not how wide each individual tile is, but just
   how much space that whole row takes compared to everything else. It just
   breaks rules." He is right, and the reason it can go is that its own
   justification expired.
   The argument above is that art height is (band width)/4 x 9/16, so the only
   way to make the key images taller without dropping to 2x2 was a wider band.
   That stopped being true in the `min-width: 1200px` block above, which replaced
   the aspect-ratio with an explicit `height: clamp(150px, 15.5vw, 250px)`. Art
   height is now driven by the VIEWPORT and does not depend on the band at all —
   223px at 1440 either way. So the break-out was buying nothing it was written
   to buy, and was costing the one thing a hub index should not cost: a row that
   starts and ends somewhere no other row on the page does.
   ⇒ When a rule's stated reason is a derivation, check the derivation still
   holds before keeping the rule. This one had been dead since the height became
   explicit, and nothing pointed it out because a band that is too wide still
   renders perfectly. */

/* ═══ THE BRAND SYSTEM BLOCK ═══════════════════════════════════════════════════
   The research gap the first cut had: brand work is judged as a SYSTEM — the
   logo's applications, the colour roles, the type roles, the grid — and the page
   was showing finished marks only. These three components are what closed it.
   -------------------------------------------------------------------------- */

/* 🔴 THE FIVE LOGO APPLICATIONS, AND THE FIRST VERSION OF THIS RULE BROKE THE
   WHOLE PAGE ON A PHONE. It read:

       grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));

   `auto-fit` cannot collapse a track whose MINIMUM is 140px, so five tracks
   demanded 700px plus gaps on a 402px viewport. The grid measured 748px wide,
   the row it sat in stretched to match, and `document.scrollWidth` went to 776
   against a 402px client width — the page scrolled sideways, which is the one
   thing this repo's layout rules say must never happen. It also dragged .tspec
   out to 625px, so the damage did not look like it came from here.

   ⚠️ AND IT IS THE EXACT TRAP DOCUMENTED 60 LINES ABOVE in this same file, about
   `minmax(0, 1fr)` versus a floor that cannot shrink. Writing the warning did not
   stop me reintroducing it. Explicit counts at breakpoints, and a zero floor. */
/* 🔴 EACH VARIATION GETS ITS OWN CELL, and that is what fixes "different sizes".
   Juan, twice: the row of five marks reads as unevenly laid out. Measured, the
   marks are ALREADY ink-normalised — ink areas 4961-6913px², per-mark
   corrections of only x0.91-1.07 — so resizing them was the wrong fix and the
   measurement is what stopped it. What actually makes the row scatter is that it
   mixes two shape classes (three lockups at 3.6-4.3:1 against two marks at ~1:1)
   with `align-items: end`, so the wide ones sit low and wide, the square ones
   tall and narrow, and nothing bounds any of them. Five shapes floating on a
   dark field at five different aspect ratios have no common edge for the eye.
   ⇒ A bounded cell per variation, equal in size, mark optically centred with the
   same clear space on every side. That is how a brand book presents a logo
   suite, and it is the clearspace grid the 2026 sources describe made visible:
   the CELL supplies the consistent frame the marks' own aspects cannot. */
.dgrid--marks {
  grid-template-columns: repeat(5, minmax(0, 1fr));
  max-width: 1100px; align-items: stretch;
}
.dmark {
  display: flex; flex-direction: column;
  background: rgba(220, 230, 221, .028);
  border: 1px solid rgba(220, 230, 221, .085);
  border-radius: 4px;
  padding: 20px 14px 0;
}

/* ═══ THE SHEET — one field, hairline dividers, no box per item ═══════════════
   Juan, Sep 4 2026, with an Allan Peters carousel as the reference: "maybe my
   assets should share the same background feel at times, or the same container,
   and be divided with dividers, kind of how he does it. It just looks clean."

   🟢 AND IT IS THE SAME DESIGN AS THE ONE ABOVE, AT A DIFFERENT SETTING — which is
   why this is a modifier and not a rewrite. § the logo suite argues that five
   shapes at five aspect ratios "have no common edge for the eye" and that the CELL
   has to supply the frame their own aspects cannot. That reasoning is intact and
   this keeps it: the cells stay equal, the marks stay optically centred, the
   clear space is unchanged. What changes is the COUNT of edges. Five bordered,
   tinted panels put five competing rectangles on the page and the eye has to
   choose which one is the object; a sheet gives it ONE outer edge and reads the
   internal rules as structure instead. Allan Peters' logo-suite frame is exactly
   this — a 2x2 of variants divided by thin rules on one field, no box per mark.

   ⚠️ THE GAP *IS* THE DIVIDER. `gap: 1px` with the rule colour on the GRID and the
   field colour on the CELLS means the browser draws the hairlines for free, at
   every internal boundary, with no pseudo-elements and nothing to keep in sync
   when a span changes. `overflow: hidden` is what lets the corners round.
   ⚠️ THE 22px GAP GOING AWAY COSTS NOTHING, measured: adjacent cells each carry
   25.92px of side padding at 1440, so there is still ~52px between two marks —
   more than the gap was contributing. The clear space was never the gap's job.
   ⚠️ RULE COLOUR IS `.09`, WHICH IS THE SITE'S DIVIDER VALUE — the same
   `rgba(220, 230, 221, .09)` `.item--end` and `.doc__foot` use, not `.dmark`'s own
   `.085`. One divider value site-wide is the point of doing this at all.
   🟢 AND THE SUITE IS ALREADY MATCHED ON THE SNOUT — 0.5%. THE PARAGRAPH THAT WAS
   HERE CLAIMED THE OPPOSITE AND WAS WRONG, twice over, so both errors are kept
   because they are the same error this repo keeps making.

   It said "all five render at exactly 104px tall … so the SAME snout appears at
   roughly a third of the size inside the lockup that it does standing alone."
   **Error one: 104px is the BOX, not the drawn image.** `getBoundingClientRect`
   returns the element's box; under `object-fit: contain` the artwork inside it is
   smaller on whichever axis is not limiting. Two of these five are width-limited,
   so "all five at 104" was never a statement about any mark.
   **Error two: the ratio was inferred from the aspect ratios instead of measured.**

   Measured properly — each source's alpha channel decoded to raw grey and profiled
   by column and row, then the drawn size computed from the real box:

     mark              snout drawn      "KOVA" cap drawn
     full lockup       111.2px          66.3px
     standard logo     111.4px          84.0px
     wordmark alone    -                80.0px
     snout icon        110.8px          -

   ⇒ **The snout is matched across all three that carry it, to within 0.5%.** The
   sources are each trimmed to their ink and exported at 1200 wide, and the snout
   occupies 354/1200 of the lockup and 301/1200 of the standard — so height-limiting
   both lands the snout at the same size by construction. Someone did this on
   purpose. The wordmark varies 80.0 against 84.0 between the two cells where it is
   comparable (5%), and is 21% smaller in the lockup, where the artwork itself gives
   part of its height to the PROTEIN ICE CREAM subhead. That is the artwork's own
   proportion, not the container's.

   ⚠️ SO THERE IS NOTHING TO FIX HERE, and `match-marks-on-ink-area` is SATISFIED
   rather than violated. Do not "normalise" these assets on the strength of the
   claim that was here. ⚠️ And the numbers above are COMPUTED from measured boxes
   plus measured source ink, not read off a render — a different and weaker claim
   than "seen", per the rule about saying which one a statement rests on. */
.dgrid--sheet {
  gap: 1px;
  background: rgba(220, 230, 221, .09);
  border: 1px solid rgba(220, 230, 221, .09);
  border-radius: 5px;
  overflow: hidden;
}
.dgrid--sheet > .dmark {
  border: 0;
  border-radius: 0;
  background: rgba(220, 230, 221, .028);
}
/* 🔴 AN ORPHAN CELL IS A HOLE IN A SHEET, WHICH IT WAS NOT IN A ROW OF BOXES —
   the one regression the sheet introduces, caught by measuring rather than by
   reasoning about it. At 402px this grid resolves to TWO columns for FIVE marks,
   so the mascot lands alone on row three. As five separate panels that read as a
   last item sitting by itself; inside one field the empty half of the row shows
   the GRID's background instead of a cell's, and the grid's background is the
   rule colour — lighter than the cells by design, because that is what draws the
   dividers. So the gap becomes a pale rectangle the size of a mark.
   ⇒ A last cell with no partner takes the whole row. `:nth-child(odd)` is what
   makes this a statement about being unpaired rather than about being fifth — it
   holds if a sixth mark is ever added (no orphan, rule does nothing) and it holds
   at the three-column breakpoint too. Third of three today, fifth of five here.
   🔴 THE UNSCOPED VERSION OF THIS RULE SHIPPED A DESKTOP BUG AND JUAN PHOTOGRAPHED
   IT. It read `.dgrid--sheet > .dmark:last-child:nth-child(odd) { grid-column: 1 / -1 }`
   with NO media query, and the comment beside it asserted "this generic rule stays
   for any future sheet that is NOT the bento" — which was simply false, because
   `.dgrid--sheet > .dmark` matches the bento's own cells. Measured at 1920px: the
   six tracks take spans 3+3 on row one and 2+2 on row two, and the mascot's
   `1 / -1` cannot fit in the two tracks left, so it wrapped to a THIRD row at full
   width and stranded tracks 5-6 of row two as an empty pale rectangle — the sheet's
   grid background showing through, which is the exact hole the narrow fix existed to
   remove, reintroduced at desktop by the fix itself.
   ⇒ **Deleted, not scoped.** `.dgrid--sheet` is used on exactly one element in the
   whole site, so a "generic" rule here had no second client to serve — it was
   speculation, and the speculation is what broke the one real case. The narrow
   orphan fix lives with § the bento's narrow block, inside the media query where it
   can see the `!important` it has to beat.
   ⚠️ THE LESSON, because it is the third of its kind this session: a rule written
   for an EDGE (the wrapped, two-column case) must be scoped to that edge. Left
   unscoped it applies to the interior, where the assumption it rests on — "there
   are two columns" — is false. Same shape as
   `boundary-rules-dont-apply-to-interiors`. */
.dmark .vid__cap { margin-top: 0 !important; padding-bottom: 10px; }
@media (max-width: 760px) { .dgrid--marks { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 440px) { .dgrid--marks { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.dmark img {
  height: 62px; width: 100%; object-fit: contain; object-position: center;
  background: transparent; box-shadow: none;
  margin-bottom: 14px;
}
.dnote { margin-top: -6px !important; }

/* colour, as chips with their role rather than as a row of squares. A swatch
   without its rule is decoration; the rule is the part that is design work. */
/* ⚠️ ONE ROW PER COLOUR, NOT TWO. Juan on §03: it "seems kinda messy or doesn't
   seem like easily digestible." The list was the biggest reason — every colour
   spent two lines (chip + name + hex, then the role wrapped underneath on its
   own row), and a second list below it dropped the role column entirely, so the
   same component rendered two different shapes on one page.
   2026 guidance on brand sections is specific and matches: a colour palette
   reads as a CHART — swatch, name, code, and a note on its intended role — so
   the role belongs in a column, not in a paragraph under the swatch.
   ⇒ A 4-column grid: chip, name, hex, role. The rows align, the section loses
   about a third of its height, and both palettes use one component. */
.sw { list-style: none; margin: 0 0 20px; padding: 0; display: grid; gap: 7px 14px; }
/* 🔴 ONE RULE PER SELECTOR, IN ONE PLACE — and the first attempt at this chart
   got that wrong in a way worth keeping. It inserted a NEW `.sw li` above the
   existing one and leaned on `!important` for two properties out of four. The old
   rule still won `grid-template-columns` (3 tracks, not 4) while the new one won
   `grid-column: auto`, so the role text auto-placed into the 24px SWATCH column
   and wrapped one word per line — visibly worse than what it replaced.
   Equal specificity means source order decides. work.css warns about it, and this
   file has now been bitten three times (.dgrid, .dtiles, this).
   ⇒ Rewrite the rule. Never stack another one above it.

   ⚠️ ONE ROW PER COLOUR, NOT TWO. Juan on §03: it "seems kinda messy or doesn't
   seem like easily digestible." The palette was the biggest reason — every colour
   spent two rows (chip/name/hex, then the role underneath), and a SECOND list
   below it dropped the role column entirely and switched to flex, so one
   component rendered two different shapes on one page. 2026 guidance on brand
   sections matches: a palette reads as a CHART — swatch, name, code, and the
   role it plays — so the role belongs in a column. */
.sw li {
  display: grid;
  grid-template-columns: 24px minmax(0, auto) minmax(0, auto) minmax(0, 1fr);
  align-items: center; gap: 0 14px;
}
.sw__chip {
  /* circles, not squares — Juan, Sep 2 2026. A round chip reads as a colour
     SAMPLE; a rounded square reads as a tile or an app icon, which is a
     different object on a page that is also showing icons. */
  width: 24px; height: 24px; border-radius: 50%; background: var(--c);
  box-shadow: inset 0 0 0 1px rgba(220, 230, 221, .18);
  align-self: center;
}
.sw b { font-size: 14.5px; color: var(--fg); font-weight: 600; white-space: nowrap; }
.sw code {
  font-family: var(--mono); font-size: 11.5px; color: var(--amber);
  letter-spacing: .04em; white-space: nowrap;
}
.sw li > span:last-child { font-size: 13.8px; line-height: 1.45; color: var(--fg-dim); }
/* below 760 the role drops under the name rather than squeezing a fourth track —
   and it spans from the NAME column, never from the swatch. */
@media (max-width: 760px) {
  .sw li { grid-template-columns: 24px minmax(0, 1fr) auto; align-items: baseline; }
  .sw__chip { align-self: center; }
  .sw li > span:last-child { grid-column: 2 / -1; margin-top: 2px; }
}

/* type roles. A definition list, because that is what it is: a name and the one
   job it does. */
.tspec { margin: 0 0 16px; max-width: var(--measure); display: grid; gap: 1px;
         background: rgba(220, 230, 221, .09); border: 1px solid rgba(220, 230, 221, .09); }
.tspec > div {
  display: grid; grid-template-columns: minmax(0, 15em) minmax(0, 1fr); gap: 0;
  background: var(--void);
}
.tspec dt {
  padding: 9px 12px; font-size: 13.8px; color: var(--fg); font-weight: 600;
  border-right: 1px solid rgba(220, 230, 221, .09);
}
.tspec dt em { font-style: normal; font-weight: 400; color: var(--fg-dim); }
.tspec dd { margin: 0; padding: 9px 12px; font-size: 13.8px; color: var(--fg-dim); }
@media (max-width: 560px) {
  .tspec > div { grid-template-columns: minmax(0, 1fr); }
  .tspec dt { border-right: 0; padding-bottom: 0; }
  .tspec dd { padding-top: 2px; }
}

/* ═══ THE MODIFIER TABLE on /onword ════════════════════════════════════════════
   ⚠️ It scrolls inside its OWN container. A table wide enough to hold three
   columns of key names will overflow a phone, and the one thing the page body
   must never do is scroll sideways. tabindex makes the scroll container
   keyboard-reachable, which it is not by default. */
.dtable { overflow-x: auto; margin: 0 0 16px; max-width: 100%; -webkit-overflow-scrolling: touch; }
.dtable:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; }
.dtable table { border-collapse: collapse; min-width: 480px; width: 100%; }
.dtable th, .dtable td {
  text-align: left; padding: 8px 12px; font-size: 13.6px;
  border-bottom: 1px solid rgba(220, 230, 221, .09);
}
.dtable th {
  font-family: var(--mono); font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fg-dim); font-weight: 500;
}
.dtable td { color: var(--fg-dim); }
.dtable td:first-child { color: var(--fg); white-space: nowrap; }
.dtable kbd {
  font-family: var(--mono); font-size: 11.5px; padding: 1px 5px;
  border: 1px solid rgba(220, 230, 221, .22); border-radius: 3px; color: var(--fg);
}

/* the Onword lead mark, which is wide rather than square, so it does not reuse
   .dlids' two-object row */
.donword { max-width: min(78vw, 420px); }
.donword img {
  display: block; width: 100%; height: auto; border-radius: 4px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, .45);
}

/* THE LEDE MOVED BELOW THE BAND, so it has to buy back the space the <h1>'s
   own 22px bottom margin used to give it. It takes .dpov's spacing step rather
   than a new number, so the three blocks under the index keep one rhythm. */
.dtiles + .doc__lede { margin-top: clamp(24px, 3.2vw, 36px); }

/* the hub's through-line. It sits UNDER the index rather than above it, because
   the index is what the first screen is for — a reader who wants the argument
   scrolls four lines to find it, and a reader who wants the work never has to. */
.dpov {
  margin-top: clamp(22px, 3vw, 34px);
  max-width: var(--measure);
  padding-left: 14px;
  border-left: 2px solid rgba(240, 168, 60, .32);
}
.dpov p { margin: 0; font-size: var(--t-base); line-height: 1.68; color: var(--fg-dim); }

/* ═══ THE BENTO STUDY ══════════════════════════════════════════════════════════
   🟢 ADOPTED Sep 3 2026 AND FOLDED IN. It began as the `bento-study` branch; Juan
   accepted it after looking at the branch's own Pages URL. The parked `.dlegend`
   variant was DELETED at fold-in — it was unused by any page, and dead CSS that
   ships is exactly what README § Files exists to catch. Commit `d8e39d6` on
   `bento-study` still has its markup if the margin form is ever wanted back.

   THIRD CUT, Sep 3 2026. Juan: *"wouldn't it look better if the media shared the
   same height so they look more uniform altogether?"* — and *"I don't understand
   why the second page is so small."* Both are the same defect and he is right.

   🔴 WHAT I HAD WRONG: `align-items: start`. CSS Grid ALREADY equalises heights
   within a row — that is the default `stretch` — and I had switched it off in the
   second cut to stop tiles stretching around natural-aspect images. The cure
   created the ragged edge he is looking at. 2026 bento guidance is explicit on
   both halves: use `grid-auto-rows` for uniform rows, and give every cell real
   interior padding (24-32px is the quoted range) so the tiles read as one
   framework instead of as boxes that happen to sit near each other.

   🔴 AND THE SECOND HALF OF HIS QUESTION HAS A HARDER ANSWER. /web §01's three
   palette renders are all 16:9. Three same-aspect images in cells of 4/6 and 2/6
   can NEVER render at the same size — a uniform row height just pads the narrow
   one. **For same-aspect content being compared, equal height forces equal
   width.** So the comparison rows are now EQUAL cells, and what they keep from
   the bento is the part Juan actually asked for twice: the tile, and the copy
   inside it.
   ⇒ **Bento here is a CONTAINER style, not a sizing scheme.** Unequal sizing
   survives in exactly one place — `.dmarks-bento`, where the five Kova marks are
   genuinely different SHAPES and no two of them want the same box.
   -------------------------------------------------------------------------- */
.dbento {
  display: grid;
  gap: clamp(10px, 1.3vw, 16px);
  grid-template-columns: repeat(6, minmax(0, 1fr));
  max-width: 1100px; margin: 0 0 20px;
  /* 🟢 THE MEDIA BOX IS THE ARTWORK'S OWN ASPECT, DECLARED PER SECTION. This is
     what makes uniform height and zero cropping the SAME setting rather than a
     trade. A fixed `grid-auto-rows` height did give uniform rows, but a 16:9
     render in a 300px-tall cell then floated in ~90px of dead tile — the
     letterbox reading as emptiness that `the-plate-belongs-to-the-image` warns
     about. Because the comparison cells are now EQUAL width, matching the box to
     the source aspect means every tile in a row is identical, the artwork fills
     it exactly, and nothing is cropped or padded out.
     ⚠️ SET `--media` TO THE ACTUAL SOURCE DIMENSIONS of that section's assets. If
     a section's assets are ever regenerated at a new aspect, this is the line
     that has to move with them, or the letterbox comes back. */
  --media: 1200 / 675;
}
.dbento--onword { --media: 1200 / 760; }
.dbento--sipful { --media: 1200 / 674; }
/* the icon and the lockup, now on identical 1221x747 canvases — see the
   sipful-mark note in design-assets.sh for how the mark size was matched. */
.dbento--sipmark { --media: 1221 / 747; }
/* EQUAL CELLS. These rows are comparisons — a state against a state, a palette
   against a palette, a cut against a cut — so the cells match and the artwork is
   the only thing that differs. 6 tracks divides by 2 and 3; the four Onword
   states go 2x2 rather than 4-across so each one keeps a readable size. */
.dbento--2 > * { grid-column: span 3; }
.dbento--3 > * { grid-column: span 2; }
.dbento--4 > * { grid-column: span 3; }

/* THE TILE IS THE CONTAINER, and the padding is what makes it one. */
.dbento .dfig {
  display: flex; flex-direction: column; min-width: 0;
  background: rgba(220, 230, 221, .028);
  border: 1px solid rgba(220, 230, 221, .1);
  border-radius: 6px;
  padding: clamp(14px, 1.6vw, 24px) clamp(14px, 1.6vw, 24px) 0;
  overflow: hidden;
}
/* the media area takes whatever the row height leaves after the caption, and the
   image is CONTAINED inside it — full artwork, centred, never cropped. The
   letterboxing that `contain` produces is absorbed by the padding above, which is
   why it reads as a plate rather than as the empty space
   `the-plate-belongs-to-the-image` warns about. */
.dbento .dfig picture { display: block; aspect-ratio: var(--media); }
.dbento .dfig img {
  display: block; width: 100%; height: 100%;
  object-fit: contain; object-position: center;
  box-shadow: none !important; border-radius: 0;
  background: transparent;
}
/* ═══ FULL BLEED, AND THE RULE THAT DECIDES IT ════════════════════════════════
   Juan: *"why can't they take the whole space of the container? Why do they have
   padding a bit? Can we make it so it's pretty much full bleed?"* — asked about
   the masa screenshots and again about Onword.

   🟢 THE RESEARCH GIVES A RULE RATHER THAN A PREFERENCE. Card guidance says
   inset padding buys "clear visual separation between content and the container
   boundary" (24px desktop / 20px mobile) — but that presumes the media has NO
   EDGE OF ITS OWN. Full bleed is for media that is already a complete rectangle.
   ⇒ **DOES THE ARTWORK OWN ITS OWN EDGE?**
     · A website screenshot DOES — the browser viewport is its edge. Bleeding it
       makes the tile read as the screen, which is what it is a picture of.
       Inset it and you get a frame inside a frame.
     · Onword's four states DO — they were recomposited onto identical canvases
       with matched clear space in the sixteenth pass. `.dgrid--suite`'s own
       comment already says why padding them again is wrong: "the normalised
       canvases already carry the frame, and a CSS cell would pad an
       already-padded asset twice." My tile padding was doing exactly that.
     · Sipful's marks and studies DO — each is drawn on its own field, blue or
       white, edge to edge.
     · A LOGO MARK DOES NOT. `.dmark`'s alpha PNGs have no field, and their clear
       space is a RATIO OF THE MARK rather than a value (keyart.py's rule). Those
       keep their panel and their padding — and on a textured dark ground the
       research is explicit that a knockout mark wants a solid field under it.
   ⇒ So bleed is the default for the screenshot and canvas rows, and padding
   survives exactly where the artwork has no edge of its own.
   ⚠️ The caption keeps its inset even when the media bleeds — text against a tile
   edge is the one thing the padding rule is unambiguously right about. */
.dbento--bleed .dfig { padding: 0; }

/* MODE A — the caption belongs to the container. It sits under the media on the
   tile's own ground rather than in a tinted band: with real padding the tile is
   already legible as a container, and a second tint inside it read as a box in a
   box. */
/* ═══ THE GENERATION GRID — EIGHT FRAMES, PICKED FOR DIFFERENCE ═══════════════
   🟢 WAS A 37-FRAME SCROLL STRIP (`.dstrip`) FOR ONE DAY. Juan looked at it and
   asked whether a section like this should show variations of one thing or different
   settings and subjects, and whether a strip was even the right form. Both answers
   went against what had shipped.

   🔴 ON THE SET, IN TWO STEPS. First the strip held every clean frame, because "the
   whole surviving set is the evidence for the count." That was wrong: 2026 portfolio
   guidance is blunt that *"more images dilute impact rather than demonstrate range"*,
   near-duplicates are *"one answer repeated three times"*, and sixteen of the 33
   survivors were the same seated rooftop-ledge setup. So it cut to eight.
   🔴 THEN JUAN ASKED THE HARDER HALF: *"I do believe I have different campaigns."* He
   was right, and eight frames of ONE shoot answered "which frames differ" when the
   question was "what does this pipeline do." The favourites folder holds at least ten
   distinct bodies of work. The grid now shows eight JOBS — the mark in felt and in
   clay, an event booth, two packaging renders, a courtyard set, a studio editorial and
   one campaign frame — and it MOVED above the contact sheets, so the page reads breadth
   first and then depth on one campaign's method.
   🟢 The felt and clay pair is deliberate: one mark, two materials. **Repetition
   without a stated variable reads as the generator repeating itself; variation with one
   reads as method.** That distinction is what the first two cuts were missing.

   🔴 ON THE FORM. A grid, not a strip, and not a bento or a slideshow:
     · NN/g on carousels — *"include 5 or fewer frames ... it is unlikely users will
       engage with more than that"*, and *"people often scroll past carousels"*. A rail
       of 33 buried 29 of them behind a gesture.
     · horizontal scrolling is *"far less intuitive than vertical scrolling"* on the
       web and needs a bleed affordance to hint at itself. A grid needs none.
     · BENTO loses here by this page's own settled rule: unequal tiles break a
       comparison, and these are all the same kind of thing at the same aspect.
     · A SLIDESHOW hides content behind interaction, which the JS-visibility rule
       already bars (it is why bento expanders were rejected).
   ⇒ Eight equal cells, all eight visible at once, every one a plain <img> in the HTML.

   🟢 NO CAPTIONS AND NO CONTAINER, which is the container rule applied rather than
   an omission: a container earns its place when there are siblings to confuse, and
   with no captions there is nothing to pair. `film.html` has the same shape.
   ⚠️ THE SCROLL MACHINERY IS GONE ON PURPOSE — no `overflow-x`, no `scroll-snap`, no
   `tabindex`, no `role="region"`. Those existed only because a scroll container that
   answers a pointer alone fails WCAG 2.1.1. A grid has nothing to scroll, so keeping
   them would announce a region that does not exist.
   -------------------------------------------------------------------------- */
/* 🔴 MASONRY, THIRD FRAMING OF THIS ROW. Juan: *"wouldn't a bento be better just to
   show off the whole image?"* The goal he named is NO CROPPING, and a bento is the wrong
   tool for it: 2026 guidance says plainly that "multiple box sizes ... leads to images
   being automatically cropped depending on where they appear, often cutting off
   important parts", because a bento cell's shape is chosen for the composition rather
   than for the picture in it. This page's settled bento rule (it wins for a HUB of
   unlike things) is about GROUPING; it does not buy uncropped images.
   🟢 What does is letting each tile take its own image's aspect — masonry "allows
   images to maintain their original aspect ratios while efficiently filling horizontal
   space", which is the same answer `the-plate-belongs-to-the-image` already reached on
   this repo: hug the artwork, never letterbox it inside a plate.
   ⇒ CSS columns. No JS, no crop, no pad, no letterbox, every image whole.
   ⚠️ READING ORDER IS COLUMN-MAJOR with `columns`, not left-to-right. That is acceptable
   here and only here: the tiles carry no captions and no sequence, so there is nothing
   for a reader to follow. If a caption or an order ever matters in this row, columns is
   the wrong mechanism and it needs a real grid with per-item spans.
   ⚠️ `break-inside: avoid` is load-bearing — without it a figure splits across a column
   boundary and half an image appears at the top of the next column. */
/* 🔴 AUDITED Sep 3 2026: MASONRY SUITS NOTHING ELSE ON THIS SITE, and the reason is
   one property, not a judgement call. Juan asked whether masonry is better suited to
   anything currently using bento. Every bento-ish row was checked:
     /design  .dtiles          4 nav tracks     4 descriptions
     /design  .dgrid--rejects  3 frames         3 captions
     /onword  .dbento--4       4 mark states    4 captions
     /packaging .dmarks-bento  5 marks          5 captions
     /sipful  .dbento--sipmark 2 marks          2 captions
     /sipful  .dbento--sipful  2 studies        2 captions
     /web     .dbento--3       3 renders        3 captions
   🔴 ALL SEVEN CARRY PER-ITEM CAPTIONS, and `columns` reads COLUMN-MAJOR. A caption
   only means anything while the reader can tell which picture it belongs to, so
   column-major order breaks every one of them. **Masonry's gate is CAPTIONS, not the
   like/unlike axis** — which is a different rule from the one the bento decision tree
   gives, and it disqualifies all seven before that tree is even reached.
   🟢 `.dgen` qualifies because it is the ONLY uncaptioned multi-item media row on the
   site. (film.html has 11 uncaptioned figures, but they are `.slide` — a stateful
   carousel where `data-i` order is the component, so masonry is meaningless there.)
   ⇒ Do not re-propose masonry for a captioned row. If one ever wants it, the caption
   has to go first, and then the container rule says the row loses its container too.
   -------------------------------------------------------------------------- */
.dgen {
  columns: 4 200px;
  column-gap: 10px;
  max-width: 1100px; margin: 0 0 8px;
}
@media (max-width: 860px) { .dgen { columns: 3 170px; } }
@media (max-width: 560px) { .dgen { columns: 2 140px; } }
.dgen figure { margin: 0 0 10px; break-inside: avoid; -webkit-column-break-inside: avoid; }
.dgen picture { display: block; }
.dgen img {
  display: block; width: 100%; height: auto;
  border-radius: 4px;
  background: rgba(220, 230, 221, .03);
}
/* the count sits under the grid as a caption on the SET, not on any one frame */
.dgen__note {
  margin: 0 0 22px; font-family: var(--mono); font-size: 11.5px;
  line-height: 1.6; color: var(--fg-dim);
}

/* ═══ A CAPTION IN A SET STARTS ON THE SAME LINE AS ITS SIBLINGS ══════════════
   🔴 ADDED Sep 3 2026, replacing `margin-top: auto` on every caption in this file.
   Juan: *"the first mark has one line of copy while the second has two ... the one
   with less should be centred within its space."* He is right that it looks wrong,
   and the cause is worth writing down because `margin-top: auto` looked harmless.

   MEASURED on /sipful #brand at a 1500px viewport, where the icon caption wraps to
   one line and the lockup caption to two:
     picture bottom   331.4   331.4   <- identical, as the aspect box guarantees
     caption top      348.4   331.4   <- the SHORT one is pushed DOWN 17px
     gap img->cap      17px     0px
   So the one-line caption's only line sat level with the TWO-line caption's SECOND
   line, and a 17px void opened between the icon and its own caption.

   🔴 WHY BOTTOM-ALIGNING WAS NEVER RIGHT HERE. `margin-top: auto` earns its place
   when the MEDIA heights differ and you want the captions to share a baseline. In
   this file the media height is equal by construction every time — `.dbento` sets
   `aspect-ratio: var(--media)` on equal-width cells, `--mixed` and `--ab` give the
   image a definite height. So it never had slack to fix and only ever had caption
   slack to mis-spend.

   🟢 AND THE ANSWER IS TOP, NOT CENTRED, THOUGH CENTRED IS THE BETTER INSTINCT OF
   THE TWO. Three ways to spend that 17px:
     bottom (was)   17 above / 0 below   first lines off by 17px, caption detached
     centred        8.5 / 8.5            first lines off by 8.5px, still detached
     top (now)      0 above / 17 below   FIRST LINES ALIGN EXACTLY, caption attached
   Centring halves the error; it cannot remove it, because any space above the short
   caption is space its first line does not share with its sibling. A reader scans a
   set ACROSS, so two captions that begin on the same line read as one row of text —
   that is the thing worth buying, and only top-align buys it. The leftover then sits
   at the bottom of the shorter card, below the text and inside the border, where it
   reads as slightly deeper bottom padding rather than as a gap.
   📌 This does NOT retire the copy rule — a caption in a set still stays within a
   line of its siblings. But the line COUNT is width-dependent (both /sipful captions
   wrap to two lines at 1032px and split 1/2 at 1500px), so copy alone can never
   guarantee it at every width. The copy rule reduces how often this happens; this
   rule makes it harmless when it does.
   -------------------------------------------------------------------------- */
.dbento .dfig figcaption {
  margin-top: 0 !important;
  padding: 11px 0 13px;
}

/* 🔴 THIS RULE MUST STAY BELOW THE ONE ABOVE, and it did not — which is why Juan
   saw it work on the Kova marks and not on Onword or Sipful. Both selectors are
   (0,2,1), so SOURCE ORDER decides, and the bleed rule was written FIRST and
   silently lost. `.dtile`'s block in this same file already records the identical
   bug, and `css-specificity-loses-silently` is the memory for it. Measured before
   moving it: `paddingLeft: 0px` on `.dbento--bleed .dfig figcaption`.
   ⚠️ The Kova marks looked right the whole time because they are `.dmark`, which
   carries its own padding and never entered this contest — so "it works in one
   place" was evidence of nothing.
   THE RULE ITSELF: bleeding the MEDIA is a claim about the artwork's edge. Text
   has no edge of its own, so the padding guidance (20-24px) applies to a caption
   unchanged, bled tile or not. */
.dbento--bleed .dfig figcaption { padding: 13px clamp(16px, 1.7vw, 22px) 15px; }

/* NARROW. One column. Nothing else to do: the media box is an aspect rather than
   a height, so it shrinks with the cell on its own. */
@media (max-width: 760px) {
  .dbento { grid-template-columns: minmax(0, 1fr); }
  .dbento > * { grid-column: 1 / -1 !important; }
}

/* ─── THE FIVE KOVA MARKS, AND THE ONE PLACE UNEQUAL CELLS EARN IT ────────────
   Juan asked for this one and it is the best case on the site: a wide horizontal
   lockup, a stacked logo, a wordmark, a square icon and the mascot are genuinely
   different SHAPES, so five identical tracks were the layout telling a lie about
   them. Row 1 the two full-word marks 3+3; row 2 the three compact ones 2+2+2.
   🟢 THE `.dmark` PANELS STAY — they carry the plate these alpha marks need and
   they normalise the clear space. The bento changes the TRACK WIDTHS only.
   🟢 AND THE MARKS ARE BIGGER, which Juan asked for directly: "scale them up a
   bit, and give them more padding or gap vertically." `.dmark img` was pinned at
   62px, set once when the cells were a fifth of the row; at 3/6 and 2/6 there is
   room for roughly half again. The height is now a clamp so it grows with the
   cell instead of staying at a number chosen for a narrower one. */
.dmarks-bento { grid-template-columns: repeat(6, minmax(0, 1fr)) !important; }
.dmarks-bento > *:nth-child(1) { grid-column: span 3; }
.dmarks-bento > *:nth-child(2) { grid-column: span 3; }
.dmarks-bento > *:nth-child(3) { grid-column: span 2; }
.dmarks-bento > *:nth-child(4) { grid-column: span 2; }
.dmarks-bento > *:nth-child(5) { grid-column: span 2; }
.dmarks-bento .dmark { padding: clamp(24px, 2.6vw, 38px) clamp(16px, 1.8vw, 26px) 0; }
/* 🟢 AND THE TWO WIDE MARKS GET MORE ROOM ABOVE AND BELOW, which Juan asked for
   directly: "for the more wide marks, like the first two ... adding more gap or
   padding on top of it and below it just to give them a bit more space."
   It is also the correct reading of clear space: the exclusion zone is a RATIO
   of the mark, so the lockup and the standard logo — which are physically the
   largest of the five — need proportionally more, not the same. The three
   compact marks keep the base padding for the same reason. */
.dmarks-bento > *:nth-child(1) .vid__cap,
.dmarks-bento > *:nth-child(2) .vid__cap { padding-bottom: clamp(14px, 1.5vw, 20px); }
.dmarks-bento > *:nth-child(1),
.dmarks-bento > *:nth-child(2) { padding-top: clamp(34px, 3.6vw, 54px); }
.dmarks-bento > *:nth-child(1) img,
.dmarks-bento > *:nth-child(2) img { margin-bottom: clamp(26px, 2.8vw, 38px); }
.dmarks-bento .dmark img {
  height: clamp(74px, 7.4vw, 104px);
  margin-bottom: clamp(18px, 2vw, 26px);
}
/* ⚠️ the spans have to be UNSET on a phone, not just the template — a span of 3
   inside a 2-track grid overflows the row instead of wrapping. */
@media (max-width: 760px) {
  .dmarks-bento { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .dmarks-bento > * { grid-column: span 1 !important; }
  /* ...EXCEPT a last mark with no partner, once this grid is a SHEET. Five cells
     in two columns leaves the fifth alone, and inside one field the empty half of
     that row paints the GRID's background — which is the rule colour, lighter than
     a cell by design because that is what draws the dividers. So the hole reads as
     a pale rectangle the size of a mark. As five separate bordered panels it read
     as nothing at all, which is why this only became a defect when the sheet did.
     ⚠️ `!important` here is not a shortcut, it is the only thing that reaches past
     the declaration directly above. Scoped to `.dgrid--sheet` so the boxed form of
     this grid is untouched. */
  .dgrid--sheet.dmarks-bento > .dmark:last-child:nth-child(odd) { grid-column: 1 / -1 !important; }
}

/* ═══ ONE CONTAINER RULE FOR THE WHOLE OF /design ══════════════════════════════
   🟢 ADOPTED Sep 3 2026, with the rest of the study. Juan: *"other pieces of media have copy along
   with it. Should we not give them a container like we've done to the bentos? Or
   should we take away the containers the bentos have to match the rest? I'm
   wondering what's best to make everything uniform ... and that goes for all the
   pages in design."*

   🟢 THE RESEARCH GIVES THE DECIDING QUESTION, and it is not "do they all look
   the same." A card container *"groups disparate elements as one cohesive unit,
   ensuring users never puzzle over which caption belongs to which picture — the
   frame itself communicates through visual grouping"*, and cards *"are almost
   always shown as a set, siblings side by side."* Mixing contained and bare
   *"requires considering when the visual grouping is necessary for clarity
   versus when bare elements with captions suffice."*
   ⇒ **A CONTAINER EARNS ITS PLACE WHEN THERE ARE SIBLINGS TO CONFUSE.**
     · a row of 2-5 figures, each with its own caption → the frame does the
       pairing, so it gets a container;
     · a single wide figure with one caption under it → nothing to disambiguate,
       so a container is decoration and it stays bare.
   ⇒ So "uniform" here means uniform BY RULE, not uniform by appearance. Every
   multi-item row on /design, /packaging, /onword, /sipful and /web is now a card
   set; every single figure is a bare plate. Counted at the time of writing: 8
   multi-item rows contained, 8 single figures left alone.
   ⚠️ DO NOT "finish the job" by containing the single figures. That is the same
   error in the opposite direction, and the rule above is what tells them apart.

   🟢 `.dmark` was already a container and needs no change — it arrived at the
   same answer independently, for a set of five marks.
   -------------------------------------------------------------------------- */
.dcards .dfig {
  background: rgba(220, 230, 221, .028);
  border: 1px solid rgba(220, 230, 221, .1);
  border-radius: 6px;
  display: flex; flex-direction: column;
  /* 🟢 MEDIA BLEEDS BY DEFAULT, TEXT STAYS INSET — which is the card convention
     and the same rule the bleed block above states. USWDS offers both and names
     the padded one the OPTION ("the inset media option indents the media element
     so it doesn't extend to the edge of the card"), so bleed is the default and
     inset is what needs a reason.
     ⚠️ MEASURED BEFORE SWITCHING, asset by asset: every photograph, render,
     contact sheet, FDA panel and Bear's mark in these rows is OPAQUE and fills
     its own rectangle — corner pixels came back `b4b5b1ff`, `181818ff`,
     `3986c0ff`, `f8ebd8ff`, `3e1a00ff` and so on, never `00000000`. They own
     their edge, so a 20px inset was a frame inside a frame. */
  padding: 0; overflow: hidden;
}
/* the hairline on `.dfig img` exists to keep artwork off the bare phosphor. Inside
   a container it is a second frame 1px from the first, so it comes off — same
   reason `.dbento` drops it. */
.dcards .dfig img { box-shadow: none !important; border-radius: 0; }
/* 🔴 EXCEPT THE LIDS, WHICH I BROKE. `.lid img { border-radius: 50% }` is (0,1,1)
   and `.dcards .dfig img` is (0,2,1) AND later, so the reset won twice over and
   the lids lost their circle clip — they rendered as square artboards with black
   corners, which is exactly what design.css § A LID IS A CIRCLE exists to
   prevent. Juan spotted it and asked whether it was deliberate. It was not.
   ⚠️ Third source-order/specificity casualty of this branch. When adding a
   blanket reset inside a new container class, grep the property first for what
   already sets it — `css-specificity-loses-silently` is the memory. */
.dcards .lid img { border-radius: 50% !important; }
.dcards .dfig .vid__cap {
  margin-top: 0 !important;
  padding: 10px clamp(12px, 1.4vw, 20px) clamp(12px, 1.4vw, 18px);
}

/* 🔴 THREE ROWS KEEP THE INSET, AND EACH HAS A MEASURED REASON — none of them is
   artwork that fills its cell, so there is no edge to bleed to:
   · `--ab`   the four lids are SQUARE artboards clipped to circles by CSS
              (`border-radius: 50%`), so the rectangle is not the artwork. Their
              corner pixels are opaque BLACK and never shown. Bleeding would push
              a circle flush against a straight border.
   🟢 `--tight` WAS THE THIRD AND IS NO LONGER: the Onword pair was recomposited
     onto identical 730x254 canvases (Sep 3 2026), so it now owns a rectangular
     edge like everything else and bleeds. The modifier is deleted. This is the
     shape of the general fix — an inset that exists because the artwork does not
     fill its cell is an ASSET job, not a CSS one.
   · `--mixed` these images use `width: auto` so they hug their own artwork rather
              than filling the track. Bleeding a hugging image is a contradiction.
              It is also where the one genuinely TRANSPARENT asset lives —
              `collateral-merch`, corner `00000000` — and for alpha artwork the
              container IS the solid field the logo guidance asks for on a busy
              ground. */
.dcards.dgrid--ab .dfig,
.dcards.dgrid--mixed .dfig { padding: clamp(12px, 1.4vw, 20px); }
.dcards.dgrid--ab .dfig .vid__cap,
.dcards.dgrid--mixed .dfig .vid__cap { padding: 10px 0 0; }
/* ⚠️ `.dgrid--mixed` sizes its images with `width: auto` so they hug their own
   artwork, and `--tight` gives them a definite height. Both are documented
   layouts and both keep working inside a padded box — the container only adds the
   frame. Do not re-set image sizing here. */
