/* ==========================================================================
   project.css — shared styles for the generated project detail pages and the
   projects index. Loaded after components.css. Uses ONLY existing tokens.
   (One page-type stylesheet, cached across all 13 generated pages — far
   cheaper than inlining the same CSS into every build output.)
   ========================================================================== */

/* --- Right-edge section rail (01–08), reused from the home treatment ------- */
.rail { right: var(--margin); gap: 0; align-items: flex-end;
        transition: opacity var(--t-micro) var(--ease-out); }
.rail.rail--hidden { opacity: 0; pointer-events: none; }
.rail__item { display: block; min-width: 34px; padding: 12px 0; text-align: right;
  font-family: var(--font-tech); font-size: 12px; letter-spacing: 0.08em;
  color: var(--ink-30); font-variant-numeric: tabular-nums; }
.rail__item.is-active { color: var(--accent); font-size: 15px; }

/* --- Section eyebrow number pairing --------------------------------------- */
.pd-section { padding-block: clamp(72px, 11vh, 160px); }
.pd-section--tight { padding-block: clamp(48px, 7vh, 96px); }

/* --- Section rhythm — ONE rule for every section, every template ----------
   eyebrow  →  16px  →  headline  →  32px  →  first body/lead element.

   Each element owns the gap BELOW it rather than the next one owning the gap
   above, which is what keeps the stack correct in both cases: with a headline
   the eyebrow tightens to 16px, without one it keeps the full 32px and the
   section closes up with no hole left behind.

   The third rule zeroes the top margin of whatever follows, so a component that
   carries its own margin-top (the screens grid, an image row) cannot collapse
   against the eyebrow and reintroduce the drift this replaced — before this,
   Ideation sat 16px from its eyebrow while Viability sat 36px. */
.pd-section .eyebrow { margin: 0 0 32px; }
.pd-section .eyebrow:has(+ .pd-headline),
.pd-section .eyebrow:has(+ .pd-hero__title),
/* §Solution keeps its headline INSIDE the split's text column, so the headline
   is no longer the eyebrow's next sibling — but it still reads as the line
   directly under the eyebrow, and it must sit the same 16px away as every other
   section's does, not the 32px an eyebrow leaves above a block. */
.pd-section .eyebrow:has(+ .editorial-split .pd-headline) { margin-bottom: 16px; }
.pd-headline {
  margin: 0 0 32px; max-width: 22ch;
  font-size: var(--type-h2); font-weight: 500;
  letter-spacing: -0.02em; line-height: 1.12;
}
.pd-section .eyebrow + *, .pd-section .pd-headline + * { margin-top: 0; }

/* --- §01 Hero ------------------------------------------------------------- */
.pd-hero { padding-top: clamp(96px, 14vh, 180px); }
.pd-hero__title { font-size: var(--type-hero); font-weight: 500; letter-spacing: -0.03em;
  line-height: var(--lh-display); margin: 0 0 32px; }
.pd-hero__standfirst { max-width: 52ch; }
.pd-hero__media { margin-top: clamp(40px, 6vh, 80px); }
/* Optional prototype link, under the meta table (projects with meta.prototypeUrl). */
.pd-hero__proto { margin-top: 24px; }

/* --- Meta table already themed in components.css (.meta-table) ------------- */

/* --- Framed hero media (the frame device) --------------------------------- */
.frame-media > .ratio { border-radius: var(--radius); }

