/*
 * pm-showcase-grid — the showcases page (WO-34, frame 55:1868).
 *
 * Source frame: docs/design/frames/34-showcases.json — the centred heading
 * with the family filter, the three-column staggered grid (each block of
 * nine is three column stacks, gap 8), the CTA band between the two blocks
 * and the 176px "View more" row.
 *
 * Compiled to assets/css/components/showcase-grid.css by
 * src/build-tokens.mjs, which substitutes the breakpoint markers (a comment
 * holding `@bp <name>`) with the pixel value from tokens/tokens.json. Never
 * type a breakpoint width here.
 *
 * Every colour is a --pm-color-* token, and the heading's SIZE is the WO-27
 * typography token — but its leading and tracking are deliberately NOT the
 * WO-27 tokens. Frame 55:1876 measures leading `normal` and no tracking at
 * all, against --pm-leading-tight (1) and --pm-tracking-h1 (-3px), which came
 * from the hero frame. WO-104 scopes the frame's two values to this heading
 * alone; both WO-27 tokens keep their global values. The reasoning sits on
 * the .pm-showcase-grid__heading rule below — read it before "restoring"
 * anything there. No hex value and no literal
 * breakpoint width may appear in this file. Values the token file does not
 * hold (the 898px heading measure, the 6px filter dot, the 176px row) are
 * typed with a comment citing the extract key they come from — the same
 * technique as the button's 14px label.
 */

/*
 * The heading block (34-showcases.json#heading): column centred, gap 32,
 * padding 96 top — the space under the header band. 32 is --pm-space-8, 96
 * is --pm-space-24.
 */
.pm-showcase-grid__head {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--pm-space-8);
	padding-top: var(--pm-space-24);
	text-align: center;
}

/*
 * The eyebrow — the section-header pattern's accent rule + Caption Meta
 * accent (34-showcases.json#heading.eyebrow), the pm-section-header
 * markup's structure with this component's own classes: a 40px, 1px Terra
 * Sienna rule and the caption-size accent text, 16px apart.
 */
.pm-showcase-grid__eyebrow {
	display: inline-flex;
	align-items: center;
	gap: var(--pm-space-4);
}

.pm-showcase-grid__eyebrow-rule {
	flex: none;
	width: 40px;
	height: 1px;
	background-color: var(--pm-color-accent);
}

.pm-showcase-grid__eyebrow-text {
	font-family: var(--pm-font-family-base);
	font-size: var(--pm-font-size-caption);
	font-weight: var(--pm-font-weight-400);
	line-height: var(--pm-leading-normal);
	letter-spacing: var(--pm-tracking-caption);
	text-transform: uppercase;
	color: var(--pm-color-accent);
}

/*
 * The H1 (34-showcases.json#head_section_55_1872.heading, node 55:1876):
 * Roboto Light 56, leading `normal`, NO tracking, uppercase, Volcanic Ink,
 * centred, 898px wide — a 898 × 132 box of TWO lines.
 *
 * Only the SIZE is a WO-27 H1 token, and this comment is the recorded reason
 * the other two are not (WO-104 AC 8). The extract's own `measured.heading.h1`
 * ("56 / 1 / -3") describes the HERO frame's typography, not this one:
 *
 * - Tracking. Node 55:1876 measures 0 — the frame emits no tracking utility,
 *   and solving tracking from the ink in the committed 1:1 PNG gives +0.07px
 *   and +0.13px, zero within a pixel. --pm-tracking-h1's -3px squeezes the
 *   string onto ONE line. CSS's own word for "no tracking" is the initial
 *   value `normal`, so that is what is written here: a keyword, not a literal,
 *   and nothing to name or source in tokens.json.
 * - Leading. The frame renders a 66px baseline pitch at 56px, which is the
 *   keyword `normal` on the committed face (1.171875 × 56 = 65.6).
 *   --pm-leading-tight (1) would give a two-line heading 112px against the
 *   frame's 132 and drag the filter row — and the whole gallery under it —
 *   20px up the page, while AC 1's gap and the line count both still passed.
 *   --pm-leading-normal already holds the keyword and the eyebrow above
 *   already uses it; there is no new token here either.
 *
 * Do NOT "restore" the WO-27 tokens on this rule, and do NOT change their
 * global values: every other h1 on the site still wants them, and the
 * frame-versus-frame contradiction is open question 2 (Eleonor's). WO-104
 * scopes the frame's values to this one heading and nothing else.
 *
 * The 898px measure is the extract's value with no token, typed like the
 * section header's 702px.
 */
