/* ==========================================================================
   Shared sections

   Sections that appear on more than one template. They kept their original
   `home-` class names when they were lifted out of home.css so existing markup
   and seeded content stay attached — the prefix is historical, not a statement
   about where they are allowed to run.

   Currently: Featured Talent and Testimonials, used by the homepage and the
   Wedding Artists page (Figma 51284:8961, sections 3 and 4).

   Loaded alongside home.css or wedding-pages.css rather than merged into
   either, so neither page pays for the other's sections.
   ========================================================================== */

/* ==========================================================================
   Featured Talent — Figma 51284:4776 (1440x1159)
   ========================================================================== */

.home-talent {
	padding-block: var(--space-32);
	background-color: var(--c-white);
}

@media (min-width: 64em) {
	.home-talent {
		padding-block: var(--space-64);
	}
}

.home-talent__inner {
	width: 100%;
	max-width: calc(var(--container) + (var(--gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--gutter);
}

/* Stacked on the 428 frame the filter labels sit 12 under the heading, not the
   32 a generic stack gap gives (51289:2630 puts the chips at y=194). */
.home-talent__head {
	display: flex;
	flex-direction: column;
	gap: var(--space-12);
	margin-bottom: var(--space-48);
}

.home-talent__intro {
	display: flex;
	flex-direction: column;
	gap: var(--space-12);
}

/* Figma 51284:4781 — the heading box is 351 wide, which is what breaks it onto
   two lines rather than running the full width of the 507 intro column. */
.home-talent__title {
	max-width: 351px;
	margin: 0;
	color: var(--c-primary-500);
}

/*
 * The 428 frame (51288:1687) sets this heading at ~38px and breaks it after the
 * ampersand. The global display-2xl ramp lands at 28.6 there, which fits on one
 * line inside the 351 box. Ramped to hit 38 at 428 and 48 at 1440, then bounded
 * to phones so the verified desktop size is untouched.
 */
@media (max-width: 63.999em) {
	.home-talent__title {
		font-size: clamp(2rem, 2.111rem + 0.988vw, 3rem);
	}
}

.home-talent__filters {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-24);
}

.home-talent__filter {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.home-talent__filter-label {
	margin: 0;
	font-size: var(--fs-body-xs);
	line-height: 20px;
	color: #303026;
}

.home-talent__chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
	max-width: 414px;
}

/* Figma 51284:4788 — 4/18 padding, 72px radius, 12/20 label. */
.chip {
	min-height: 28px;
	padding: var(--space-4) 18px;
	border-color: #303026;
	border-radius: 72px;
	color: #303026;
	font-size: var(--fs-body-xs);
	line-height: 20px;
}

.chip[aria-pressed="true"] {
	background-color: var(--c-primary-500);
	border-color: var(--c-primary-500);
	color: var(--c-white);
}

/* Two-up on phones as the 428 design shows, stepping to the 4-column desktop
   grid. auto-fill with a 240px floor collapsed to a single column at 375. */
.home-talent__grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-24) var(--space-16);
}

@media (min-width: 48em) {
	.home-talent__grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: var(--space-24);
	}
}

.home-talent__empty {
	margin-top: var(--space-24);
	color: var(--c-body);
}