/* --- Stat cards: unit + count-up ------------------------------------------ */
.stat-card__num { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.stat-card__unit { font-family: var(--font-tech); font-size: var(--type-h3);
  font-weight: 500; letter-spacing: -0.01em; color: var(--ink-60); }
.band-dark .stat-card__unit { color: color-mix(in srgb, var(--paper-on-dark) 62%, transparent); }

/* --- Placeholders on dark bands read against --dark-surface --------------- */
.band-dark .img-ph { background: var(--dark-surface);
  color: color-mix(in srgb, var(--paper-on-dark) 58%, transparent); }
.band-dark .img-ph__label { color: color-mix(in srgb, var(--paper-on-dark) 45%, transparent); }
.band-dark .img-ph__path { color: var(--paper-on-dark); }
.band-dark .ratio { background: var(--dark-surface); }

/* --- §02 Keynote video ---------------------------------------------------- */
.pd-video { position: relative; }
.pd-video video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pd-play { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center;
  background: transparent; cursor: pointer; }
.pd-play__ring { display: grid; place-items: center; width: 88px; height: 88px;
  border-radius: var(--radius-circle); border: 1px solid var(--paper-on-dark);
  color: var(--paper-on-dark); transition: background var(--t-micro) var(--ease-out),
  color var(--t-micro) var(--ease-out); }
.pd-play:hover .pd-play__ring { background: var(--paper-on-dark); border-color: var(--paper-on-dark);
  color: var(--dark); }
.pd-play__tri { width: 0; height: 0; margin-left: 5px;
  border-top: 11px solid transparent; border-bottom: 11px solid transparent;
  border-left: 18px solid currentColor; }
.pd-video.is-playing .pd-play { display: none; }

/* --- §03 Problem: big stat + personas ------------------------------------- */
.pd-bigstat { text-align: left; }
.pd-bigstat__num { font-family: var(--font-tech); font-size: var(--type-stat); font-weight: 500;
  letter-spacing: -0.03em; line-height: 1; font-variant-numeric: tabular-nums; }
.pd-bigstat__unit { font-family: var(--font-tech); font-size: clamp(24px,3vw,40px); color: var(--ink-60); }
.pd-bigstat__label { margin-top: 16px; color: var(--ink-60); max-width: 30ch; }
.pd-personas { margin-top: clamp(48px, 7vh, 88px); }
.pd-persona h3 { margin: 0 0 8px; }
.pd-persona p { color: var(--ink-60); }

/* Optional user-journey / task-flow / IA diagram, full container width under
   the personas row. Present only on projects that declare problem.journeyImage
   — the plate is wide by nature, so it is never boxed into a column. */
.pd-journey { margin-top: clamp(48px, 7vh, 88px); }

/* --- Image rows (every group of images on a project page) ------------------
   ONE rule, set at build time by scripts/build-projects.js:
     • one image   → .pd-imgrow--solo: full column width, its own aspect ratio,
                     uncropped (the <img> carries .pd-natural).
     • two or more → .pd-imgrow--cN: N equal cells, every cell pinned to the
                     FIRST image's aspect ratio, so all the images in the group
                     share one width AND one height.
   The ideation sketch grid asks for all its images on ONE row (.pd-imgrow--row,
   N = the image count); detail plates and custom-section media use balanced
   columns that wrap without leaving an orphan cell. */
.pd-imgrow { display: grid; gap: var(--gutter); align-items: start;
  margin-top: clamp(40px, 6vh, 72px); }
.pd-imgrow > figure { margin: 0; }
.pd-imgrow--solo, .pd-imgrow--c1 { grid-template-columns: minmax(0, 1fr); }
.pd-imgrow--c2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pd-imgrow--c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pd-imgrow--c4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pd-imgrow--c5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.pd-imgrow--c6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.pd-imgrow--c7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.pd-imgrow--c8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }

/* The optional process strip (the bottom row of the ideation section) swipes
   sideways instead of gridding, and its cells are ALWAYS square — whatever the
   shots' own proportions — so the band reads as one even rhythm. A strip of one
   image is built as .pd-imgrow--solo instead, so it is never marooned. */
.pd-process { display: flex; gap: var(--gutter); overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: 12px; margin-top: clamp(40px, 6vh, 72px); }
.pd-process > figure { flex: 0 0 clamp(280px, 42vw, 460px); scroll-snap-align: start; margin: 0; }

.pd-quote { margin: clamp(48px, 7vh, 88px) 0 0; max-width: 22ch;
  font-size: var(--type-h2); font-weight: 500; letter-spacing: -0.02em; line-height: 1.12; }

