/* Repertoire archive (/repertoire/).

   Figma: page `3498:11890` → hero `3905:17387`, filter bar `3219:10572`,
   card grid `3219:10733`, «View All» `3219:10741` (1440); page
   `4379:24549` → compact filter bar `4379:24632`, grid `4379:24639`,
   «View All» `4379:24783` (390). No repertoire frame exists at 768 or
   1920, so those two widths are derived from the same Event Card family
   on the only frames that do draw it at those widths — the tablet home
   `4181:18182` (2 columns, card 334, gutter 36, page margin 32, card
   `4184:18352`) and the Full HD home `4190:19490` (3 columns, card 486,
   gutter 32, card `4190:20335`). Every such derived number is marked
   "derived" below.

   Composition (the point of this file): the grid is NOT a uniform 3×N
   grid. Each column starts at its own vertical offset and the rows run on
   a fixed pitch, so the columns read as staggered. Measured from
   `3219:10733` at 1440 (group top y=673):

     column x      119        519        919        (pitch 400 = 376 + 24)
     row 1 y       713 (+40)  858 (+185) 673 (+0)
     row 2 y      1506       1639       1470        (row top 1470 = +797)
     row 3 y      2303       2436       2267        (row top 2267 = +1594)

   i.e. a 797px row pitch, column offsets +40 / +185 / 0 in the first row
   and +36 / +169 / 0 in every row after it (the 4px/16px difference
   between row 1 and rows 2–3 is in the frame itself — hand-placed cards,
   not a rule; reproduced here rather than averaged away, and filed in
   designer-questions.md).

   The offset is a margin on the grid ITEM (`.repertoire-grid__item`), and
   the pitch is that item's `min-height` minus its own offset — so a row
   is exactly `--pitch` tall while the cards fit inside it, and grows
   (never overlaps) if a longer title makes one card taller than the
   design's. The card itself keeps its content height, so DOM order,
   reading order and visual order are the same left-to-right, row-by-row
   order: no `order`, no absolute positioning, no masonry script.

   First batch: 9 cards at ≥1440 (the 1440 frame), 6 below it (the 390
   frame; the tablet home also draws 6). The extra three are served in the
   HTML and hidden by CSS under 1440 — one server response for every
   width, and «Переглянути весь репертуар» (?all=1) reveals the rest.

   Mobile filters (<1440): the design collapses them behind a compact
   «фільтри» bar (`4379:24632`) that opens a full-screen drawer
   (`4379:24951`). Built here as a native <details> disclosure — same bar
   closed (58px, icon + label, centred), the panel opens in flow instead
   of over the page: a drawer needs JS for the focus trap, and the filter
   form has to work with JavaScript off (global-constraints.md). */

/* ------------------------------------------------------------- hero */

.repertoire-hero {
	position: relative;
	padding: 0 16px 102px; /* 4379:24588: content bottom 309 → hero bottom 411. */
	background: var(--color-neutral-100);
}

/* The breadcrumb <nav> is a block whose <ol> is inline-flex; as an
   inline-level box it would sit on a text baseline and carry the line
   box's leading (same fix as afisha.css). */
.repertoire-hero .breadcrumbs {
	display: flex;
}

/* WCAG AA: the shared breadcrumb link colour (--color-neutral-500) is
   4.31:1 on this hero's #F3F4F6 — axe-core flags it at 390. -600 passes
   at 6.9:1. A local override because breadcrumbs.css belongs to the page
   shell; the same substitution belongs there for every grey hero
   (reported, designer-questions.md). */
.repertoire-hero .breadcrumbs a {
	color: var(--color-neutral-600);
}

.repertoire-hero__content {
	display: flex;
	flex-direction: column;
	gap: 20px; /* 4379:24597 itemSpacing. */
	margin-top: 102px; /* breadcrumbs bottom 91 → title top 193 (4379:24599). */
}

.repertoire-hero__title {
	margin: 0;
	color: var(--color-neutral-800);
	font-family: var(--font-title);
	font-size: var(--fs-h1-m); /* H1/H1 M at 390 (60), H1/H1 L at 1440 below. */
	font-weight: 400;
	line-height: var(--lh-h1-m);
	letter-spacing: var(--ls-h1-m);
	text-transform: uppercase;
	text-box: trim-both cap alphabetic; /* leadingTrim CAP_HEIGHT (4379:24599 / 3219:10545). */

	/* One word that never wraps at the design's own size; at 150% text
	   scale (the a11y panel, WCAG 1.4.10 reflow) it is wider than the page
	   and would otherwise push a horizontal scrollbar. */
	overflow-wrap: anywhere;

	/* Kilok caps at 0.70 of the font size in the design (42/60 at 390,
	   143/204 at 1440); the metric substitute (Exo 2) caps at 0.659, so a
	   trimmed box alone lands ~4% short and pulls the whole bottom-aligned
	   hero up with it. `min-height`, not `height`: at the design's size the
	   box is exactly the design's, and at 150% text scale — where the word
	   wraps — it grows with the text instead of letting it overflow (which
	   also leaves the overflowing line hit-testable over the filter bar
	   below). */
	min-height: calc(var(--fs-h1-m) * 0.7);
}