@media (min-width: 64em) {
	/* 507 intro + 48 gap + 779 filters = 1334 of the 1344 design container.
	   The filter block needs its own basis or it overruns and shrinks the
	   heading column. */
	.home-talent__head {
		flex-direction: row;
		align-items: flex-end;
		justify-content: space-between;
		gap: 3.57%; /* 48 / 1344 */
	}

	.home-talent__intro {
		flex: 0 0 37.72%; /* 507 / 1344 */
	}

	/*
	 * Figma 51284:4782 — the two groups are 414 and 341 wide with a 24 gap
	 * (779 total). Their widths set where each label starts.
	 */
	.home-talent__filters {
		flex: 0 1 57.96%; /* 779 / 1344 */
		gap: 3.08%; /* 24 / 779 */
		justify-content: flex-start;
	}

	.home-talent__filter--artist_type {
		flex: 0 0 53.15%; /* 414 / 779 */
	}

	.home-talent__filter--music_type {
		flex: 0 0 43.77%; /* 341 / 779 */
	}

	/*
	 * The design lays each group out as two explicit rows of four. The chip
	 * track is narrower than its group so the wrap lands in the same place —
	 * at the full group width a fifth chip would fit on the first row.
	 */
	.home-talent__chips {
		max-width: 80%;
	}

	.home-talent__grid {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

/* ==========================================================================
   Our Wedding Celebrants — Figma "Container" (1440×725)

   The artist card again, in a 400 column instead of 318: the name goes to the
   display face, the instruments line drops and the disc carries the arrow.
   Everything else — the photo crop, the hover lift — is inherited, so a change
   to the card lands here too.
   ========================================================================== */

.celebrant-list {
	padding-block: var(--space-64);
	background-color: var(--c-white);
}

.celebrant-list__inner {
	display: flex;
	flex-direction: column;
	gap: var(--space-48);
	width: 100%;
	max-width: calc(var(--container) + (var(--gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.celebrant-list__title {
	margin: 0;
	color: var(--c-primary-500);
	text-align: center;
}

/*
 * One card per row on a phone, then capped 400 tracks rather than fractions:
 * the frame shows two cards holding their width against the left of a 1344
 * row, not two halves of it. A third celebrant wraps onto the same track size.
 *
 * The narrow rule is an explicit single column, not auto-fill: `auto-fill` with
 * a zero minimum resolves to as many zero-width tracks as it likes, and every
 * card lands in the same place stacked on top of the others.
 */
.celebrant-list__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--space-24);
}

/*
 * Two flexible columns through the tablet band, then the design's fixed 400
 * tracks once the row is wide enough to hold two of them plus the gap. A
 * capped minmax cannot do both: auto-fill counts repetitions against the track's
 * maximum, so 400 as a max gives one lonely column at 768.
 */
@media (min-width: 48em) {
	.celebrant-list__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 64em) {
	.celebrant-list__grid {
		/* Centred under the centred heading: the frame sets two 400 cards and a
		   24 gap at x=308 of 1440, which is the row centred, not left-aligned.
		   auto-fit, not auto-fill — the latter keeps the empty third track and
		   centres the row against that instead of against the cards. */
		grid-template-columns: repeat(auto-fit, 400px);
		justify-content: center;
	}
}

/*
 * The display face belongs to the Wedding Celebrants page's own 400px card
 * (51293:4302), not to the celebrant wherever it appears: the enquiry frames
 * set the name in the body face at the artist card's size — ink measures 15 on
 * both the step 2 artist name and the step 3 celebrant name, against 24 on the
 * landing page. Excluded here rather than overridden in enquiry.css so the
 * base rule simply stands in the flow.
 */
.card-artist--celebrant:not(.card-artist--select):not(.card-artist--chosen) .card-artist__name {
	font-family: var(--font-display);
	font-size: var(--fs-display-lg);
	line-height: 1.2;
	color: var(--c-body);
}

.card-artist--celebrant .card-artist__foot {
	align-items: center;
}

/* ==========================================================================
   Testimonials — Figma 51288:8311 (1440x820)
   ========================================================================== */

.home-quotes {
	position: relative;
	padding-block: var(--space-32);
	padding-inline: var(--gutter);
	overflow: hidden;
}

/*
 * The rotating wordmark runs off the bottom-right corner of the band; the
 * supplied asset is already cropped to the visible portion, so it sits flush to
 * the right edge and the section's overflow trims the rest.
 */
/*
 * Fitting a circle to the wordmark in the Homepage frame puts its centre at
 * (1358, 767) from the band's top-left with a 118px radius; the build drew the
 * same 118px circle centred at (1357, 649) — right size, right column, 118px
 * too high, which lifted the arc into the quote panel instead of sweeping under
 * its bottom-right corner. The offset is the whole correction.
 */
/*
 * The 428 frames keep it: the same arc, pushed far enough off the corner that
 * only its leading edge is on screen — ~100px wide, crossing the band's bottom
 * edge under the panel's bottom-right corner. It was hidden below 64em, which
 * dropped the mark from the mobile band on every page that carries this
 * section. Offsets are reset to the frame's own at 64em below.
 */
.home-quotes__arc {
	position: absolute;
	right: -108px;
	bottom: -104px;
	width: 210px;
	height: 260px;
	color: rgba(255, 255, 255, 0.9);
	pointer-events: none;
}

.home-quotes__arc .icon {
	width: 100%;
	height: 100%;
}

@media (min-width: 64em) {
	.home-quotes__arc {
		right: 0;
		bottom: -81px;
	}
}

.home-quotes__inner {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-48);
	width: 100%;
	max-width: var(--container);
	margin-inline: auto;
}

/* Figma 51288:8313 — the heading box is 595 wide, which is what breaks it onto
   the designed two lines. */
.home-quotes__title {
	max-width: 595px;
	margin: 0;
	text-align: center;
	color: var(--c-white);
}

.home-quotes__stage {
	display: flex;
	flex-direction: column;
	align-items: center;
	width: 100%;
}

/*
 * The panel itself never moves — it clips the track sliding underneath it, so
 * the translucent ground stays put while the quotes change.
 */
/*
 * Figma 51288:8315 pads the translucent list item 24 at the bottom, so the
 * panel runs on past the photo row and the pills straddle that strip rather
 * than hanging off a hard edge.
 */
.home-quotes__viewport {
	width: 100%;
	padding-bottom: 24px;
	overflow: hidden;
	background-color: rgba(255, 255, 255, 0.15);
}

/*
 * One row, one slide wide each. --slide is set by app.js; keeping the offset in
 * a custom property means the transition is declared once here rather than
 * being written into an inline style on every step.
 *
 * Slides stretch to the tallest of the set, so the panel holds one height for
 * the whole run — a carousel that resizes mid-slide reads as a fault, and it
 * would jog the section below it exactly like the FAQ answers used to.
 */
.home-quotes__track {
	display: flex;
	align-items: stretch;
	translate: calc(var(--slide, 0) * -100%) 0;
	transition: translate 550ms cubic-bezier(0.22, 1, 0.36, 1);
}

.home-quotes__item {
	display: flex;
	flex: 0 0 100%;
	flex-direction: column;
	gap: var(--space-40);
	padding: var(--space-24);
}

/* 330x340 inside the mobile panel (51288:9140) — near square, where the
   desktop frame crops the same photo to a 497x400 landscape. */
.home-quotes__media {
	aspect-ratio: 330 / 340;
}

@media (prefers-reduced-motion: reduce) {
	.home-quotes__track {
		transition: none;
	}
}

.home-quotes__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.home-quotes__body {
	display: flex;
	flex-direction: column;
	gap: var(--space-40);
}

/*
 * The frame drew this at 22/24 around a two-sentence quote. The reviews the
 * client supplied run to several paragraphs, and at that size the longest one
 * fills the panel to the point of being a wall of text, so the scale steps down
 * to 19/20 — the copy still reads as the feature quote it is, and the panel
 * stops dominating the band.
 */
.home-quotes__quote {
	margin: 0;
	font-size: clamp(1.1875rem, 1.1478rem + 0.1482vw, 1.25rem);
	line-height: var(--lh-body);
	color: var(--c-white);
}

.home-quotes__quote p {
	margin: 0;
}

/*
 * A review long enough to run to several paragraphs needs them separated: with
 * margin:0 alone the breaks collapse and the quote reads as one wall of text.
 * `p + p` so the single-paragraph quotes the design was drawn around — which
 * carry no <p> at all — are untouched.
 */
.home-quotes__quote p + p {
	margin-top: var(--space-16);
}

.home-quotes__author {
	font-size: var(--fs-body-xs);
	font-weight: var(--fw-medium);
	line-height: 12px;
	letter-spacing: 1.33px;
	text-transform: uppercase;
	color: var(--c-white);
}

/* Figma overlaps the controls onto the panel by 24px. */
.home-quotes__controls {
	display: flex;
	gap: var(--space-8);
	margin-top: -24px;
}

.btn--white {
	min-width: 160px;
	background-color: var(--c-white);
	color: var(--c-black);
}

.btn--white:hover,
.btn--white:focus-visible {
	background-color: var(--c-primary-100);
	color: var(--c-black);
}

@media (min-width: 64em) {
	.home-quotes {
		padding-block: var(--space-64);
	}

	/* 1114 of the 1344 container; the mobile panel is the full column. */
	.home-quotes__stage {
		max-width: 82.89%;
	}

	/*
	 * stretch, not center: the photo's aspect ratio still sets the row height
	 * for the short quotes the design was drawn around, but a review long
	 * enough to outgrow it now takes the photo up with it. Centred, the photo
	 * floated in the middle of a tall row with dead grey above and below it.
	 */
	.home-quotes__item {
		flex-direction: row;
		align-items: stretch;
		padding: var(--space-40);
	}

	/*
	 * 497 of the 1034 the panel actually offers a column, not of its 1114 outer
	 * width — the 40px padding on each side is not available to share. Against
	 * 1114 the photo came out 461 wide and, through the fixed aspect ratio, 371
	 * instead of 400 tall, which shortened the whole section by 53px.
	 */
	.home-quotes__media {
		flex: 0 0 48.07%; /* 497 / 1034 */
		aspect-ratio: 497 / 400;
	}

	/*
	 * Centred against the photo: with the row now sized by whichever column is
	 * taller, a short quote left to sit at the top hangs off the photo's top
	 * edge with a run of empty grey under it. A long one fills the column and
	 * centring makes no difference.
	 */
	.home-quotes__body {
		flex: 0 1 48.07%;
		justify-content: center;
	}
}

/* Guard the two-column quote panel against a min-content squeeze. */
.home-quotes__body,
.home-quotes__media {
	min-width: 0;
}

/* ==========================================================================
   Real Weddings — Figma 51284:4900 (1440x857)
   ========================================================================== */

.home-weddings {
	display: flex;
	flex-direction: column;
	gap: 65px;
	padding-block: var(--space-64) var(--space-24);
	background-color: var(--c-white);
}

.home-weddings__head {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-8);
	padding-inline: var(--gutter);
	text-align: center;
}

.home-weddings__title {
	margin: 0;
	color: var(--c-primary-500);
}

.home-weddings__rail {
	display: flex;
	gap: 19px;
	margin: 0;
	padding: 0;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	list-style: none;
}

.home-weddings__rail::-webkit-scrollbar {
	display: none;
}

/*
 * flex-shrink is 0 on every slide so the row keeps its designed widths and the
 * rail scrolls instead of squeezing. That replaces the min-width the featured
 * slide used to carry, which cannot be animated: it would clamp the width to
 * 888 the instant the class changed and the expansion would jump rather than
 * run.
 */
.home-weddings__slide {
	position: relative;
	flex: 1 0 257px;
	height: 585px;
	scroll-snap-align: start;
	overflow: hidden;
}

/*
 * The rail is full-bleed, so above 1440 something has to take up the slack.
 * It is the narrow slides that grow, not the wide one — the wide one is pinned
 * at 888 deliberately.
 *
 * A 3:2 photo at 585 tall is 877 wide, so 888 is the most the expanded slide
 * can be before the picture has to stretch to fill it. Letting the wide slide
 * absorb the slack instead pushed it to 1368 at 1920 and brought the hover zoom
 * straight back. Growing the narrow ones keeps every slide inside the picture's
 * natural width up to about a 2680px viewport.
 *
 * At 1440 the widths already total the frame exactly, so there is no slack to
 * distribute and the design's 257 / 888 / 257 is untouched.
 */
.home-weddings__slide.is-featured {
	flex-grow: 0;
	flex-basis: 888px;
}

.home-weddings__link {
	display: block;
	width: 100%;
	height: 100%;
}

/*
 * Cursor cue — a pill that rides the pointer across a slide.
 *
 * app.js writes --cue-x / --cue-y and eases them, so the pill trails the
 * pointer slightly rather than being welded to it; that lag is what stops it
 * feeling like a tooltip. Centred on the point, clipped by the slide's own
 * overflow, and pointer-events: none so it never intercepts the link.
 *
 * Hidden until .is-cued is set, which only happens on a fine pointer — there is
 * no cursor to follow on a touch screen.
 */
.home-weddings__cue {
	position: absolute;
	top: 0;
	left: 0;
	z-index: 1;
	translate: calc(var(--cue-x, 50%) - 50%) calc(var(--cue-y, 50%) - 50%);
	padding: 0.7em 1.4em;
	border-radius: var(--radius-pill);
	background-color: var(--c-primary-800);
	color: var(--c-white);
	font-family: var(--font-body);
	font-size: var(--fs-body-lg);
	line-height: 1.3;
	white-space: nowrap;
	opacity: 0;
	scale: 0.8;
	pointer-events: none;
	transition: opacity 220ms cubic-bezier(0.22, 1, 0.36, 1),
		scale 320ms cubic-bezier(0.34, 1.4, 0.64, 1);
}

.home-weddings__slide.is-cued .home-weddings__cue {
	opacity: 1;
	scale: 1;
}

@media (prefers-reduced-motion: reduce) {
	.home-weddings__cue {
		transition: opacity 220ms linear;
		scale: 1;
	}
}

.home-weddings__slide img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/*
	 * The slide is 257 wide at rest and 888 when it opens, against a fixed 585
	 * height, so a portrait source has no vertical overflow narrow and a great
	 * deal of it wide — the anchor has to hold the faces in the open state,
	 * which is the tight one. Measured on both portrait slides at 257 / 385 /
	 * 888: 5 keeps every face in at all three, 25 loses them once the slide
	 * opens. The landscape slides cover this box with no vertical overflow at
	 * any width, so this moves only the portraits.
	 */
	object-position: 50% 5%;
}

/*
 * The picture is cropped to whatever width its slide currently is, which is
 * what the design does: its narrow slides are tight portrait crops and its wide
 * one is a landscape crop of the same photo, at visibly different zooms.
 *
 * Sizing the picture off the slide's fixed height instead would hold one scale
 * through the expansion, but it also shows the whole frame at every width —
 * measured about 26% wider than the design's crop. The design's framing wins;
 * the two cannot both be had, because matching it at 257 and at 888 means two
 * different zooms by definition.
 */

.home-weddings__caption {
	position: absolute;
	inset: auto 40px 40px;
	display: flex;
	flex-direction: column;
	gap: 11px;
	color: #f2efea;
}

/*
 * Expanding rail — hover any slide and it becomes the wide one while the rest
 * contract, carrying the caption with it.
 *
 * Done with :has() rather than JS: the rail only needs to know that one of its
 * children is hovered, which is exactly what :has() expresses. Note the
 * specificity — :has() takes the weight of its heaviest argument, so the
 * "everything contracts" rule below scores higher than a plain
 * .slide:hover would. The expanded rule has to repeat the :has() prefix to
 * outrank it.
 *
 * The rail's height is fixed, so this animates width inside a box that never
 * changes size — nothing below the section moves.
 */
@media (hover: hover) and (pointer: fine) {
	/*
	 * Ease-in-out rather than the ease-out used elsewhere in the theme.
	 *
	 * The slide travels 631px, and an ease-out curve puts most of that in the
	 * first third of its duration — at any duration it lands early and then
	 * crawls, which reads as a snap however long you make it. Easing both ends
	 * spreads the movement evenly, so the panel feels like it has weight.
	 */
	.home-weddings__slide {
		transition: flex-basis 900ms cubic-bezier(0.65, 0, 0.35, 1),
			flex-grow 900ms cubic-bezier(0.65, 0, 0.35, 1);
	}

	.home-weddings__caption {
		transition: opacity 400ms cubic-bezier(0.65, 0, 0.35, 1);
	}

	/* At rest only the featured slide is captioned, as the design has it. */
	.home-weddings__slide:not(.is-featured) .home-weddings__caption {
		opacity: 0;
	}

	.home-weddings__rail:has(.home-weddings__slide:hover, .home-weddings__slide:focus-within)
		.home-weddings__slide {
		flex-grow: 1;
		flex-basis: 257px;
	}

	.home-weddings__rail:has(.home-weddings__slide:hover, .home-weddings__slide:focus-within)
		.home-weddings__caption {
		opacity: 0;
	}

	.home-weddings__rail:has(.home-weddings__slide:hover, .home-weddings__slide:focus-within)
		.home-weddings__slide:is(:hover, :focus-within) {
		flex-grow: 0;
		flex-basis: 888px;
	}

	/*
	 * Held back until the slide is most of the way open. The outgoing caption
	 * has no delay, so the old one clears before the new one arrives rather
	 * than the two crossing over mid-move.
	 */
	.home-weddings__rail:has(.home-weddings__slide:hover, .home-weddings__slide:focus-within)
		.home-weddings__slide:is(:hover, :focus-within) .home-weddings__caption {
		opacity: 1;
		transition-delay: 320ms;
	}

	@media (prefers-reduced-motion: reduce) {
		.home-weddings__slide,
		.home-weddings__caption {
			transition: none;
		}
	}
}

.home-weddings__credits {
	font-size: var(--fs-body-sm);
	font-weight: var(--fw-medium);
	line-height: 16px;
	letter-spacing: 2px;
	text-transform: uppercase;
}

.home-weddings__couple {
	font-family: var(--font-display);
	font-size: var(--fs-display-2xl);
	line-height: var(--lh-display);
	letter-spacing: var(--ls-display);
}

/*
 * The couple's own words, under their name — Special Offers "Past Events
 * Highlights" only. The homepage band has no quote, so nothing appears there
 * unless the field is filled and the section is asked for it.
 */
.home-weddings__quote {
	max-width: 520px;
	margin-top: var(--space-8);
	font-family: var(--font-body);
	font-size: var(--fs-body-md);
	line-height: var(--lh-body);
}

@media (max-width: 63.999em) {
	.home-weddings {
		gap: var(--space-40);
	}

	/*
	 * The mobile frame runs three equal 360x585 slides — 1118 of track behind a
	 * 428 window, which is why 15px of each neighbour shows either side of the
	 * 19px gaps. 340x440 was both narrower and 145 shorter than the design.
	 */
	.home-weddings__slide,
	.home-weddings__slide.is-featured {
		flex: 0 0 min(84.1vw, 360px);
		height: 585px;
		scroll-snap-align: center;
	}

	.home-weddings__caption {
		inset: auto var(--space-24) var(--space-24);
	}

	/*
	 * A quote needs about 160px more than the caption alone, and every slide has
	 * to grow together or the row goes ragged — hence the :has() on the rail
	 * rather than on the slide that carries one.
	 */
	.home-weddings__rail:has(.home-weddings__quote) .home-weddings__slide,
	.home-weddings__rail:has(.home-weddings__quote) .home-weddings__slide.is-featured {
		height: 600px;
	}

	.home-weddings__quote {
		font-size: var(--fs-body-sm);
	}
}

/* --- drag to scroll ------------------------------------------------------ */

.rail[data-drag-scroll] {
	cursor: grab;
}

.rail[data-drag-scroll].is-dragging {
	cursor: grabbing;
	scroll-snap-type: none;
	/* Kills text/image selection and the card link firing mid-drag. */
	user-select: none;
}

.rail[data-drag-scroll].is-dragging a {
	pointer-events: none;
}

/*
 * Stacked card (< 48em): the rail gives every card the tallest card's height,
 * and with two auto rows the leftover was split evenly between the photo and
 * the copy — so the photo drifted 360–420px across one rail instead of holding
 * the frame's ratio. Pin the slack to the copy row, which already expects it
 * (.card-package-detail__link has margin-top: auto). Reset to a single row in
 * the 48em two-column block below, where the card is one row of two columns.
 */
.card-package-detail {
	grid-template-rows: auto 1fr;
}

.card-package-detail__media {
	margin: 0;
}

/*
 * Stacked card (< 48em): the photo spans the card, so it takes the 428 frame's
 * near-square crop — 338 x 340 inside the 360-wide card. Measured off
 * "Design Site QA/Special Offers/special-offers-mobile.jpg" (photo box
 * x 55-392, y 1926-2265) and confirmed pixel-identical in
 * "Wedding Packages/wedding-packages-mobile.jpg" (y 1784-2123). The portrait
 * 241/477 below is the *two-column* ratio and only applies from 48em up;
 * leaving it on the stacked card made a 358-wide photo ~709px tall.
 */
.card-package-detail__media img {
	width: 100%;
	height: 100%;
	aspect-ratio: 338 / 340;
	object-fit: cover;
	border-radius: var(--radius-lg);
}

.card-package-detail__body {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-16);
	padding: var(--space-16) var(--space-16) var(--space-24);
}