/* --- §05 Solution: steps (detail images use .pd-imgrow above) ------------- */
.pd-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gutter);
  margin-top: clamp(48px, 7vh, 88px); }
.pd-step { padding-top: 4px; }
.pd-step__n { display: block; font-family: var(--font-tech); font-size: var(--type-index);
  letter-spacing: 0.06em; color: var(--ink-30); margin-bottom: 12px; }
.pd-step h3 { margin: 0 0 8px; }
.pd-step p { color: var(--ink-60); }

/* Natural-aspect image (solution render / details / features): keep the column
   width, show the image's true aspect uncropped (no .ratio box). */
.pd-natural { display: block; width: 100%; height: auto; border-radius: var(--radius);
  background: var(--surface-2); }

/* --- Dimension-callout hooks (leader line + Space-Grotesk value + tick) ---- */
.pd-dim { position: absolute; z-index: 2; }
.pd-dim--tl { top: 12%; left: 8%; }
.pd-dim--br { bottom: 14%; right: 8%; }

/* --- §06 Features --------------------------------------------------------- */
.pd-feature + .pd-feature { margin-top: clamp(56px, 9vh, 120px); }
.pd-feature h3 { margin: 0 0 16px; font-size: var(--type-h3); }
.pd-feature__body { color: var(--ink-60); max-width: 42ch; margin-bottom: 20px; }

/* A feature carried by a phone screenshot (.pd-shot — set by the build when the
   file measures taller than 1:1.67). At full column width a 9:19.5 capture
   stands ~1580px tall and dwarfs the paragraph beside it, so it renders at 60%
   — height reduced by 40%, aspect untouched — centred in its column. The corner
   is the same 10.7%-of-image-width rule as the screens grid: 60% of the figure
   width, so 0.107 × 60cqw. */
.pd-feature .pd-shot { container-type: inline-size; }
.pd-feature .pd-shot > .pd-natural {
  width: 60%; margin-inline: auto; border-radius: 6.42cqw;
}

/* --- §Screens — device-framed screenshot grid (digital projects) -----------
   Optional section, rendered after §Features for projects carrying screens[].
   Denser than .pd-imgrow (3–4 columns) because screenshots are read as a set,
   and every cell is pinned to the FIRST screen's aspect ratio, so a row of
   portrait captures shares one width and one height.

   The frame is a deliberate, documented exception to two BRAND_GUIDE §5 rules —
   "no strokes" and the 2px radius ceiling — sanctioned for this component only:
   a screenshot needs an edge to read as a device rather than a floating
   rectangle. --surface-2 body, one 1px --line border, 8px radius, and nothing
   else: no notch, no bezel highlight, no fake hardware. */
.pd-screens { display: grid; gap: clamp(20px, 3vh, 32px) var(--gutter);
  align-items: start; margin-top: clamp(40px, 6vh, 72px); }
.pd-screens > figure { margin: 0; }
.pd-screens--c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pd-screens--c4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pd-screen { padding: 8px; background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 8px; }

/* --- Device screenshots: matching the corner the image already has ----------
   A phone capture arrives with its corners baked in and the pixels outside them
   TRANSPARENT. Measured off the source captures, that corner is 10.7% of the
   image's own width — so it cannot be matched with a fixed px radius, which
   would drift every time the column resizes. 1cqw is 1% of the figure's width,
   so the radius here tracks the image exactly at every breakpoint. Without it,
   whatever sits behind the image (its --surface-2 fill, the device frame) shows
   through the transparent corners as four square nubs.
   Applied only where the build measured a genuine device shot (taller than
   1:1.67): a landscape web grab keeps the site's square-cornered treatment. */
.pd-screens--device > figure { container-type: inline-size; }
/* The capture is inset by the frame's 8px padding and 1px border on each side,
   hence (100cqw - 18px); the frame's own corner is that plus the 9px inset, so
   the two curves stay concentric. */