.pm-showcase-grid__heading {
	max-width: 898px;
	margin: 0;
	font-family: var(--pm-font-family-base), system-ui, sans-serif;
	font-size: var(--pm-font-size-h1);
	font-weight: var(--pm-font-weight-300);
	line-height: var(--pm-leading-normal);
	letter-spacing: normal;
	text-transform: uppercase;
	color: var(--pm-color-ink);
	/* Long Dutch compounds wrap rather than overflow a 375px viewport
	   (MISTAKES.md §E4). */
	overflow-wrap: break-word;
}

/*
 * The family filter (34-showcases.json#heading.filter): a row of links,
 * gap 32, wrapping below the point where the five labels no longer fit —
 * the 375px judgement, since only the navigation is designed for mobile
 * (MISTAKES.md §C7); Dutch and French labels run 20–30% longer
 * (MISTAKES.md §E4).
 */
.pm-showcase-grid__filter-list {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--pm-space-8);
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * Each item: Roboto Regular 14 uppercase, padding 8px 0, gap 8 between the
 * dot and the label (34-showcases.json#heading.filter). Inactive items are
 * body-text (Texte at 64%); the active item is ink. Hover joins the active
 * colour — colour-only over the motion tokens, the button system's
 * convention. The 14px is typed here: no size token holds it (the button's
 * 14px precedent).
 */
.pm-showcase-grid__filter-item a {
	display: inline-flex;
	align-items: center;
	gap: var(--pm-space-2);
	padding: var(--pm-space-2) 0;
	font-family: var(--pm-font-family-base);
	font-size: 0.875rem;
	font-weight: var(--pm-font-weight-400);
	line-height: var(--pm-leading-tight);
	text-transform: uppercase;
	text-decoration: none;
	color: var(--pm-color-body-text);
	transition-property: color;
	transition-duration: var(--pm-motion-duration-fast);
	transition-timing-function: var(--pm-motion-ease-standard);
}

.pm-showcase-grid__filter-item a:hover {
	color: var(--pm-color-ink);
}

.pm-showcase-grid__filter-item--active a {
	color: var(--pm-color-ink);
}

/*
 * The active item's 6px dot (34-showcases.json#heading.filter — "6px ink
 * circle … before the ACTIVE item only"): the extract's filter-dot.svg as
 * the mask, the colour as the ink token — the shape travels in the asset,
 * the colour stays a token. The 6px is the extract's measured value with no
 * token, typed like the button's 14px.
 */
.pm-showcase-grid__filter-item--active a::before {
	content: "";
	flex: none;
	width: 6px;
	height: 6px;
	background-color: var(--pm-color-ink);
	-webkit-mask-image: url("../../img/filter-dot.svg");
	mask-image: url("../../img/filter-dot.svg");
	-webkit-mask-size: 6px 6px;
	mask-size: 6px 6px;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
}

/*
 * The filter-change focus target (WO-34 AC 6): the screen-reader heading
 * the JS writes the count into and moves focus to. The WordPress utility
 * class, scoped to this component — visually hidden, still announced.
 */
.pm-showcase-grid .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/*
 * The grid (34-showcases.json#grid): full bleed — the scaffold body
 * constrains content to a centred 72rem column, and the frame's gallery
 * runs edge to edge (the pm-cta-band technique).
 */
.pm-showcase-grid__grid {
	width: 100vw;
	margin-inline: calc(50% - 50vw);
}

/*
 * Each block of nine: three columns, gap 8
 * (34-showcases.json#grid.layout — "three columns, gap 8, full bleed; each
 * column a stack gap 8"). 8 is --pm-space-2. The stagger comes from the
 * slot ratios the widget writes onto each card, not from the grid.
 *
 * `align-items: start` and the vertical padding belong to the continuous
 * column drift (ADR 0014 consequence 4, WO-103a §B). `display: grid` STAYS:
 * `grid-template-columns` is the only implementation of both the three-track
 * gate parallax.js reads and the 1024/768 collapse below, and on a flex
 * container it computes to `none`.
 *
 * - `align-items: start` makes a column's box BE its content height. Grid's
 *   default `stretch` makes every `__col` box as tall as the block, so
 *   clipping and offsetting a column would move a box that is not the content.
 * - `padding: var(--pm-space-12) 0` — 48px, and it does two jobs that are now
 *   the same number. It is the frame's OWN spacing between the filter row and
 *   the gallery (34-showcases.json#head_section_55_1872
 *   .gap_filter_row_to_gallery; the client's ruling of 2026-10-01, "use the
 *   spacing between tabs and images that we have in design"), and it is the
 *   room the drift moves into. The clamp came down to fit it —
 *   --pm-motion-distance-gallery-column-clamp is 38px, so the largest upward
 *   excursion still sits 10px inside this box. ADR 0014 consequence 4d, which
 *   SUPERSEDES 4c and the 120px it accepted; the deviation 4c recorded (an 8px
 *   block-to-band seam becoming 120px) is not this build's any more.
 *
 *   It is UNCONDITIONAL — the same 48px at every width — because it is the
 *   page's design spacing, not drift room for the widths where the drift runs.
 *   The old `padding-block: 0` below the laptop breakpoint is deleted for that
 *   reason (WO-104 AC 1): left in place it made the gap 0px below 1024 and put
 *   the cards flush against the filter labels.
 *
 *   Room of some kind is still needed wherever the drift DOES run: without it
 *   `overflow: clip` clips to a padding box that IS the cards, so a drifting
 *   column loses card pixels; and because `__grid` declares no gap and `__head`
 *   no bottom margin, a transformed `__col` would paint an opaque photo over
 *   the filter row in the first frame the visitor sees.
 * - `overflow: clip`, NEVER `hidden`: `hidden` creates a scroll container, and
 *   this block is full of focusable cards — a scrollable ancestor lets the
 *   browser scroll a focused card into view and shift the whole composited
 *   stack. `clip` does not.
 *
 *   It stays deliberately UNCONDITIONAL, and WO-104 restates WHY, because the
 *   reason WO-103a recorded no longer exists: the padding does not disappear at
 *   the breakpoint any more, so there is no 150ms window in which a stale
 *   transform outlives its room. The clip must still never be scoped, for
 *   three reasons that do not depend on that window:
 *     - the clip is the ONLY thing between a transformed column and the filter
 *       row, and the states in which a transform EXISTS are not the states the
 *       breakpoint describes. `parallax.js` never clears `_cur` on a resize, so
 *       a drag from 1440 down across the breakpoint keeps painting the wide
 *       viewport's offset for up to RESIZE_DEBOUNCE_MS before
 *       `computeGeometry()`'s deactivation branch resets it.
 *     - the clamp is a TOKEN, and 48px of room is a much smaller margin than
 *       120px was. Raise --pm-motion-distance-gallery-column-clamp past 48 in
 *       tokens.json and every column overruns this box on the next build; the
 *       clip is what makes that a clipped card rather than a photo over the
 *       filter row.
 *     - `overflow` inside a media query is what WO-103a AC B2 calls "the real
 *       test", and WO-104 AC 8 keeps it load-bearing. The E2/B2 supersession
 *       WO-104 AC 1 records covers the PADDING clauses only — it is not
 *       permission to scope the clip.
 */
.pm-showcase-grid__block {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--pm-space-2);
	align-items: start;
	padding: var(--pm-space-12) 0;
	overflow: clip;
}