.card-package-detail__title {
	margin: 0;
	color: var(--c-black);
	font-size: var(--fs-display-lg);
	line-height: var(--lh-display);
}

.card-package-detail__text {
	margin: 0;
}

.card-package-detail__text p {
	margin: 0;
}

.card-package-detail__text p + p {
	margin-top: 1em;
}

/* Label / value pairs, the labels bold and the values on a shared left edge. */
.card-package-detail__specs {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: var(--space-8) var(--space-16);
	margin: 0;
}

.card-package-detail__specs dt {
	font-weight: var(--fw-semibold);
}

.card-package-detail__specs dd {
	margin: 0;
}

.card-package-detail__link {
	display: inline-flex;
	align-items: center;
	gap: var(--space-8);
	margin-top: auto;
	padding-block: 11px;
	color: var(--c-primary-500);
	font-family: var(--font-display);
	font-size: var(--fs-body-lg);
	line-height: 1.3;
	text-decoration: none;
}

.card-package-detail__link .icon {
	width: 20px;
	height: 20px;
	transition: transform var(--dur-fast) var(--ease);
}

.card-package-detail__link:hover {
	color: var(--c-primary-800);
}

.card-package-detail__link:hover .icon {
	transform: translateX(2px);
}

@media (min-width: 48em) {
	/* Photo column is 241 of the card's 840. */
	.card-package-detail {
		grid-template-columns: 28.7% minmax(0, 1fr);
		grid-template-rows: auto;
		align-items: stretch;
	}

	/* Portrait crop, because here the photo is that narrow 241 column. */
	.card-package-detail__media img {
		aspect-ratio: 241 / 477;
	}

	/*
	 * On hover the photo column collapses so the copy slides across to the
	 * left edge rather than staying stranded in its half. grid-template-columns
	 * animates between two tracks of the same type, which is what makes this
	 * work without repositioning anything by hand.
	 */
	@media (hover: hover) and (pointer: fine) {
		.card-package-detail {
			transition: grid-template-columns 480ms var(--ease), gap 480ms var(--ease);
		}

		.card-package-detail:hover,
		.card-package-detail:focus-within {
			grid-template-columns: 0% minmax(0, 1fr);
			gap: 0;
		}

		@media (prefers-reduced-motion: reduce) {
			.card-package-detail {
				transition: none;
			}
		}
	}

	.card-package-detail__title {
		font-size: 2rem;
	}
}