.pd-screens--device .pd-screen { border-radius: calc(0.107 * (100cqw - 18px) + 9px); }
.pd-screens--device .pd-screen > .ratio,
.pd-screens--device .pd-screen .img-ph { border-radius: calc(0.107 * (100cqw - 18px)); }
/* The capture itself keeps the site's square-cornered image treatment, and sits
   on --surface so it separates from the frame body behind it. */
.pd-screen > .ratio { border-radius: var(--radius); background: var(--surface); }
.pd-screen .img-ph { background: var(--surface); }
.band-dark .pd-screen { background: var(--dark-surface);
  border-color: color-mix(in srgb, var(--paper-on-dark) 16%, transparent); }
.band-dark .pd-screen > .ratio, .band-dark .pd-screen .img-ph { background: var(--dark); }
/* Column count steps down with the viewport; phones always land on 2-up, so a
   portrait capture stays a readable size instead of one screen per scroll. */
@media (max-width: 1024px) { .pd-screens--c4 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 640px) {
  .pd-screens--c3, .pd-screens--c4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- §05 Spec sheet (from solution.specs) --------------------------------- */
.pd-specs { margin-top: clamp(48px, 7vh, 88px); max-width: 640px; }
.pd-specs .eyebrow--tick { margin-bottom: 16px; }

/* --- §07 Viability: quadrant SVG + differentiator list -------------------- */
.pd-landscape { display: grid; grid-template-columns: 1.4fr 1fr; gap: clamp(32px, 5vw, 72px);
  align-items: center; margin-top: clamp(48px, 7vh, 88px); }
.pd-quadrant { margin-top: clamp(48px, 7vh, 88px); }
.pd-landscape .pd-quadrant { margin-top: 0; }
.pd-quad { width: 100%; height: auto; max-width: 720px; }
.pd-quad text { font-family: var(--font-sans); }
.pd-diff__title { margin: 0 0 20px; font-size: var(--type-label); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: color-mix(in srgb, var(--paper-on-dark) 58%, transparent); }
@media (max-width: 900px) { .pd-landscape { grid-template-columns: 1fr; gap: 60px; } }

/* --- §08 Outcome: awards + next-project footer ---------------------------- */
.pd-awards { list-style: none; padding: 0; margin: 0; }
.pd-awards li { position: relative; padding-left: 28px; margin-bottom: 12px; }
.pd-awards li::before { content: ""; position: absolute; left: 0; top: 0.5em;
  width: 16px; height: 2px; background: var(--accent); }
.pd-award-year { font-family: var(--font-tech); font-size: var(--type-index);
  color: var(--ink-30); font-variant-numeric: tabular-nums; }
.pd-learnings { color: var(--ink-60); margin-top: 24px; }
.pd-learn-list { margin-top: 24px; }
.pd-learn-list li { color: var(--ink-60); }

.pd-next { display: block; color: inherit; }
.pd-next__inner { max-width: var(--max-w); margin-inline: auto; padding: clamp(56px,9vh,120px) var(--margin);
  display: flex; align-items: baseline; justify-content: space-between; gap: 24px; }
.pd-next__label { font-size: var(--type-label); font-weight: 500; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-60); }
.pd-next__title { font-size: var(--type-h1); font-weight: 500; letter-spacing: -0.025em;
  transition: color var(--t-micro) var(--ease-out); }
.pd-next:hover .pd-next__title { color: var(--accent); }
.pd-next__index { font-family: var(--font-tech); font-size: var(--type-index); color: var(--ink-30); }
.pd-next__arrow { transition: transform var(--t-micro) var(--ease-move); color: var(--ink-30); }
.pd-next:hover .pd-next__arrow { transform: translateX(6px); color: var(--accent); }

/* Image rows keep equal cells at every width — they just take fewer columns as
   the viewport narrows (a narrower cell is still the group's shared aspect).
   A one-row group (.pd-imgrow--row) holds its single row through tablet and only
   rewraps on phones, where 4-plus cells across would be unreadable. */
