/*
 * pm-gallery-card — the photo card the whole site is built from (WO-12).
 *
 * Source frames: docs/design/frames/12-gallery-card.json — the design
 * system's gallery-card-tall-image / gallery-card-square (21:114), measured
 * against the homepage's post-feedback gallery-row-1 (55:506), whose caption
 * typography wins (12-gallery-card.json#contradictions[1]).
 *
 * Compiled to assets/css/components/gallery-card.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. No hex value and no literal
 * breakpoint width may appear in this file. The one value the token file
 * does not hold (the 18px caption title) is typed with a comment citing the
 * extract key — the same technique as the button's 14px label.
 *
 * The grid owns the shape (12-gallery-card.json#contradictions[0]): the card
 * fills its width and takes its height from the slot — an aspect-ratio
 * carried in --pm-card-ratio when the slot sets one, otherwise height: 100%
 * of a fixed-height slot. It has no intrinsic ratio of its own.
 */

.pm-gallery-card {
	position: relative;
	display: block;
	box-sizing: border-box;
	/* Without a ratio the card grows with a fixed height the grid sets —
	   height: 100% of its slot (WO-12 AC 3; the pm-products featured card
	   is exactly this slot). */
	height: 100%;
	overflow: hidden;
	/* 12-gallery-card.json#measured.card.radius — 0. */
	border-radius: var(--pm-radius-none);
	/* The missing-image state (WO-12 AC 1): with no <img> the card is the
	   surface-alt block — Powder Blush — and nothing else renders into it. */
	background-color: var(--pm-color-surface-alt);
}

/*
 * With a ratio the card sets its own height from the slot's aspect-ratio,
 * carried in the contract's --pm-card-ratio custom property, written inline
 * by GalleryCard::render. The height: 100% branch gives way — aspect-ratio
 * only drives a box whose height is auto.
 */
.pm-gallery-card--ratio {
	height: auto;
	aspect-ratio: var(--pm-card-ratio);
}

/*
 * A hand-placed widget sits inside Elementor's own wrapper chain
 * (.elementor-widget-pm-gallery-card > .elementor-widget-container), which
 * would otherwise stop the slot's height from reaching the card's
 * height: 100% — the card's children are all absolute, so an unresolved
 * percentage collapses it to 0. The inner wrapper passes the height down;
 * the outer wrapper takes the slot's free main axis through Elementor's
 * own flex custom property (--flex-grow, consumed by frontend.css's
 * .elementor-element rule — a direct flex-grow loses that cascade), so a
 * row slot gives it width and a column slot height. Two classes to beat
 * the (0,1,0) --flex-grow: initial Elementor sets on .elementor-element.
 * In an auto-height slot the chain resolves to auto, which leaves the
 * ratio branch untouched.
 */
.elementor-element.elementor-widget-pm-gallery-card {
	--flex-grow: 1;
}

.elementor-widget-pm-gallery-card > .elementor-widget-container {
	height: 100%;
}

/*
 * The two links above that chain which Elementor keeps at content height,
 * found by the tester's fixed-height 400px column case (12-test.md
 * failure 2): the card rendered 0×0 because the slot's height never
 * crossed either of them.
 *
 * The section's container: a min-height section defaults to
 * elementor-section-items-middle — align-items: center — which holds the
 * column at content height, so nothing below it can fill the slot.
 * Stretching the container lets the column take the slot's height.
 *
 * The column's widget-wrap: Elementor's default is a row flex with
 * align-content: flex-start, which packs the widget's line at content
 * height — and the widget's own children are absolute, so that height is
 * 0. A column flex with align-items: stretch hands the slot's height to
 * the widget, which then takes it through the --flex-grow: 1 above.
 *
 * Both rules scope themselves with :has(.pm-gallery-card--fill) over the
 * direct-child chain (container > column > widget-wrap > widget >
 * widget-container > card), so they match only a slot that directly holds
 * a fill card: pm-products renders its card nested inside its own markup,
 * which never matches, and no other section's alignment moves. The
 * container rule's eight classes also beat the (0,2,0)
 * elementor-section-items-middle default regardless of load order.
 */