.repertoire-hero__description {
	margin: 0;
	color: var(--color-neutral-800);
	font-size: var(--fs-s-text);
	line-height: var(--lh-s-text);
}

/* ---------------------------------------------------------- filters */

/* A plain GET form: the request drives the query, so the page works with
   JavaScript off. `repertoire.js` only hides the submit button and
   re-submits on change. */
.repertoire-filters {
	margin: 0;
	background: var(--color-neutral-100);
}

.repertoire-filters__disclosure {
	background: var(--color-neutral-100);
}

/* Compact bar, 4379:24632: 58 tall (56 content + the 1px top/bottom
   inside strokes), icon 24 + 6 + label, centred. */
.repertoire-filters__toggle {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	background: var(--color-neutral-100);
	box-sizing: border-box;
	padding: 17px 16px; /* 24px icon + 17 + 17 = the bar's own 58. */
	box-shadow:
		inset 0 1px 0 0 var(--color-neutral-300),
		inset 0 -1px 0 0 var(--color-neutral-300);
	color: var(--color-neutral-800);
	font-family: var(--font-body);

	/* The frame (4379:24634) sets Menu/button to 20/24 here, while the
	   variable's own 390 mode is 16/20; Ruling R13 makes the frame binding
	   where no WCAG failure argues otherwise, and a larger label never
	   does. Written out for that reason, with the mismatch filed in
	   designer-questions.md; `--font-scale` still scales it. */
	font-size: calc(20px * var(--font-scale));
	font-weight: 600;
	line-height: calc(24px * var(--font-scale) * var(--a11y-lh-scale, 1));
	text-transform: uppercase;
	cursor: pointer;
	list-style: none;
}

.repertoire-filters__toggle::-webkit-details-marker {
	display: none;
}

/* «icons/ settings» sits in a 24×24 instance box around a 20×20 glyph
   (4379:24633), so the 6px gap to the label is measured from the box. */
.repertoire-filters__toggle-icon {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 24px;
	height: 24px;
}

.repertoire-filters__toggle-icon svg {
	display: block;
	width: 20px;
	height: 20px;
}

.repertoire-filters__fields {
	display: flex;
	flex-direction: column;
	box-shadow: inset 0 -1px 0 0 var(--color-neutral-300);
}

.repertoire-filters__field {
	box-sizing: border-box;
	display: flex;
	align-items: center;
	gap: 8px; /* icon → label, 3219:10861. */
	min-height: 54px; /* filter row height in the mobile drawer (4379:25175). */
	padding: 12px 16px;
	box-shadow: inset 0 1px 0 0 var(--color-neutral-300);
	color: var(--color-neutral-800);
	font-family: var(--font-body);
	font-size: var(--fs-s-text);
	font-weight: 600;
	line-height: var(--lh-s-text);
	text-transform: uppercase;
}

.repertoire-filters__field > svg,
.repertoire-filters__dropdown > summary > svg,
.repertoire-filters__arrow svg {
	display: block;
	flex: 0 0 auto;
	width: 20px;
	height: 20px;
}

/* Season / Director: a native <select> stripped of its own arrow so the
   cell can draw the design's own «arrow down» (3384:8797) at its right
   edge, like the Genre disclosure next to it. It stays a native control:
   no JS, full keyboard and screen-reader behaviour. */
.repertoire-filters__select {
	flex: 1 1 auto;
	min-width: 0;
	appearance: none;
	border: 0;
	padding: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	text-transform: inherit;
	cursor: pointer;
}

.repertoire-filters__arrow {
	display: block;
	flex: 0 0 auto;
	margin-left: auto;
	width: 20px;
	height: 20px;
}

/* Genre: a native <details> disclosure — the design's dropdown trigger
   («жанри: усі» + arrow) with a checkbox list under it, no JS. */
.repertoire-filters__dropdown {
	display: block;
	padding: 0;
}