@media (max-width: 1024px) {
  .pd-imgrow--c3:not(.pd-imgrow--row),
  .pd-imgrow--c4:not(.pd-imgrow--row) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pd-imgrow--c5:not(.pd-imgrow--row),
  .pd-imgrow--c6:not(.pd-imgrow--row) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .pd-imgrow--c7:not(.pd-imgrow--row),
  .pd-imgrow--c8:not(.pd-imgrow--row) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .pd-steps { grid-template-columns: 1fr; }
  .pd-next__inner { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 640px) {
  .pd-imgrow--c2, .pd-imgrow--c3, .pd-imgrow--c4 { grid-template-columns: minmax(0, 1fr); }
  .pd-imgrow--c5, .pd-imgrow--c6,
  .pd-imgrow--c7, .pd-imgrow--c8 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* one-row groups pair up rather than stacking one-per-screen */
  .pd-imgrow--row.pd-imgrow--c3, .pd-imgrow--row.pd-imgrow--c4 {
    grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   CONCEPT TEMPLATE (§3B) — extra components for template:"concept" pages.
   Reuses the developed-template classes wherever the anatomy is identical
   (hero, personas, .pd-steps for the design-language cards, features, quadrant);
   only the genuinely new pieces below get their own rules.
   ========================================================================== */

/* §01 Hero — optional ≤8s ambient turntable loop under the hero still. */
.pd-hero__loop { margin-top: var(--gutter); }
.pd-hero__loop video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* §02 CONCEPT (narrative:"expression") — the design-intent pull-quote takes the
   big stat's column, and the influences row reuses the persona card component. */
.pd-quote--intent { margin: 0; max-width: 20ch; }
.pd-influences { margin-top: clamp(48px, 7vh, 88px); }
.pd-influences .pd-personas { margin-top: 20px; }

/* §03 Ideation / §04 Solution — sketches, process shots and detail plates all
   size themselves through the shared .pd-imgrow rule at the top of this file. */

/* Light-band block label (design-language references, rejected directions). */
.pd-block-title { margin: 0 0 20px; font-size: var(--type-label); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-60); }
.band-dark .pd-block-title { color: color-mix(in srgb, var(--paper-on-dark) 58%, transparent); }
.pd-refs, .pd-rejected { margin-top: clamp(48px, 7vh, 88px); }
.pd-refs__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--gutter); }
.pd-ref { padding-top: 4px; }
.pd-ref__title { margin: 0 0 8px; font-size: var(--type-h3); }
.pd-ref p { color: var(--ink-60); }
.pd-rejected .tick-list { max-width: 68ch; }

/* §06 Opportunity — concept-status note + recognition badges (dark band). */
.pd-status { margin-top: clamp(48px, 7vh, 88px); max-width: 720px; }
.pd-status__title { margin: 0 0 12px; font-size: var(--type-label); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: color-mix(in srgb, var(--paper-on-dark) 58%, transparent); }
.pd-status__body { color: color-mix(in srgb, var(--paper-on-dark) 78%, transparent); max-width: 64ch; }
.pd-badges { display: flex; flex-wrap: wrap; gap: 12px 16px; margin-top: clamp(40px, 6vh, 64px); }
.pd-badge { display: inline-flex; align-items: baseline; gap: 10px; border-radius: var(--radius);
  background: var(--dark-surface); padding: 10px 18px; }
.pd-badge__title { color: var(--paper-on-dark); font-size: var(--type-caption); }
.pd-badge__year { font-family: var(--font-tech); font-size: var(--type-index);
  color: color-mix(in srgb, var(--paper-on-dark) 55%, transparent); font-variant-numeric: tabular-nums; }

/* Recognition with a certificate/badge image: captioned plate beside the list. */
.pd-recognition { display: grid; grid-template-columns: 1.15fr 1fr; gap: clamp(32px, 5vw, 72px);
  align-items: start; margin-top: clamp(48px, 7vh, 88px); }
.pd-recognition__media { margin: 0; }
.pd-recognition .pd-badges { flex-direction: column; align-items: flex-start; margin-top: 0; }

@media (max-width: 900px) {
  .pd-recognition { grid-template-columns: 1fr; gap: 60px; }
}