.elementor-section .elementor-container:has(> .elementor-column > .elementor-widget-wrap > .elementor-element > .elementor-widget-container > .pm-gallery-card--fill) {
	align-items: stretch;
}

.elementor-widget-wrap:has(> .elementor-element > .elementor-widget-container > .pm-gallery-card--fill) {
	flex-direction: column;
	align-items: stretch;
}

/*
 * The image covers its slot (12-gallery-card.json#measured.card.image —
 * "cover, centred, clipped; the card never distorts the photo"), beneath the
 * caption in paint order.
 *
 * Two-class selector on purpose (MISTAKES.md §C15): Elementor's own
 * `.elementor img { height: auto; max-width: 100% }` is (0,1,1) and would
 * beat a lone `.pm-gallery-card__media` (0,1,0) — the photo would render at
 * natural size instead of filling the card.
 */
.pm-gallery-card .pm-gallery-card__media {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

/*
 * The caption sits over the image at the card's bottom edge, full width
 * (12-gallery-card.json#measured.caption — padding 24, gap 8). It is the
 * card's scrim: the gradient runs over the caption's own box, transparent at
 * its top edge to the scrim role at its bottom — Texte at 70% (WO-30), the
 * gradient's end stop. The transparent end is the keyword: the extract's
 * rgba(0,0,0,0), the same hue at zero alpha, not a colour the palette holds.
 */
.pm-gallery-card__caption {
	position: absolute;
	inset: auto 0 0 0;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--pm-space-2);
	padding: var(--pm-space-6);
	background-image: linear-gradient(to bottom, transparent, var(--pm-color-scrim));
}

/*
 * 12-gallery-card.json#measured.caption.title — H4 Label, Roboto Regular
 * 18 / 1, White (the on-accent role). The homepage frame's typography wins
 * over the design-system frame's Bold 18 (contradictions[1]). No size token
 * holds 18px, so it is typed here citing the extract, like the products
 * card title's 18px and the button's 14px label.
 */
.pm-gallery-card__title {
	margin: 0;
	font-family: var(--pm-font-family-base);
	font-size: 18px;
	font-weight: var(--pm-font-weight-400);
	line-height: var(--pm-leading-tight);
	color: var(--pm-color-on-accent);
	/* A long Dutch title wraps rather than stretching the card
	   (MISTAKES.md §E4). */
	overflow-wrap: break-word;
}

/*
 * 12-gallery-card.json#measured.caption.meta — Caption Meta, Roboto Regular
 * 12 / 1 / 1.5 tracking, uppercase, White at 90% (the on-image-muted role,
 * WO-30). Size, tracking and leading are the caption tokens.
 */
.pm-gallery-card__meta {
	margin: 0;
	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-tight);
	letter-spacing: var(--pm-tracking-caption);
	text-transform: uppercase;
	color: var(--pm-color-on-image-muted);
}

/*
 * WO-12 AC 6 — the linked card's focus ring, WO-09's convention: 2px accent.
 * outline-offset: -2px draws it inside the card, because the card's
 * overflow: hidden would clip an outside ring. Nothing else changes on
 * focus — the hover animation is ticket 50's.
 */
a.pm-gallery-card:focus-visible {
	outline: 2px solid var(--pm-color-accent);
	outline-offset: -2px;
}

/*
 * Ticket 50 — the hover scrim. GalleryCard::render_card writes this span
 * only when the card has a photo, right after the media, so paint order is
 * image, scrim, caption (the mega card's own .pm-mega__scrim, styled in
 * mega-menu.css, is unconditional and unrelated to this one). Rest state:
 * invisible, painted in scrim-card (Texte @32, WO-18's own mega-card scrim
 * value — 50-image-hover.json#hover.scrim). The colour never transitions
 * (ticket 54 AC 2) — only opacity moves, so on a device with no hover this
 * simply never leaves 0.
 */