.repertoire-filters__dropdown > summary {
	box-sizing: border-box;
	display: flex;
	align-items: center;
	gap: 8px;
	min-height: 54px;
	padding: 12px 16px;
	cursor: pointer;
	list-style: none;
}

.repertoire-filters__dropdown > summary::-webkit-details-marker {
	display: none;
}

.repertoire-filters__dropdown[open] > summary .repertoire-filters__arrow {
	transform: scaleY(-1);
}

.repertoire-filters__genre-list {
	display: flex;
	flex-direction: column;
	gap: 8px;
	margin: 0;
	padding: 0 16px 16px 44px;
	border: 0;
}

.repertoire-filters__genre-list label {
	display: flex;
	align-items: center;
	gap: 12px;
	font-weight: 400;
	text-transform: none;
}

/* Checkbox — 26×26 with a 1px #272B30 inside stroke (3219:10599 /
   4379:25227). The checked mark in the design is a teal (#12AD99) tick
   vector (4379:25228) that could not be exported (the month's one allowed
   Figma request came back rate-limited), so the control keeps its native
   painting and only its accent colour is set — --color-primary-700, not
   the design's -500, which is 2.8:1 against this bar and fails the 3:1
   WCAG AA contrast for a UI element (figma-rules.md: nearest passing
   token; designer-questions.md). */
.repertoire-filters input[type="checkbox"] {
	box-sizing: border-box;
	flex: 0 0 auto;
	width: 26px;
	height: 26px;
	margin: 0;
	accent-color: var(--color-primary-700);
}

.repertoire-filters__field--active label {
	display: flex;
	align-items: center;
	gap: 16px; /* 3219:10598 itemSpacing. */
	cursor: pointer;
}

/* Submit: the no-JS fallback. `repertoire.js` hides it and re-submits the
   form on every change, so with JavaScript on the filters apply
   themselves — the design has no button of its own here. */
.repertoire-filters__actions {
	display: flex;
	align-items: center;
	gap: 24px;
	padding: 16px;
}

/* With JavaScript the submit row leaves the layout — the design's filter
   bar has no button — but it is still the keyboard's commit control and
   the only apply path assistive technology can announce, so it comes back
   the moment anything inside it takes focus (the skip-link pattern, fix
   round 1, Important 5). */