/* ==========================================================================
   SIDE-BY-SIDE SPACING (detail pages)
   The vertical channel between elements that sit next to each other is 50%
   wider than the base gutter here: 24px → 36px, and the wide editorial gaps
   scale by the same factor. Row spacing is deliberately left alone — this is
   only about the space *between* columns.
   ========================================================================== */
.pd-section { --gutter-side: calc(var(--gutter) * 1.5); }        /* 24 → 36px */

/* shared grids, scoped so the projects index keeps the base gutter */
.pd-section .grid,
.pd-section .editorial-split,
.pd-section .stat-row { column-gap: var(--gutter-side); }
.pd-section .meta-row { column-gap: calc(24px * 1.5); }

/* detail-page grids */
.pd-imgrow,
.pd-process,
.pd-steps,
.pd-screens,
.pd-refs__grid { column-gap: var(--gutter-side); }

/* …but a wider column channel is only a rhythm while the group is ONE row, and
   above 1024px every image group is (the ideation sketches hold their single
   row, and no group's count wraps at its desktop column count). Below that the
   responsive column-drop starts making a matrix — the ideation grid pairs up on
   phones, c5–c8 fall to 2–4 columns — and then both gaps are visible at once:
   36px across against 24px down, which reads as a mistake rather than as
   spacing. A photo matrix wants one even gap, so the group falls back to the
   base gutter at exactly the widths where a second row can appear.
   NOTE for future content: a group of 4+ images outside the ideation grid takes
   2–4 columns at DESKTOP too (see rowCols() in scripts/build-projects.js), and
   would need this evening-up unconditionally rather than under a query. */
@media (max-width: 1024px) {
  .pd-imgrow { column-gap: var(--gutter); }
}

/* the two wide editorial splits on the dark bands */
.pd-landscape,
.pd-recognition { column-gap: clamp(48px, 7.5vw, 108px); }

/* Custom-section media (extraSections) is a .pd-imgrow like every other group;
   the class stays only as a hook for section-specific tweaks. */

/* The right-edge rail is position:fixed and paints ON TOP of the content, so
   reserve its width on the viewports where it is visible (it is hidden ≤1024px).
   Without this, meta values, step cards and differentiator lists run underneath
   the section numbers. */
@media (min-width: 1025px) {
  .pd-section.container,
  .pd-section > .container,
  .pd-next__inner { padding-right: calc(var(--margin) + 48px); }
}

/* ==========================================================================
   PROJECTS INDEX (§2)
   ========================================================================== */
.pi-head { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.pi-count { font-family: var(--font-tech); font-size: var(--type-h3); color: var(--ink-30);
  font-variant-numeric: tabular-nums; }
.pi-filters { display: flex; gap: 28px; flex-wrap: wrap; margin-top: 32px; }
.pi-filter { position: relative; background: none; border: 0; cursor: pointer;
  font-size: var(--type-label); font-weight: 500; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-60); padding: 0 0 6px; }
.pi-filter:hover { color: var(--ink); }
/* Active state: accent + weight. The underline here is the same link-state
   affordance the nav uses — not a decorative rule. */
.pi-filter::after { content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 0;
  background: var(--accent); transition: width 200ms var(--ease-out); }
.pi-filter.is-active { color: var(--accent); font-weight: 600; }
.pi-filter.is-active::after { width: 100%; }

.pi-grid { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(32px,4vw,64px) var(--gutter);
  margin-top: clamp(48px,7vh,88px); }
.pi-tile.is-hidden { display: none; }
/* Every hero on this page shows at ONE ratio — 3:2 landscape — featured or not,
   at every breakpoint, so all 12 projects read as a single set. Size variation
   comes from the featured tiles' width, never from a different crop. */
.pi-tile .tile__media { aspect-ratio: 3 / 2; }
/* Every tile is the same: one uniform 2-column grid down the whole page. The
   first three used to span full width as wide editorial tiles; the page reads
   as one set without that size variation. */
.pi-tile__lead { display: none; }
@media (max-width: 900px) {
  .pi-grid { grid-template-columns: 1fr; }
}