/*
 * The hidden attribute must win over the display values above — the
 * second block ships hidden (the "View more" fills it) and the JS
 * re-hides it on a filter change. Without this, display: grid/flex beats
 * the UA's [hidden] rule and the empty block shows as a gap.
 */
.pm-showcase-grid__block[hidden],
.pm-showcase-grid__more[hidden] {
	display: none;
}

.pm-showcase-grid__col {
	display: flex;
	flex-direction: column;
	gap: var(--pm-space-2);
}

/*
 * The entrance-parallax hint (WO-53): `assets/js/parallax.js` toggles this
 * class on a block's three columns only while that block intersects the
 * viewport — never on the middle column's own transform (it never moves)
 * nor while the block sits off-screen, so `will-change` never lingers on
 * a layer that is not about to be transformed.
 */
.pm-showcase-grid__col--parallax-active {
	will-change: transform;
}

/*
 * Two columns below laptop, one below tablet (WO-34 AC 3; the stacking
 * order is DOM order — the third column stack wraps below the first, the
 * recorded decision). The card fills whatever width the column gives it.
 */
@media (max-width: 1024px) {
	.pm-showcase-grid__block {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (max-width: 768px) {
	.pm-showcase-grid__block {
		grid-template-columns: 1fr;
	}
}

/*
 * The band's slot between the two blocks. The band itself is the
 * pm-cta-band component (its own full-bleed, its own padding); the slot
 * only carries the component in the grid's flow.
 */

/*
 * The "View more" row (34-showcases.json#view_more): 176 high, the
 * tertiary button centred. The 176px is the extract's measured value with
 * no token, typed like the button's 14px label.
 */
.pm-showcase-grid__more {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 176px;
}

/*
 * The no-results line (WO-34 AC 6).
 *
 * TODO(open-question: no-results state undesigned; string is a proposal) —
 * MISTAKES.md §C12 records the showcases filter's no-results state as not
 * designed. The proposal: a plain centred body-text line on the page
 * surface, padded like the heading block, until the designer draws one.
 */
.pm-showcase-grid__empty {
	margin: 0;
	padding: var(--pm-space-24) var(--pm-space-8);
	font-family: var(--pm-font-family-base), system-ui, sans-serif;
	font-size: 1rem;
	font-weight: var(--pm-font-weight-300);
	line-height: var(--pm-leading-body);
	text-align: center;
	color: var(--pm-color-body-text);
}

/*
 * Hover captions (WO-34 AC 2 decision): on THIS page only, the card's
 * caption is hidden until the card is hovered or focused — through the
 * --hover-captions modifier the widget puts on its root, the card component
 * untouched. The fade runs over the motion tokens; reduced motion snaps.
 * Touch devices (no hover) show the caption always — the hiding rule never
 * reaches them.
 */
.pm-showcase-grid--hover-captions .pm-gallery-card__caption {
	opacity: 0;
	transition-property: opacity;
	transition-duration: var(--pm-motion-duration-base);
	transition-timing-function: var(--pm-motion-ease-standard);
}

.pm-showcase-grid--hover-captions .pm-gallery-card:hover .pm-gallery-card__caption,
.pm-showcase-grid--hover-captions .pm-gallery-card:focus-within .pm-gallery-card__caption {
	opacity: 1;
}

@media (hover: none) {
	.pm-showcase-grid--hover-captions .pm-gallery-card__caption {
		opacity: 1;
		transition: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.pm-showcase-grid--hover-captions .pm-gallery-card__caption {
		transition: none;
	}
}

/*
 * Ticket 54 — the filter item's own colour transition (above) had no
 * reduced-motion guard anywhere in this file.
 */
@media (prefers-reduced-motion: reduce) {
	.pm-showcase-grid__filter-item a {
		transition: none;
	}
}