.pm-gallery-card__scrim {
	position: absolute;
	inset: 0;
	background-color: var(--pm-color-scrim-card);
	opacity: 0;
	transition: opacity var(--pm-motion-duration-slow) var(--pm-motion-ease-standard);
}

/*
 * The media's own scale transition — scoped to every gallery card EXCEPT
 * the mega card, which carries its own duration in mega-menu.css
 * (49-card-hover.json's base vs this ticket's slow, 50-image-hover.json).
 * The scale itself lives only in the gated hover/focus-within rule below;
 * this just describes how the property moves when it changes.
 */
.pm-gallery-card:not(.pm-mega__card) .pm-gallery-card__media {
	transition: transform var(--pm-motion-duration-slow) var(--pm-motion-ease-standard);
}

/*
 * Ticket 50 AC 1 — hover/focus on any gallery card except the mega card
 * (its own rule is in mega-menu.css) and except a card with no photo: the
 * media scales to image-hover (1.23) about its centre, clipped by the
 * card's own overflow: hidden (line 33); the scrim fades to scrim-card.
 * The frame's box never resizes — only its children transform — so the
 * layout never shifts (AC 1's CLS 0).
 *
 * `@media (hover: hover)` keeps this off any device that cannot hover, so a
 * tap can never leave the scale or the scrim stuck "on" (AC 3). The
 * `:has(.pm-gallery-card__media)` gate is the belt on top of the
 * span-not-written rule above: the showcases grid clones this card
 * template client-side and, for a no-image item, its JS removes only the
 * <img> — never the scrim span the template started with — so a hover on
 * that clone must still find no media to gate on. `:not(.pm-mega__card)`
 * keeps this selector off the mega card even though it shares the
 * .pm-gallery-card class and would otherwise tie its own (0,5,0) rule
 * (mega-menu.php:95, mega-menu.css's own rule) — `:focus-within` gives
 * keyboard users the same state (`:focus-visible` is what actually shows
 * inside it, WO-12 AC 6's ring).
 */
@media (hover: hover) {
	.pm-gallery-card:not(.pm-mega__card):has(.pm-gallery-card__media):hover .pm-gallery-card__media,
	.pm-gallery-card:not(.pm-mega__card):has(.pm-gallery-card__media):focus-within .pm-gallery-card__media {
		transform: scale(var(--pm-motion-scale-image-hover));
	}

	.pm-gallery-card:not(.pm-mega__card):has(.pm-gallery-card__media):hover .pm-gallery-card__scrim,
	.pm-gallery-card:not(.pm-mega__card):has(.pm-gallery-card__media):focus-within .pm-gallery-card__scrim {
		opacity: 1;
	}
}

/*
 * Ticket 50 AC 4 — reduced motion drops the transitions AND the scale
 * itself (ticket 54 AC 1: "no movement", not "no animated movement"); only
 * the scrim's colour cue remains, snapping instead of fading because its
 * transition is neutralised too. Placed after the hover:hover block so it
 * wins the (0,5,0) tie on the same selector by source order.
 */
@media (prefers-reduced-motion: reduce) {
	.pm-gallery-card__media,
	.pm-gallery-card__scrim {
		transition: none;
	}

	.pm-gallery-card:not(.pm-mega__card):has(.pm-gallery-card__media):hover .pm-gallery-card__media,
	.pm-gallery-card:not(.pm-mega__card):has(.pm-gallery-card__media):focus-within .pm-gallery-card__media {
		transform: none;
	}
}

/*
 * Responsive (MISTAKES.md §C7 — only the navigation is designed for mobile):
 * none. The card is its slot — it fills whatever width the grid gives it and
 * takes its height from the slot's ratio or fixed height, so it scales to
 * 375px with the grid that owns it. The caption's 24px padding is the
 * frame's measured value and holds on a 375px card.
 */