.repertoire-filters__actions--quiet,
.repertoire-filters__submit--quiet {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.repertoire-filters__actions--quiet:focus-within {
	position: static;
	width: auto;
	height: auto;
	margin: 0;
	padding: 16px;
	overflow: visible;
	clip-path: none;
	white-space: normal;
}

.repertoire-filters__submit--quiet:focus-visible {
	position: static;
	width: auto;
	height: auto;
	margin: 0;
	padding: 0 24px;
	overflow: visible;
	clip-path: none;
	white-space: normal;
}

.repertoire-filters__submit {
	box-sizing: border-box;
	min-height: 40px;
	padding: 0 24px;
	box-shadow: inset 0 0 0 1px var(--color-neutral-800);
	background: transparent;
	color: var(--color-neutral-800);
	font-family: var(--font-body);
	font-size: var(--fs-s-text);
	font-weight: 600;
	line-height: var(--lh-s-text);
	text-transform: uppercase;
	cursor: pointer;
}

/* «Скинути фільтри» is not in the design either; it is rendered only
   while a filter is active, in the design system's own link style
   (Details Link: S text semi-bold caps, neutral-800 → primary-700 on
   hover, 600ms ease-out — animations.md). */
.repertoire-filters__reset {
	color: var(--color-neutral-800);
	font-family: var(--font-body);
	font-size: var(--fs-s-text);
	font-weight: 600;
	line-height: var(--lh-s-text);
	text-transform: uppercase;
	transition: color 600ms cubic-bezier(0, 0, 0.58, 1);
}

.repertoire-filters__reset:hover,
.repertoire-filters__reset:focus-visible {
	color: var(--color-primary-700);
}

/* ------------------------------------------------------------- list */

.repertoire-main {

	/* Filter bar bottom 469 → grid top 553 (4379:24640 in the 22.09 .fig).
	   The wrapper 4379:24639 the previous number was read off is gone; its
	   children sit 4px lower now, and the 4px comes back off the «Переглянути
	   весь репертуар» gap below, so the frame's own height is unchanged. */
	padding-top: 84px;
	background: var(--color-base-white);
}

.repertoire-grid {
	--pitch: 0px;

	display: grid;

	/* `minmax(0, 1fr)`, not `1fr`: a `1fr` track resolves to at least its
	   content's min-content width, so a card whose text row cannot shrink
	   (150% text + the dyslexia font) widened the track past the grid itself
	   — 355 inside a 328 grid at 360 (final review I1). The idiom is the
	   sheet's own, see `:216` and the 768+ filter bar. */
	grid-template-columns: minmax(0, 1fr);
	row-gap: 60px; /* 4379:24640 itemSpacing; ≥768 the pitch model replaces it. */
	margin: 0 16px;
	padding: 0;
	list-style: none;
}

.repertoire-grid__item {

	/* --pitch is the grid's, inherited: declaring it here would shadow it. */
	--offset: 0px;

	display: block;
	min-width: 0; /* The other half of the `minmax(0, 1fr)` above: a grid item's own `min-width: auto` would put the card's min-content width back into the track (I1). */
	margin-top: var(--offset);
}

/* First batch: 6 cards under 1440 (4379:24549), 9 from 1440 up
   (3219:10733). The server always sends the 9; the modifier is set only
   while «Переглянути весь репертуар» is on the page, so the three extra
   cards can never end up hidden with no way to reach them. */
.repertoire-grid--batch .repertoire-grid__item:nth-child(n + 7) {
	display: none;
}

.repertoire-empty {
	margin: 0 16px;
	color: var(--color-neutral-600);
	font-size: var(--fs-s-text);
	line-height: var(--lh-s-text);
}

.repertoire-view-all {
	margin: 44px 16px 0; /* card block bottom 4577 → View All 4621 (4379:24783, 22.09). */
}

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

.c-repertoire-card {
	display: flex;
	flex-direction: column;
	gap: 20px; /* 4379:24641 itemSpacing (24 at 1440). */
}

.c-repertoire-card__media {
	position: relative;
	aspect-ratio: 358 / 478; /* «Container» 4316:27947: the poster block including the age band. */
	background: var(--color-neutral-100);
	overflow: hidden;
}

.c-repertoire-card__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* The shared parallax's own default travel is 80px, read off the article
   banner (parallax.css). On a 478-tall poster that is 17% of the box, enough
   for the `object-fit: cover` crop to read as a different photograph at the
   two ends of the pass; 40 keeps it under 9%, which is the same fraction the
   80 is of the article banner's 600. The class rides on the poster itself
   (inc/parts/repertoire.php), so it is 40 on every page that renders this
   card. */
.c-repertoire-card__image.c-parallax {
	--c-parallax-travel: 40px;
}

.c-repertoire-card__image--placeholder {
	background: var(--color-neutral-200);
}

/* Age marker — «Age Rating Container» (4316:27952 at 390, 3274:9341 at
   1440): the shared slanted band (poster-band.css). Measured off the frame
   renders: 32/54 at 390, 34/56 at 1440, 43/72 on the Full HD card — 59.3 /
   60.7 / 59.7 % of the band height, i.e. the band's own default rise of
   60 %; only the height is this card's. */
.c-repertoire-card__age {
	--c-poster-band-height: 54px;
}

.c-repertoire-card__info {
	display: flex;
	flex-direction: column;
	gap: 28px; /* «Information Container» itemSpacing (24 at 1440). */
}

.c-repertoire-card__text {
	display: flex;
	flex-direction: column;
	gap: 16px; /* «Event Information Container» itemSpacing (20 at 1440). */
}

.c-repertoire-card__date {
	margin: 0;
	color: var(--color-neutral-800);
	font-size: var(--fs-s-text);
	font-weight: 400;
	line-height: var(--lh-s-text);
}

.c-repertoire-card__title {
	margin: 0;
	color: var(--color-neutral-800);
	font-family: var(--font-title);
	font-size: var(--fs-h3);
	font-weight: 400;
	line-height: var(--lh-h3);
	letter-spacing: var(--ls-h3);
	text-transform: uppercase;
	text-box: trim-both cap alphabetic; /* leadingTrim CAP_HEIGHT (4316:27958 / 1159:7398). */
	overflow-wrap: anywhere; /* a long title at 150% text scale must wrap, not widen the page. */

	/* The trimmed box ends at the last line's cap, so its height is
	   (lines − 1) × line-height + the font's own cap height. Kilok caps at
	   0.70 of the font size in the design (25/36 at 1440, 20/28 at 390);
	   the metric substitute (Exo 2) caps at 0.659, which would make every
	   card 1.5px shorter than the frame and, stacked, move the block under
	   it. `1cap` is whichever font actually loaded, so this closes the gap
	   for the substitute and becomes zero once the real Kilok is bought. */
	padding-bottom: calc(var(--fs-h3) * 0.7 - 1cap);
}

.c-repertoire-card__title a {
	color: inherit;
	text-decoration: none;

	/* The hover variants of the card set turned «Event Title» #272B30 →
	   #0C9586 in the 22.09 .fig (4351:24489 at Full HD=off, 4351:24389 at
	   Full HD=on); the action stayed ON_HOVER SMART_ANIMATE 600 ms ease-out.
	   #0C9586 is --color-primary-600 and 3.7:1 on white, which fails AA for
	   text, so this is the same substitution details-link.css, event-card.css
	   and the header already make: --color-primary-700 at 5.5:1
	   (designer-questions.md). The literal curve, not the `ease-out` keyword,
	   so `getTiming().easing` reads back the string the motion tests compare
	   against. */
	transition: color 600ms cubic-bezier(0, 0, 0.58, 1);
}

.c-repertoire-card__title a:hover,
.c-repertoire-card__title a:focus-visible {
	text-decoration: underline;
}

/* «Actions Container»: at 390 the two controls split the row (159 + 40 +
   159 of 358, 4134:16196); from 768 up they hug and sit 32 apart. */
.c-repertoire-card__actions {
	display: flex;
	align-items: center;
	gap: 40px;

	/* Two 159-wide halves and a 40 gap are exactly the frame's 358 row, so
	   nothing wraps at 390 or above. It wraps below that, and at 150% text
	   with the dyslexia font, where «Квитки» + «Детальніше» need more than the
	   card is wide — one control per line instead of a page-wide horizontal
	   scroll (final review I1). */
	flex-wrap: wrap;
}

/* Both halves are 159 of the row's 358 in the frame (4134:16196). The
   basis is written out rather than left at 0: with a zero basis Chromium
   hands the row's whole free space to the ticket button and leaves the
   link at its content width (measured 175 + 143). `box-sizing` because
   `.c-ticket` is content-box and carries 32px of its own padding. */
.c-repertoire-card__actions > * {
	box-sizing: border-box;
	flex: 1 1 calc(50% - 20px);
}

/* The box, the type and the arrow are the shared component (details-link.css);
   what is the card's own is the hover — animations.md's «Детальніше» row is
   driven by the card here, not by the link itself. */
.c-repertoire-card:hover .c-details-link,
.c-repertoire-card:focus-within .c-details-link {
	gap: 12px;
	color: var(--color-primary-700);
}

/* Driven by the CARD, like «Детальніше» above: the action in the .fig is
   ON_HOVER on the whole variant, not on the title layer, so pointing at the
   poster colours the title too. */
.c-repertoire-card:hover .c-repertoire-card__title a,
.c-repertoire-card:focus-within .c-repertoire-card__title a {
	color: var(--color-primary-700);
}

/* ------------------------------------------------------------ 768+ */

@media (min-width: 768px) {

	.repertoire-hero {
		padding: 0 40px 102px;
	}

	.repertoire-filters__field,
	.repertoire-filters__dropdown > summary {
		padding-inline: 40px;
	}

	.repertoire-filters__genre-list {
		padding-inline: 68px 40px;
	}

	.repertoire-filters__actions {
		padding-inline: 40px;
	}

	/* Derived (no 768 repertoire frame): the tablet home 4181:18182 —
	   2 columns of 334 with a 36 gutter inside a 32 page margin, cards 80
	   apart, the left column 176 lower than the right one. The pitch is
	   the taller column's (651 + 80). The block keeps that 36px gutter and
	   centres between 769 and 1439 instead of stretching to the page
	   edges — the frame has nothing to say about those widths, and
	   `space-between` turned the gutter into 707px at 1439 (fix round 1,
	   Important 1; designer-questions.md). */
	.repertoire-grid {
		--pitch: 731px;

		grid-template-columns: repeat(2, 334px);
		justify-content: center;
		column-gap: 36px;
		row-gap: 0;
		margin-inline: 32px;
	}

	/* The stagger is a transform, the pitch is the cell's `min-height`.
	   Why not a height: a fixed cell cannot grow, so a card taller than
	   the cell (a longer title, or the theme's own 150 % text scale) used
	   to spill into the row below it and over the block's own bottom edge
	   — the View All then sat 12px on top of the last left-column card at
	   768–1439 (fix round 1, Critical 1 and 3).
	   Why this holds: the row is as tall as its tallest card, and never
	   shorter than the pitch, so the clearance between two cards in one
	   column is `row − card ≥ 0` at any text size, while at the design's
	   own size every row is exactly the pitch and every gap is exactly the
	   frame's (797 − 655 = 142, 797 − 691 = 106 at 1440). The transform
	   does not feed back into the row height, which is what keeps a
	   column's offset from inflating every row by that offset. */
	.repertoire-grid__item {
		box-sizing: border-box; /* so the row-1 guard padding below stays inside the pitch. */
		min-height: var(--pitch);
		margin-top: 0; /* the base rule's margin — from here up the offset is the transform. */
		transform: translateY(var(--offset));
	}

	/* The last visible row instead hangs from its offset (margin, not
	   transform), so the block ends at the lowest card — that is the edge
	   «View All» is measured 56px from. `:nth-last-child` alone would
	   count the three cards CSS hides under 1440 (`--batch`), which is how
	   the overlap got in; in batch mode the visible count is known, so the
	   row is addressed from the front. */
	.repertoire-grid--batch .repertoire-grid__item:nth-child(5),
	.repertoire-grid--batch .repertoire-grid__item:nth-child(6),
	.repertoire-grid:not(.repertoire-grid--batch) .repertoire-grid__item:nth-last-child(-n + 2) {
		min-height: 0;
		margin-top: var(--offset);
		transform: none;
	}

	.repertoire-grid__item:nth-child(2n + 1) {
		--offset: 176px;
	}

	.repertoire-grid__item:nth-child(2n) {
		--offset: 0px;
	}

	.repertoire-empty,
	.repertoire-view-all {
		margin-inline: 32px;
	}

	.repertoire-view-all {
		margin-top: 56px;
	}

	.c-repertoire-card {
		gap: 20px;
	}

	.c-repertoire-card__media {
		aspect-ratio: 334 / 475; /* 4184:18353. */
	}

	.c-repertoire-card__age {
		--c-poster-band-height: 54px;

		padding-left: 12px; /* 4184:18360. */
	}

	.c-repertoire-card__info {
		gap: 24px;
	}

	.c-repertoire-card__text {
		gap: 16px;
	}

	.c-repertoire-card__actions {
		align-self: flex-start; /* hug, like the frame's own Actions Container. */
		flex-wrap: wrap; /* both controls fit the card at the design's size; at 150% they stack. */
		gap: 32px;
	}

	.c-repertoire-card__actions > * {
		flex: 0 0 auto;
	}
}

/* ----------------------------------------------------------- 1440+ */

@media (min-width: 1440px) {

	/* 3905:17387: auto-layout pad=114/56/150/56, gap 10, with «Breadcrumbs»
	   (3327:16585) taken out of the flow (layoutPositioning ABSOLUTE) at
	   the frame's own top-left. Built the same way, so the padding is the
	   design's own number rather than a margin that happens to add up — and
	   the horizontal half of it is the page column (`--page-gutter`,
	   container.css), the same 56 the frame gives every other edge, so the
	   hero follows the header to 112 at 1920 instead of standing 56 px
	   further out (client fix ПР-20). */
	.repertoire-hero {
		display: flex;
		flex-direction: column;
		gap: 10px;
		padding: 114px var(--page-gutter) 150px;
	}

	.repertoire-hero .breadcrumbs {
		position: absolute;
		top: 0;
		left: var(--page-gutter);
		right: var(--page-gutter);
	}

	.repertoire-hero__content {
		display: flex;
		flex-direction: row;
		align-items: flex-end;
		justify-content: space-between;

		/* No gap: «Header Content» (3905:17386) is justify=space-between
		   with no item spacing. A gap would eat into the space the title
		   needs, and with `overflow-wrap: anywhere` on it (the 150% text
		   scale case) the substitute font's 2%-wider word would then wrap
		   at the design's own size instead of staying a text-width
		   residual. */
		gap: 0;
		margin-top: 0;
	}

	.repertoire-hero__title {

		/* The substitute Kilok sets «РЕПЕРТУАР» 2% wider than the design's
		   1044px; without `min-width: 0` that extra width pushes the
		   description off the frame's own right margin instead of staying
		   a text-width residual inside the 24px gap. */
		flex: 0 1 auto;
		min-width: 0;
		min-height: calc(var(--fs-h1-l) * 0.7);
		font-size: var(--fs-h1-l);
		line-height: var(--lh-h1-l);
		letter-spacing: var(--ls-h1-l);
	}

	.repertoire-hero__description {
		flex: 0 0 244px; /* 3219:10549. */
		width: 244px;
	}

	/* Quick Filter Bar 3219:10572: edge to edge (x=0, w=1440), 50 tall
	   (48 + the 1px top/bottom inside strokes), four equal cells divided
	   by a 1px inside stroke on each cell's left edge. No gap: the four
	   360px cells sum to exactly 1440. */
	.repertoire-filters__toggle {
		display: none;
	}

	/* «Фільтри фіксуються в горі для блока з виставами» — note 4549:26009,
	   the same words and the same Locomotive «03. CSS sticky» reference as
	   the afisha note 4496:35843 that filters.css already answers. So the
	   same answer here, at the same width: this is where the design draws
	   the bar as a bar (under 1440 it is the compact «фільтри» toggle, and
	   a sticky collapsed toggle over its own open overlay panel is not
	   something any frame shows).

	   It sticks below the site header and rides with it (note 4549:26006 on
	   this page makes the header sticky); both custom properties are 0 when
	   the header is not sticky — no script, or «Зменшити анімацію» on —
	   which is the plain `top: 0` this would otherwise be. z-index 12, the
	   afisha bar's value: over the cards, under the header's 30. */
	.repertoire-filters {
		position: sticky;
		z-index: 12;

		/* The shift goes in `top`, never in `transform`: a transform on a
	   `position: sticky` box applies while it is still IN FLOW too, so with the
	   header hidden the bar was lifted a whole header height above its own place
	   and lay over the content above it — including, on 390, the hero's focused
	   accordion toggle (fix round 1, C1: WCAG 2.4.11, accessibility-ua §3.5).
	   `top` on a sticky box does nothing until it actually sticks. The
	   `transform` argument site-header.css makes for the header itself (its
	   panels are `position: fixed` children) does not apply here: none of these
	   bars has a fixed descendant — the afisha dropdowns are `absolute` inside
	   the bar — and using `top` also stops the bar being a containing block for
	   one. */
		top: calc(var(--podil-header-h, 0px) + var(--podil-header-shift, 0px));
		transition: top 300ms cubic-bezier(0, 0, 0.58, 1);
	}

	.repertoire-filters__disclosure::details-content {
		content-visibility: visible;
	}

	.repertoire-filters__fields {
		display: grid;
		background: var(--color-neutral-100);
		grid-template-columns: repeat(4, minmax(0, 1fr)); /* not `1fr`: a long <option> would widen its own track. */
		padding-block: 1px; /* the bar is 50 with its 1px strokes drawn inside; the cells are 48. */
		box-shadow:
			inset 0 1px 0 0 var(--color-neutral-300),
			inset 0 -1px 0 0 var(--color-neutral-300);
	}

	.repertoire-filters__field {
		min-height: 48px;
		min-width: 0;
		padding: 0;
		box-shadow: inset 1px 0 0 0 var(--color-neutral-300);
	}

	.repertoire-filters__field:not(.repertoire-filters__dropdown),
	.repertoire-filters__dropdown > summary {
		min-height: 48px;
		padding: 12px 24px;
	}

	/* The toggle's 26px box is taller than the 48px cell's 12px padding
	   allows; in the frame the cell stays 48 and the box sits 11 from
	   each edge (3219:10599 at y=555 in a 544–592 cell). */
	.repertoire-filters__field.repertoire-filters__field--active {
		padding-block: 11px;
	}

	.repertoire-filters__dropdown {
		position: relative;
	}

	/* The genre list is the only part of the bar that is not 48px tall:
	   open, it drops out of the row over the cards, the way the design's
	   own dropdown does (3222:11825 on the afisha). */
	.repertoire-filters__genre-list {
		position: absolute;
		z-index: 2;
		top: 100%;
		left: 0;
		min-width: 100%;
		padding: 16px 24px;
		background: var(--color-neutral-100);
		box-shadow: inset 0 0 0 1px var(--color-neutral-300);
	}

	.repertoire-filters__actions {
		padding-inline: var(--page-gutter);
	}

	.repertoire-main {
		padding-top: 80px; /* filter bar bottom 593 → grid top 673 (3219:10734). */
	}

	/* 3219:10733: three 376 columns at x = 119 / 519 / 919 (pitch 400),
	   row pitch 797, column offsets +40 / +185 / 0 in row 1 and
	   +36 / +169 / 0 after it.

	   The block is centred, with equal gutters at every width from here up
	   (client fix ПР-3: a fixed left margin hugged the left of any viewport
	   wider than the frame — 136 / 520 at ≈1920). 3219:10733 itself sits at
	   119 → 1295 of 1440, 13px left of centre — a hand-placed group; the
	   client's instruction is equal gutters, the 13px is filed in
	   designer-questions.md. */
	.repertoire-grid {
		--pitch: 797px;

		grid-template-columns: repeat(3, 376px);
		column-gap: 24px;
		row-gap: 0;
		width: 1176px;
		margin-inline: auto;
	}

	.repertoire-grid--batch .repertoire-grid__item:nth-child(n + 7) {
		display: block;
	}

	/* Three columns here, so the last visible row moves: items 5 and 6 go
	   back to the pitch, items 7–9 (or the last three in the full list)
	   are the ones that hang from their offset. */
	.repertoire-grid--batch .repertoire-grid__item:nth-child(5),
	.repertoire-grid--batch .repertoire-grid__item:nth-child(6),
	.repertoire-grid:not(.repertoire-grid--batch) .repertoire-grid__item:nth-last-child(-n + 2) {
		min-height: var(--pitch);
		margin-top: 0;
		transform: translateY(var(--offset));
	}

	.repertoire-grid--batch .repertoire-grid__item:nth-child(7),
	.repertoire-grid--batch .repertoire-grid__item:nth-child(8),
	.repertoire-grid--batch .repertoire-grid__item:nth-child(9),
	.repertoire-grid:not(.repertoire-grid--batch) .repertoire-grid__item:nth-last-child(-n + 3) {
		min-height: 0;
		margin-top: var(--offset);
		transform: none;
	}

	.repertoire-grid__item:nth-child(3n + 1) {
		--offset: 36px;
	}

	.repertoire-grid__item:nth-child(3n + 2) {
		--offset: 169px;
	}

	.repertoire-grid__item:nth-child(3n) {
		--offset: 0px;
	}

	/* Row 1 hangs 4px (column 1) and 16px (column 2) lower than the rows
	   under it, so a card that outgrows the pitch — the 150 % text scale
	   with the dyslexia font — would close the gap to the row below by
	   exactly that difference. The padding is invisible (the card sits at
	   the cell's top) and never changes the row at the design's own size,
	   where the pitch is what sets it; it only keeps the guarantee
	   «clearance ≥ 0» true when the cards are the tallest thing in the
	   row. */
	.repertoire-grid__item:nth-child(1) {
		--offset: 40px;

		padding-bottom: 4px;
	}

	.repertoire-grid__item:nth-child(2) {
		--offset: 185px;

		padding-bottom: 16px;
	}

	.repertoire-empty {
		width: 1176px;
		margin-inline: auto;
	}

	/* 3219:10741: the button spans the card block (118 → 1295 against the
	   cards' 119 → 1295), 56 under it. */
	.repertoire-view-all {
		width: 1176px;
		margin: 56px auto 0;
	}

	.c-repertoire-card {
		gap: 24px;
	}

	.c-repertoire-card__media {
		aspect-ratio: 376 / 500;
	}

	.c-repertoire-card__age {
		--c-poster-band-height: 56px;

		padding-left: 16px;
	}

	.c-repertoire-card__info {
		gap: 24px;
	}

	.c-repertoire-card__text {
		gap: 20px;
	}
}

/* ----------------------------------------------------------- 1920+ */

/* Derived (no 1920 repertoire frame): the Full HD home 4190:19490 — three
   486 columns with a 32 gutter starting at x=286, cards 120 apart (pitch
   885 + 120), the same +40 / +185 / 0 column offsets, and the Full HD
   card 4190:20335 (poster 486×646, band 72, info gaps 28/32/24). */
@media (min-width: 1920px) {

	.repertoire-grid {
		--pitch: 1005px;

		grid-template-columns: repeat(3, 486px);
		column-gap: 32px;
		width: 1522px;
	}

	.repertoire-grid__item:nth-child(3n + 1),
	.repertoire-grid__item:nth-child(1) {
		--offset: 40px;
	}

	.repertoire-grid__item:nth-child(3n + 2),
	.repertoire-grid__item:nth-child(2) {
		--offset: 185px;
	}

	.repertoire-empty,
	.repertoire-view-all {
		width: 1522px;
	}

	.repertoire-view-all {
		margin-top: 80px; /* 4190:20743 bottom 4193 → View All 4273. */
	}

	.c-repertoire-card {
		gap: 28px;
	}

	.c-repertoire-card__media {
		aspect-ratio: 486 / 646;
	}

	.c-repertoire-card__age {
		--c-poster-band-height: 72px;

		padding-left: 20px;
	}

	.c-repertoire-card__info {
		gap: 32px;
	}

	.c-repertoire-card__text {
		gap: 24px;
	}
}

/* Windows High Contrast: box-shadow is dropped, so the filter bar's own
   edges disappear (base.css convention — an inside outline replaces
   them; outline never changes the box). */
@media (forced-colors: active) {

	.repertoire-filters__toggle,
	.repertoire-filters__field,
	.repertoire-filters__dropdown > summary {
		outline: 1px solid CanvasText;
		outline-offset: -1px;
	}
}