/* ==========================================================================
   Article card row — "Wedding Blog & Inspiration" on Resources (Figma
   "Frame 1984078247" 1440×804) and "Continue reading" under an article
   (51284:5455, 1440×769). The card itself is also what the blog index grids.
   ========================================================================== */

.res-blogs {
	padding-block: var(--space-48);
	background-color: var(--c-white);
}

.res-blogs__inner {
	display: flex;
	flex-direction: column;
	gap: var(--space-32);
	width: 100%;
	max-width: calc(var(--container) + (var(--gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.res-blogs__head {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.res-blogs__title {
	margin: 0;
	color: var(--c-primary-500);
}

/* Stacked: heading, cards, then the pill centred beneath them. */
.res-blogs__more {
	order: 3;
	align-self: center;
}

.res-blogs__grid {
	order: 2;
	display: grid;
	gap: var(--space-24);
	margin: 0;
	padding: 0;
	list-style: none;
}

.res-blogs__grid > li {
	display: flex;
}

/* Two-up on a tablet, three-up once the columns can hold the design's card —
   at 768 a three-up row leaves each card about 230 wide and the copy crushes. */
@media (min-width: 48em) {
	.res-blogs__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 64em) {
	.res-blogs__grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (min-width: 64em) {
	.res-blogs {
		padding-block: var(--space-64);
	}

	/* Heading and pill share the first row; the cards span both columns below. */
	.res-blogs__inner {
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto;
		align-items: center;
		gap: var(--space-40) var(--space-32);
	}

	.res-blogs__more {
		order: 0;
		justify-self: end;
	}

	.res-blogs__grid {
		grid-column: 1 / -1;
	}
}

/* --- blog card ----------------------------------------------------------- */

.card-article {
	position: relative;
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	width: 100%;
	padding: 11px;
	/*
	 * A solid #3e3e3e stroke, not the 16% --c-border tint. Sampled off the
	 * Articles and Resources 1440 exports: every card edge reads exactly
	 * rgb(62, 62, 62) with zero deviation along the run (Articles card 1,
	 * top border y=979 and bottom y=1519, x 60–470; Resources card 1, left
	 * x=48 / right x=1391). Matches .card-experience, the same card
	 * construction, corrected to the same value.
	 */
	border: 1px solid var(--c-ground-dark);
	border-radius: var(--radius-lg);
	background-color: var(--c-white);
	transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}

.card-article:hover {
	border-color: var(--c-primary-500);
	box-shadow: var(--shadow-card);
}

.card-article__media {
	margin: 0 0 var(--space-16);
}

.card-article__media img {
	width: 100%;
	height: auto;
	aspect-ratio: 413 / 205;
	object-fit: cover;
	border-radius: var(--radius-md);
}

.card-article__body {
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	gap: var(--space-12);
	padding: 0 var(--space-8) var(--space-8);
}

/*
 * Wraps rather than holding one line at every width: a long category name
 * ("Wedding Planning") plus the date is wider than a card in a three-up grid
 * on a tablet, and nowrap put the date outside the card.
 */
.card-article__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-8) var(--space-12);
	margin: 0;
	font-size: var(--fs-body-sm);
	color: var(--c-body);
}

.card-article__chip {
	display: inline-flex;
	align-items: center;
	min-height: 1.625rem;
	padding: 0 var(--space-12);
	border: 1px solid var(--c-border);
	border-radius: var(--radius-pill);
	line-height: 1;
	white-space: nowrap;
}

.card-article__title {
	margin: 0;
	color: var(--c-primary-500);
	font-family: var(--font-display);
	font-size: var(--fs-display-lg);
	line-height: var(--lh-tight);
	letter-spacing: 0;
}

.card-article__title a {
	color: inherit;
	text-decoration: none;
}

/*
 * Whole-card link. The anchor covers the panel, so the "Read More" row beneath
 * is decoration rather than a second link to the same URL.
 */
.card-article__title a::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
}

.card-article__text {
	margin: 0;
	color: var(--c-body);
	font-size: var(--fs-body-md);
	line-height: var(--lh-body);
}

/* Pushed to the bottom so the link lines up across cards of unequal copy. */
.card-article__link {
	display: inline-flex;
	align-items: center;
	gap: var(--space-8);
	margin-top: auto;
	padding-top: var(--space-16);
	color: var(--c-primary-500);
	font-family: var(--font-display);
	font-size: var(--fs-body-md);
	line-height: 1.4;
}

.card-article__link .icon {
	width: 1.125rem;
	height: 1.125rem;
	transition: transform var(--dur-fast) var(--ease);
}

.card-article:hover .card-article__link .icon {
	transform: translateX(3px);
}

/*
 * Cards in a row line their blurbs — and the Read More under them — up with each
 * other whatever the titles do. The grid's tracks are the card's own rows (media,
 * meta, title, blurb, link) and every card subgrids them, so a one-line title in
 * one card and a three-line title beside it still hand the next row the same
 * baseline. margin-top:auto on the link already pinned that one row to the
 * bottom; everything between the photo and it still drifted.
 *
 * Min-height on the title only fixes the one-versus-two case — at 768 the same
 * headline runs to three lines — and clamping it would truncate copy the client
 * wrote. Rows are assigned rather than auto-placed so a card with no featured
 * image still starts its body on track 2 instead of sliding everything up one.
 *
 * All three grids that hold this card are listed: the Resources blog band and
 * the "Continue reading" rail under an article (res-blogs), the Articles index
 * and its category results (blog-index), and the archive and search fallbacks
 * (page-shell). They are the same two-up-then-three-up grid, so they align the
 * same way. Below 48em each card is the full width and there is nothing beside
 * it to line up with.
 */
@supports (grid-template-rows: subgrid) {
	@media (min-width: 48em) {
		:is(.res-blogs__grid, .blog-index__grid, .page-shell__grid) > li,
		:is(.res-blogs__grid, .blog-index__grid, .page-shell__grid) > li > .card-article {
			display: grid;
			grid-row: span 5;
			grid-template-rows: subgrid;
			/* The media's own 16 bottom margin used to open this gap. */
			row-gap: 0;
		}

		:is(.res-blogs__grid, .blog-index__grid, .page-shell__grid) .card-article__media {
			grid-row: 1;
		}

		:is(.res-blogs__grid, .blog-index__grid, .page-shell__grid) .card-article__body {
			display: grid;
			grid-row: 2 / span 4;
			grid-template-rows: subgrid;
			row-gap: var(--space-12);
		}
	}
}

/* ==========================================================================
   LIVE from a #JANDAFAMJAM

   Resources — Figma "Frame 1984078253" 51284:5270 (1440×624): three clips on
   white with a "More Videos" pill. Gallery — 51284:5894 (1440×1008): the same
   band on the off-white ground, with intro copy and six clips that page.
   ========================================================================== */

.res-videos {
	padding-block: var(--space-48);
	background-color: var(--c-white);
}

.res-videos__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-32);
	width: 100%;
	max-width: calc(var(--container) + (var(--gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.res-videos__title {
	margin: 0;
	color: var(--c-primary-500);
	text-align: center;
}

.res-videos__grid {
	display: grid;
	gap: var(--space-24);
	width: 100%;
	margin: 0;
	padding: 0;
	list-style: none;
}

.res-videos__item {
	display: flex;
	flex-direction: column;
	gap: var(--space-12);
}

.res-videos__media {
	position: relative;
	margin: 0;
	overflow: hidden;
	border-radius: var(--radius-md);
	background-color: var(--c-ground-dark);
}

.res-videos__media img {
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
	object-fit: cover;
}

/* The play disc is translucent white, which disappears on a bright poster —
   the frame's own thumbnails are all darker than ours are. */
.res-videos__media::after {
	content: "";
	position: absolute;
	inset: 0;
	background-color: rgba(0, 0, 0, 0.2);
	pointer-events: none;
}

.res-videos__play {
	position: absolute;
	/* Above the scrim, which is a ::after and so paints last. */
	z-index: 1;
	top: 50%;
	left: 50%;
	translate: -50% -50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 64px;
	height: 64px;
	padding: 0;
	border: 0;
	border-radius: var(--radius-circle);
	background-color: rgba(255, 255, 255, 0.35);
	color: var(--c-white);
	cursor: pointer;
	transition: background-color var(--dur-fast) var(--ease), scale var(--dur-fast) var(--ease);
}

.res-videos__play .icon {
	width: 24px;
	height: 24px;
	/* The glyph's own mass sits right of centre, so nudge it back into the disc. */
	margin-left: 3px;
}

.res-videos__play:hover,
.res-videos__play:focus-visible {
	background-color: rgba(255, 255, 255, 0.55);
	scale: 1.06;
}

.res-videos__label {
	margin: 0;
	color: var(--c-primary-500);
	font-family: var(--font-display);
	font-size: var(--fs-display-lg);
	line-height: var(--lh-tight);
	letter-spacing: 0;
}

@media (prefers-reduced-motion: reduce) {
	.res-videos__play {
		transition: none;
	}

	.res-videos__play:hover,
	.res-videos__play:focus-visible {
		scale: 1;
	}
}

@media (min-width: 48em) {
	.res-videos__grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (min-width: 64em) {
	.res-videos {
		padding-block: var(--space-64);
	}

	.res-videos--overlap {
		padding-bottom: calc(var(--space-64) + var(--footer-angle-h));
	}

	.res-videos__inner {
		gap: var(--space-40);
	}
}

/* ==========================================================================
   How It All Works — "From First Message to Last Song"

   Figma: package frames 51284:7831 section 4, Special Offers 51284:6138
   (1440x1418). A #F3FFFF band with the line-art doodle behind the copy column
   and the six stages listed open in the right one.
   ========================================================================== */

.pkg-process { position: relative; padding-block: var(--space-64); overflow: hidden; background-color: var(--c-ground-frost); }

.pkg-process__inner {
	display: grid;
	gap: var(--space-40);
	width: 100%;
	max-width: calc(var(--container) + (var(--gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.pkg-process__copy { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-16); }
.pkg-process__title { margin: 0; color: var(--c-black); }
/* 20/30, a step above the intro copy on the same page — "Because" measures
   80px in the export against 83 rendered at 20px, and the pitch is 30 = 20 x
   1.5. Both methods agree, and both disagree with 18. type-scale-audit #5. */
.pkg-process__text { margin: 0; color: var(--c-body); font-size: var(--fs-body-xl); line-height: var(--lh-body); }

/* The filigree this replaced was a watermark run behind the copy at 20%. The
   Janda note is a logo, so it sits below the copy in its own space at full
   strength instead. Offsets are measured from .pkg-process__copy. */
/* Same reasoning as the FAQ mark: in the flow while the column stacks, a
   watermark once the copy column has space of its own. Sized for the lockup,
   which is wider against its height than the note it replaced. */
.pkg-process__doodles { position: static; width: 96px; margin: 0 auto var(--space-16); pointer-events: none; }
.pkg-process__art { display: block; width: 100%; height: auto; }

.pkg-process__list { margin: 0; padding: 0; list-style: none; }

.pkg-process__item { border-top: 1px solid #dbeaea; padding-bottom: var(--space-32); }


/*
 * Every stage is open in the frame — there is no disclosure control, so the
 * row is a heading rather than a summary and nothing here is interactive.
 */
.pkg-process__question {
	display: grid;
	grid-template-columns: 2.375rem minmax(0, 1fr);
	align-items: start;
	gap: var(--space-8);
	margin: 0;
	padding-block: var(--space-16) var(--space-12);
}

.pkg-process__number { font-family: var(--font-display); font-style: italic; font-size: var(--fs-body-sm); line-height: 1.6; color: var(--c-body); }
.pkg-process__label { font-family: var(--font-display); font-size: var(--fs-display-lg); line-height: 1.3; color: var(--c-black); }

.pkg-process__answer { color: var(--c-body); padding-left: 2.875rem; font-size: var(--fs-body-sm); line-height: 1.6; }
.pkg-process__answer p { margin: 0; }
.pkg-process__answer p + p { margin-top: 0.75em; }

/*
 * Not two columns.
 *
 * Both frames let the heading run to about 615 of the container while the list
 * beside it starts at 519 — they overlap horizontally, which is only possible
 * because the list starts *below* the copy rather than level with it. Laid out
 * as a grid the heading was squeezed into the left track and the section came
 * out ~400px shorter than either frame (1019 against 1418 / 1430), with the
 * doodle left with nothing to sit against.
 *
 * So the copy is a block at the top and the list is a narrower block pushed
 * right underneath it, with the doodle absolutely placed in the space that
 * leaves on the left.
 */
@media (min-width: 64em) {
	.pkg-process { padding-block: var(--space-64) var(--space-96); }
	.pkg-process__inner { display: block; }
	.pkg-process__copy { width: 100%; }
	.pkg-process__title, .pkg-process__text { max-width: 620px; }

	.pkg-process__list {
		width: 66%;
		max-width: 892px;
		margin-left: auto;
		margin-top: var(--space-56);
	}

	/* 452 from the section top in the frame, so 388 from the copy block's, and
	   flush with the frame's left edge — a gutter left of where the copy starts. */
	/* Level with step 03: the mark's centre sits on that step's heading, which
	   lands at ~786 on Celebrants and ~753 on Special Offers — the two pages
	   that draw this band — so one offset serves both. */
	/* Centre in the space left by the list, including when its width caps. */
	.pkg-process__doodles { position: absolute; left: calc((100% - min(892px, 66%)) / 2); top: 620px; width: 190px; margin: 0; transform: translateX(-50%); }

	/* 45px from the divider to the title in the frame, and the answer lines up
	   under the title rather than under the number. */
	.pkg-process__question { grid-template-columns: 45px minmax(0, 1fr); gap: 0; padding-block: var(--space-24) var(--space-12); }
	.pkg-process__answer { padding-left: 45px; font-size: var(--fs-body-md); }
	.pkg-process__label { font-size: 2rem; }
}

/* ==========================================================================
   Signature rail — pinned scroll

   The stage is a tall spacer; the sticky child holds the viewport while the
   rail travels sideways, then releases and the page carries on. app.js owns
   the stage height and the rail offset, so with JS off none of this engages
   and the section is just a heading over a draggable rail.
   ========================================================================== */

.wp-signature__stage {
	position: relative;
}

/* Parks below the sticky bar and gives back the height it takes, so the rail
   still centres in the screen it can actually see. The compact bar height is
   the right one: the section is far enough down that the header is always in
   its scrolled state by the time this pins. */
.is-pinned [data-pin-sticky] {
	position: sticky;
	top: var(--header-h-stuck);
	display: flex;
	flex-direction: column;
	justify-content: center;
	min-height: calc(100svh - var(--header-h-stuck));
	overflow: hidden;
}

/* Driven by scroll position, so its own smooth scrolling has to stand down or
   the two fight and the rail lags behind the page. */
/*
 * Compact pin. The Signature rail fills a screen, so holding a viewport's
 * height there is right; this rail is only ~470 tall, and forcing 100svh
 * padded it out with a screen of dead space. Sized to its own content instead
 * and parked below the header — app.js reads the height back, so the stage
 * still consumes exactly the rail's travel.
 */
.is-pinned[data-pin-compact] [data-pin-sticky] {
	min-height: 0;
	top: calc(var(--header-h-stuck) + var(--space-16));
	padding-block: var(--space-16);
}

.is-pinned [data-drag-scroll] {
	scroll-behavior: auto;
	/* Snapping quantises scrollLeft to card edges, which turns the 1:1 mapping
	   into a series of jumps. The page scroll is the only driver here. */
	scroll-snap-type: none;
	overflow-x: hidden;
	cursor: default;
}

@media (prefers-reduced-motion: reduce) {
	.is-pinned [data-pin-sticky] {
		position: static;
		min-height: 0;
	}

	.is-pinned [data-drag-scroll] {
		overflow-x: auto;
		cursor: grab;
	}
}


/* --- Gallery variant: tinted ground, intro copy, paged in threes ---------- */

.res-videos--mist {
	background-color: var(--c-ground-mist);
}

/* Last band on the page: reserve the footer angle's height so the slope rides
   up into this ground rather than over the pager. */
.res-videos--overlap {
	padding-bottom: calc(var(--space-48) + var(--footer-angle-h));
}

/* Two centred lines under the heading in the Gallery frame. */
.res-videos__text {
	max-width: 760px;
	margin: 0;
	color: var(--c-body);
	font-size: var(--fs-body-md);
	line-height: var(--lh-body);
	text-align: center;
}

.res-videos__pager {
	display: flex;
	justify-content: center;
	gap: var(--space-8);
}

.res-videos__pager .rail-btn--prev .icon {
	transform: scaleX(-1);
}
