/*
Theme Name: Farol
Theme URI: https://github.com/vvidai/technoinspire-wp
Author: vvidai
Author URI: https://vvidai.com
Description: Block theme for Techno Inspire. Selective electronic-music experiences aboard schooners and in coastal inns, told through photography.
Requires at least: 7.1
Tested up to: 7.1.2
Requires PHP: 8.2
Version: 0.89.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: farol
Tags: blog, photography, one-column, block-patterns, full-site-editing
*/

/*
 * Convés Reservado
 *
 * Global values live in theme.json. This sheet owns the scale hierarchy and
 * three compositions:
 *
 *   arrival   the single dominant element of the whole site
 *   index     dense editorial rows; the newest row is larger
 *   ledger    the event archive as a compact, scannable record
 *   rail      small identical cells for the event selection
 *
 * Two failures are encoded here so they are not repeated. First: image
 * proportions were once varied by grid position, which is not information and
 * rendered as disorder. Second: every block was then made equally huge, which
 * removed hierarchy and made the pages enormous and flat. The rule that
 * resolves both: exactly one dominant element per page, difference in scale
 * only where the content justifies it (recency), and uniformity everywhere
 * ranks are equal.
 *
 * Text over photography is always white. Measured worst case, a fully blown
 * photograph under a 0.86 black scrim gives white 14.16:1, warm gray 5.8:1 and
 * orange 4.37:1 — so gray body copy and orange text never sit on an image.
 */

/* 1. Floor -------------------------------------------------------------- */

* {
	box-sizing: border-box;
}

html {
	background: var( --ti-field );
	caret-color: var( --ti-accent );
	scrollbar-color: var( --ti-accent ) var( --ti-field );
	scrollbar-width: thin;
}

/*
 * The field contract.
 *
 * Four semantic tokens, declared once here for the dark field and once inside
 * `.ti-counter-field` for the light one. Components read the contract and never a preset
 * directly, so putting `ti-counter-field` on a section is enough to flip everything inside it:
 * rules, secondary text, image wells and hover.
 *
 * Before this, the theme's own components used `base-3` for every rule, `muted` for every lede
 * and `base-2` for every well, all of which only work on black. `muted` is `#AAA5A1` and reads
 * 2.26:1 on the warm white — adding the light class to a section used to produce a light band
 * with unreadable secondary text and near-black hairlines. The plugin blocks already worked this
 * way; the theme did not.
 */
/*
 * `.ti-page-field` is the page's own world, named so a surface can ask for it back.
 *
 * It exists because of the theme chooser. Its dialog is written inside the footer, and the footer is a
 * counter field, so the panel inherited the near-black field and opened almost black over a warm white
 * page — while the reader was looking at an option called "Light". A modal covers the whole viewport;
 * it belongs to the page, not to the band its markup happens to sit in.
 *
 * One declaration block with two selectors rather than a copy: a second copy of the contract is a
 * second thing to forget when a token moves.
 */
body,
.ti-page-field {
	--ti-strong: var( --wp--preset--color--contrast );
	/*
	 * Between strong and muted: the resting state of something that answers a pointer, like a
	 * brand mark in the footer. It was consumed by four block rules and declared only inside
	 * `.ti-counter-field :where( .ti-music-card )`, so everywhere else it fell through to its
	 * fallback and the quiet state was never quiet. Promoting it to the contract is what makes
	 * the rest of those rules mean what they say.
	 */
	--ti-quiet: var( --wp--preset--color--contrast-2 );
	--ti-muted: var( --wp--preset--color--muted );
	--ti-line: var( --wp--preset--color--base-3 );
	--ti-well: var( --wp--preset--color--base-2 );
	--ti-accent: var( --wp--preset--color--accent );
	/*
	 * The label inside an accent fill. It is white in both fields and it must not be derived from
	 * the field, because the fill is its own context: the red is the same red on a light page as
	 * on a dark one. Reading `--ti-strong` here put ink on red at 3.53:1 on every light surface —
	 * the ticket button, the one control the whole site exists to deliver.
	 */
	--ti-on-accent: #FFFFFF;
	--ti-field: var( --wp--preset--color--base );

	/*
	 * Optical devices, not brand colours, and that is why they are black in both fields: text
	 * over a photograph is always white here, so what sits behind it always has to darken.
	 * The scrim strength is measured — 84% is the value that holds 4.57:1 against a blown-out
	 * frame — and the veil is the lightbox backdrop. They live in the contract because the
	 * numbers are a theme decision, and a plugin that hardcoded them would silently disagree
	 * with the next theme.
	 */
	--ti-shade: #000000;
	--ti-scrim: rgba( 0, 0, 0, 0.84 );
	--ti-veil: rgba( 0, 0, 0, 0.92 );

	/*
	 * Text sitting on a photograph. White in every field and every variation, because what is
	 * behind it is a photograph and not a field: the project rule is that text over image is
	 * always white, and the scrim above exists to guarantee it.
	 *
	 * It is a literal rather than a palette read, and that is the lesson from inverting the page.
	 * The hero title, the arrival metadata and the event stage date all read the `contrast`
	 * preset, which meant white while the page was dark — the moment the page went light they
	 * turned black on top of a photograph. Pointing them at `ink` instead is no better: `ink` is
	 * white in one variation and black in the other, because both slots swap roles when the
	 * polarity swaps. No palette slot reliably means white.
	 *
	 * Encoding "white because of what is underneath" as "the field's strongest colour" was the
	 * mistake. They are different reasons that happened to share a value, and the scrim/white
	 * pair is what keeps a caption readable over a photograph nobody has seen yet — so it belongs
	 * with the optical devices, not with the brand.
	 */
	--ti-on-image: #FFFFFF;
	/*
	 * The one size every small label shares. It was nine literals between 0.62rem and 0.74rem
	 * across the theme and six plugins, which rendered as 9.9 to 11.8px — 35 findings from the
	 * detector on the rendered HTML, and unreadable on a phone at night, which is the usage scene
	 * the product declares. One token, read with a literal fallback by the plugins (D27).
	 */
	--ti-label-size: var( --wp--preset--font-size--label, 0.8125rem );
	/*
	 * The brand orange, and only where it passes. #F65D00 reads 6.18:1 on the near-black field and
	 * 2.99:1 on the warm white, so on the page's own field the token falls back to the red. The
	 * counter field declares the orange itself. It is a detail colour — a rule, an underline, a
	 * numeral on black — never the action, which stays red everywhere.
	 */
	--ti-flare: var( --ti-accent );
}

body {
	background: var( --ti-field );
}

/*
 * White on the accent, not black. The accent used to be orange, where black read 6.08:1 and
 * white only 3.24:1; on the red it is the other way round — white 5.57:1, black 3.53:1. Every
 * label that sits inside an accent fill inverted with the palette, and this is one of them.
 */
::selection {
	background: var( --ti-accent );
	color: var( --ti-strong );
}

:where( a, button, input, select, textarea, [tabindex] ):focus-visible {
	outline: 2px solid var( --ti-accent );
	outline-offset: 4px;
}

a {
	text-decoration-thickness: 1px;
	text-underline-offset: 0.24em;
}

img {
	display: block;
}

.wp-block-post-date,
.ti-pagination,
.ti-ledger__year {
	font-variant-numeric: tabular-nums;
}

@media ( prefers-reduced-motion: reduce ) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* 2. Shell -------------------------------------------------------------- */

/*
 * Short documents still close at the bottom of the usable viewport. The main
 * absorbs only spare space, so the footer remains in normal flow and long
 * documents keep their natural height. Logged-in views subtract the external
 * WordPress admin bar instead of introducing an admin-bar-sized scroll range.
 */
.wp-site-blocks {
	--ti-admin-bar-offset: 0px;

	display: flex;
	flex-direction: column;
	min-height: calc( 100vh - var( --ti-admin-bar-offset ) );
	min-height: calc( 100dvh - var( --ti-admin-bar-offset ) );
}

.wp-site-blocks > main {
	flex: 1 0 auto;
}

.admin-bar .wp-site-blocks {
	--ti-admin-bar-offset: 32px;
}

.wp-site-blocks > *,
.ti-main {
	margin-block-start: 0 !important;
}

/* Service pages use the shell's spare height instead of inventing a viewport. */
.ti-service-page {
	display: grid;
	place-items: center;
	padding-block: clamp( 3.5rem, 9vw, 7rem );
	text-align: center;
}

.ti-service-page__content {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: clamp( 1rem, 2.2vw, 1.5rem );
	width: min( 100%, 40rem );
	margin: auto;
}

.ti-service-page__mark {
	display: grid;
	place-items: center;
	width: clamp( 3.75rem, 8vw, 4.75rem );
	aspect-ratio: 1;
	color: var( --ti-accent );
	background: var( --ti-strong );
}

.ti-service-page__mark svg {
	width: 70%;
	height: 70%;
	fill: none;
	stroke: currentcolor;
	stroke-width: 2;
	stroke-linecap: square;
	stroke-linejoin: miter;
}

.ti-service-page__mark circle {
	fill: currentcolor;
	stroke: none;
}

.ti-service-page__title {
	max-width: 15ch;
	margin: 0;
	font-size: clamp( 2.4rem, 7vw, 4.75rem );
	font-weight: 450;
	line-height: 0.98;
	letter-spacing: -0.04em;
	text-wrap: balance;
}

.ti-service-page__lede {
	max-width: 52ch;
	margin: 0;
	color: var( --ti-quiet );
	font-size: clamp( 1rem, 1.7vw, 1.12rem );
	line-height: 1.65;
}

.ti-service-page__search {
	width: min( 100%, 34rem );
	margin-block: 0.5rem 0;
	text-align: left;
}

.ti-service-page__home {
	display: inline-flex;
	align-items: center;
	gap: 0.55rem;
	min-height: 44px;
	color: var( --ti-strong );
	font-weight: 600;
	text-decoration: underline;
	text-decoration-color: var( --ti-line );
	text-underline-offset: 0.3em;
}

.ti-service-page__home:hover {
	color: var( --ti-accent );
	text-decoration-color: currentcolor;
}

.ti-service-page__home svg {
	width: 1rem;
	height: 1rem;
	fill: none;
	stroke: currentcolor;
	stroke-width: 1.5;
	stroke-linecap: square;
	stroke-linejoin: miter;
}

/*
 * Empty, pending and recoverable-error messages share structure, not meaning.
 * The copy remains the primary signal; colour only distinguishes urgency.
 */
.ti-notification {
	--ti-notification-signal: var( --ti-strong );

	position: relative;
	width: min( 100%, 70ch );
	margin-inline: auto !important;
	padding: 1rem 0 1rem 1.5rem;
	color: var( --ti-quiet );
	border-block: 1px solid var( --ti-line );
	line-height: 1.6;
	text-align: left;
}

.ti-notification::before {
	content: "";
	position: absolute;
	top: 1.55rem;
	left: 0;
	width: 0.5rem;
	height: 0.5rem;
	background: var( --ti-notification-signal );
}

/*
 * Three states, three forms.
 *
 * They used to be three colours, and only two of them existed: pending and error both resolved to the
 * accent, so the two were indistinguishable, while `--empty` fell through to `--ti-strong` and drew the
 * loudest mark on the page for the least important thing anybody could be told. Emphasis was upside
 * down and one state was invisible.
 *
 * Hue cannot fix it. The palette is white, red and black by the owner's decision, and there is no
 * second hue to spend — which is the same conclusion the event filter reached. So the difference is
 * form, and form is also what survives for a reader who cannot separate red from black:
 *
 *   empty    a hollow square, in the secondary colour. Absence, stated quietly.
 *   pending  the same square, filled, in the brand red. Something is coming.
 *   error    no square at all: a rule down the whole left edge. A different shape, not a redder dot.
 *
 * The accent stays a fill and a rule, never a letter, which is the standing rule for this palette.
 */
.ti-notification--empty {
	--ti-notification-signal: transparent;
}

.ti-notification--empty::before {
	box-shadow: inset 0 0 0 1px var( --ti-muted );
}

.ti-notification--pending {
	--ti-notification-signal: var( --ti-accent );
}

/*
 * The error state, told apart by shape.
 *
 * Its colour once read `var( --wp--preset--color--sea, #C52D1F )`. The `sea` slug was retired when red
 * became the brand accent, so the preset resolved to nothing and the hardcoded fallback painted — the
 * accent's own value, which is what made error and pending identical.
 *
 * The rule replaces the mark rather than joining it: two signals competing at the same edge read as
 * decoration, and a full-height rule is already more present than an eight-pixel dot. The padding
 * absorbs the rule's width so the text does not shift between one state and another.
 */
.ti-notification--error {
	--ti-notification-signal: transparent;

	padding-inline-start: calc( 1.5rem - 2px );
	border-inline-start: 2px solid var( --ti-accent );
}

.ti-notification--error::before {
	content: none;
}

.ti-notification[hidden] {
	display: none;
}

.ti-notification a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	margin-inline-start: 0.75rem;
	color: var( --ti-strong );
	font-weight: 600;
	text-underline-offset: 0.25em;
}

@media ( max-width: 520px ) {
	.ti-notification a {
		display: flex;
		width: fit-content;
		margin-block-start: 0.35rem;
		margin-inline-start: 0;
	}
}

.ti-section,
.ti-prose {
	position: relative;
	padding-block: clamp( 3.25rem, 5vw, 5rem );
}

/*
 * The light field, and the other half of the contract.
 *
 * Every value is derived from `ink` rather than declared as its own colour, so the light field
 * cannot drift away from the dark one. Measured over the warm white: secondary text lands at
 * 6.28:1, well above the 4.5:1 floor that the raw `muted` token missed at 2.26:1.
 */
.ti-counter-field {
	--ti-strong: var( --wp--preset--color--ink );
	--ti-quiet: color-mix( in srgb, var( --wp--preset--color--ink ) 78%, transparent );
	--ti-muted: color-mix( in srgb, var( --wp--preset--color--ink ) 66%, transparent );
	--ti-line: color-mix( in srgb, var( --wp--preset--color--ink ) 18%, transparent );
	--ti-well: color-mix( in srgb, var( --wp--preset--color--ink ) 8%, transparent );
	--ti-accent: var( --wp--preset--color--accent );
	/*
	 * Also a literal, for the same reason as `--ti-on-image`: the label belongs to the red fill,
	 * and no palette slot stays white across variations. Deriving it from `contrast` put ink on
	 * red at 3.53:1 the moment the page inverted.
	 */
	--ti-on-accent: #FFFFFF;
	--ti-field: var( --wp--preset--color--canvas );
	--ti-flare: var( --wp--preset--color--flare );

	background: var( --ti-field );
	color: var( --ti-strong );
}

/*
 * There is no blanket `color: inherit` here any more, and there must not be one.
 *
 * It existed because `theme.json` painted every heading with the `contrast` preset, which is
 * white and therefore invisible on a light field — so the field had to force headings back to
 * inherit. Widening that list to cover `p`, `li` and `time` then flattened the page: `:where()`
 * carries no specificity, the theme sheet loads after the block sheets, so `inherit` beat every
 * block's own secondary colour. Measured on the next-event band: date, boarding time, place and
 * series all rendered at 18.2:1, the same value as the title, and the band lost its hierarchy.
 *
 * Headings now read `--ti-strong` and body text reads `--ti-quiet`, both declared by the field.
 * Nothing needs to be forced, and a block that has an opinion about one of its own parts keeps
 * it.
 */

:where( .ti-index__list, .ti-ledger__list ) {
	margin: 0;
	padding: 0;
}

:where( .ti-index__list, .ti-ledger__list ) > li {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* 3. Header ------------------------------------------------------------- */

/*
 * Sticky belongs to the template-part wrapper. Applying it to .ti-header
 * would constrain the sticky box to a parent with the same height.
 */
.wp-site-blocks > header.wp-block-template-part {
	position: sticky;
	top: 0;
	z-index: 50;
}

.admin-bar .wp-site-blocks > header.wp-block-template-part {
	top: 32px;
}

/*
 * The bar stays on the counter field even though the page is light, and the reason is material
 * rather than aesthetic: the only brand asset in the repository is a white 903x263 PNG. On a
 * light bar it is invisible. Until a dark or vector mark exists, the header is dark.
 *
 * It reads well as a decision anyway — a dark bar over a light page is a firm top edge, and the
 * frosted material keeps working because what scrolls under it is photography.
 */
.ti-header {
	position: relative;
	z-index: 20;
	min-height: 4.75rem;
	padding-block: 1.1rem;
	background: var( --ti-field );
	box-shadow: 0 1px 0 var( --ti-line );
}

/*
 * The bar is opaque, and the frosted material that used to live here is gone on purpose.
 *
 * It was measured as pixel-identical to an opaque bar at scroll zero, "because 72% of base over
 * base is base". That held while the page was dark. The page is light now, and 72% of the
 * near-black field over the warm white composites to about #4D4C4A: a mid-grey bar that is
 * neither night nor day, and the first thing every visitor saw (critique of 26/09/2026). Glass
 * as decoration is also on the project's list of refused defaults.
 *
 * An opaque near-black bar is the firm top edge the white logo needs, and the one place on a
 * light page where the brand orange can live at full strength.
 */

.ti-header__row {
	align-items: center;
}

/*
 * The mark is 24px tall, so the link was a 24px tap target. The hit area is
 * grown to the 44px floor without changing the size of the mark itself.
 */
.ti-brand {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	line-height: 0;
}

.ti-brand__mark {
	width: auto;
	height: 1.5rem;
}

.ti-nav .wp-block-navigation__container {
	gap: clamp( 1.4rem, 3vw, 2.6rem );
}

/*
 * `min-width` as well as `min-height`. Measured independently of the project's own gate:
 * "Sobre" was 42x44 and "Fotos" 42.3x44, so the two shortest labels sat 1.7 to 2.0px under
 * the 44px floor on width while the height was exact. `a11y.py` reported no small target on
 * the same routes, so the gate's filter is narrower than the floor it claims to guard.
 */
.ti-nav .wp-block-navigation-item > a {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	color: var( --ti-quiet );
	font-size: var( --ti-label-size, 0.8125rem );
	font-weight: 500;
	letter-spacing: 0.14em;
	text-decoration: none;
	text-transform: uppercase;
}

.ti-nav .wp-block-navigation-item > a::after {
	content: "";
	position: absolute;
	inset: auto 0 6px;
	height: 1px;
	background: var( --ti-flare );
	transform: scaleX( 0 );
	transform-origin: left center;
	transition: transform 180ms cubic-bezier( 0.16, 1, 0.3, 1 );
}

.ti-nav .wp-block-navigation-item > a:hover,
.ti-nav .wp-block-navigation-item.current-menu-item > a {
	color: var( --ti-strong );
}

.ti-nav .wp-block-navigation-item > a:hover::after,
.ti-nav .wp-block-navigation-item.current-menu-item > a::after {
	transform: scaleX( 1 );
}

.ti-nav .wp-block-navigation__responsive-container-open,
.ti-nav .wp-block-navigation__responsive-container-close {
	min-width: 44px;
	min-height: 44px;
	color: var( --ti-strong ) !important;
}

body .wp-block-navigation__responsive-container.is-menu-open {
	padding: 2rem;
	background-color: var( --ti-field ) !important;
	color: var( --ti-strong );
}

.ti-nav .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container {
	gap: 0;
	width: 100%;
}

.ti-nav .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item {
	width: 100%;
	border-bottom: 1px solid var( --ti-line );
}

body .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item > a {
	width: 100%;
	padding-block: 0.6rem;
	color: var( --ti-strong ) !important;
	font-family: var( --wp--preset--font-family--display );
	font-size: clamp( 1.8rem, 9vw, 2.8rem );
	font-weight: 300;
	line-height: 1.15;
	letter-spacing: -0.02em;
	text-transform: none;
}

/* 4. Arrival: the one dominant element --------------------------------- */

/*
 * No `background` here, and the absence is the fix.
 *
 * This rule used to set `background: var( --ti-well )`. The section also carries
 * `.ti-counter-field`, which sets `background: var( --ti-field )` — the near-black field — and
 * both selectors are a single class, so source order decided it: this rule sits later in the file
 * and the slideshow silently won. On a counter field `--ti-well` resolves to white at 8%, a
 * translucent tint meant to sit *on* a dark field, so the section never painted the field its own
 * tokens assume.
 *
 * With slides present a photograph covers the area and nothing looks wrong. Measured with the stage
 * empty: the section painted white at 8% over the light page, and the no-results sentence, which
 * reads `--ti-quiet` from the counter field, came out white at 78% — about 1.1:1, a sentence nobody
 * can read, in the largest object on the site.
 *
 * Letting the field paint itself makes the empty state legible without a second rule for it, and
 * near-black is a better placeholder behind a loading photograph than a light tint was.
 */
.ti-slideshow {
	position: relative;
	isolation: isolate;
}

.ti-slideshow > .screen-reader-text {
	margin: 0;
}

.ti-arrival,
.ti-arrival .wp-block-post-template,
.ti-arrival .wp-block-post-template > li {
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * The server-rendered state is a real horizontal rail. If the module never
 * loads, every story remains reachable by touch, trackpad and keyboard scroll.
 */
.ti-arrival .wp-block-post-template {
	display: flex;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	overscroll-behavior-x: contain;
	scrollbar-color: var( --ti-accent ) var( --ti-well );
}

.ti-arrival .wp-block-post-template > li {
	flex: 0 0 100%;
	min-width: 0;
	scroll-snap-align: start;
}

/* Enhancement overlays equal-height slides, so activation causes no shift. */
.ti-slideshow[data-ti-slideshow-enhanced] .wp-block-post-template {
	display: grid;
	overflow: hidden;
}

.ti-slideshow[data-ti-slideshow-enhanced] .wp-block-post-template > li {
	grid-area: 1 / 1;
	visibility: hidden;
	z-index: 0;
	opacity: 0;
	pointer-events: none;
	transition:
		opacity 320ms cubic-bezier( 0.16, 1, 0.3, 1 ),
		visibility 0s linear 320ms;
}

.ti-slideshow[data-ti-slideshow-enhanced] .wp-block-post-template > li.is-active {
	visibility: visible;
	z-index: 1;
	opacity: 1;
	pointer-events: auto;
	transition-delay: 0s;
}

.ti-slideshow[data-ti-slideshow-instant] .wp-block-post-template > li,
.ti-slideshow[data-ti-slideshow-instant] .ti-arrival__image img {
	transition: none !important;
}

.ti-slideshow[data-ti-slideshow-enhanced] .ti-arrival__image img {
	transform: scale( 1.018 );
	transition: transform 900ms cubic-bezier( 0.16, 1, 0.3, 1 );
}

.ti-slideshow[data-ti-slideshow-enhanced] li.is-active .ti-arrival__image img {
	transform: scale( 1 );
}

.ti-arrival__stage {
	position: relative;
	height: clamp( 28rem, 74svh, 40rem );
	margin: 0;
	overflow: hidden;
	background: var( --ti-well );
	isolation: isolate;
}

.ti-arrival__image {
	position: absolute;
	inset: 0;
	z-index: 0;
	margin: 0;
}

.ti-arrival__image a,
.ti-arrival__image img {
	width: 100%;
	height: 100%;
}

.ti-arrival__image img {
	object-fit: cover;
	object-position: center 32%;
}

/* The text band never drops below 0.86 opacity, which is the measured floor. */
.ti-arrival__stage::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(
		to top,
		color-mix( in srgb, var( --ti-shade ) 95%, transparent ) 0,
		color-mix( in srgb, var( --ti-shade ) 88%, transparent ) 38%,
		color-mix( in srgb, var( --ti-shade ) 32%, transparent ) 72%,
		color-mix( in srgb, var( --ti-shade ) 24%, transparent ) 100%
	);
}

.ti-arrival__panel {
	position: relative;
	z-index: 2;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	align-items: flex-start;
	gap: clamp( 0.75rem, 1.6vh, 1.1rem );
	width: 100%;
	max-width: 86rem;
	height: 100%;
	margin-inline: auto;
	padding: 0 var( --wp--preset--spacing--40 ) clamp( 2.25rem, 5vh, 3.5rem );
	color: var( --ti-on-image );
}

.ti-arrival__meta {
	gap: 1rem;
	margin: 0;
	color: var( --ti-on-image );
}

.ti-arrival__meta :where( a, time ),
.ti-arrival__meta .wp-block-post-date,
.ti-arrival__meta .wp-block-post-terms {
	color: inherit !important;
}

.ti-arrival__title {
	max-width: 24ch;
	margin: 0;
	color: var( --ti-on-image );
	font-family: var( --wp--preset--font-family--body );
	font-size: clamp( 1.75rem, 3vw, 2.9rem );
	font-weight: 400;
	line-height: 1.08;
	letter-spacing: -0.026em;
	text-wrap: balance;
	hyphens: manual;
}

.ti-slideshow__controls {
	position: absolute;
	inset: auto max( var( --wp--preset--spacing--40 ), calc( ( 100vw - 86rem ) / 2 ) ) clamp( 2.25rem, 5vh, 3.5rem ) auto;
	z-index: 4;
	display: flex;
	align-items: center;
	gap: 0.2rem;
	color: var( --ti-on-image );
}

.ti-slideshow__controls[hidden],
.ti-slideshow__control[hidden],
.ti-slideshow__control [hidden] {
	display: none !important;
}

.ti-slideshow__control {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	color: color-mix( in srgb, var( --ti-on-image ) 82%, transparent );
	background: transparent;
	border: 0;
	cursor: pointer;
	transition: color 180ms cubic-bezier( 0.16, 1, 0.3, 1 ), transform 100ms cubic-bezier( 0.16, 1, 0.3, 1 );
}

.ti-slideshow__control:last-child {
	border-inline-end: 0;
}

.ti-slideshow__control:hover,
.ti-slideshow__control:focus-visible {
	color: var( --ti-accent );
}

.ti-slideshow__control:active {
	transform: translateY( 1px );
}

.ti-slideshow__control svg {
	width: 1rem;
	height: 1rem;
}

.ti-slideshow__control--pause {
	margin-inline-end: 0.3rem;
}

.ti-slideshow__counter {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 3.7rem;
	height: 44px;
	color: var( --ti-quiet );
	font-size: var( --ti-label-size, 0.8125rem );
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.12em;
}

@media ( max-width: 47.99rem ) {
	.ti-slideshow .ti-arrival__panel {
		padding-block-end: 5.75rem;
	}

	.ti-slideshow__controls {
		inset: auto var( --wp--preset--spacing--40 ) 1.25rem auto;
	}
}

@media ( prefers-reduced-motion: reduce ) {
	.ti-slideshow[data-ti-slideshow-enhanced] .wp-block-post-template > li,
	.ti-slideshow[data-ti-slideshow-enhanced] .ti-arrival__image img {
		transition: none;
		transform: none;
	}
}

/* 5. Actions ------------------------------------------------------------ */

.ti-arrival__action,
.ti-arrival__action a,
.ti-section__more a {
	display: inline-flex;
	align-items: center;
	justify-content: space-between;
	gap: 1.75rem;
	min-height: 44px;
	font-size: var( --ti-label-size, 0.8125rem );
	font-weight: 600;
	letter-spacing: 0.12em;
	text-decoration: none;
	text-transform: uppercase;
}

/*
 * The arrival action speaks in sentence case. As a tracked uppercase line it was 78 characters
 * of capitals at 11px, counting the screen-reader suffix — the detector's all-caps-body finding
 * on the home, and a shout in the one place a quiet invitation reads as confidence.
 */
.ti-arrival__action,
.ti-arrival__action a {
	font-size: 0.95rem;
	font-weight: 500;
	letter-spacing: 0.01em;
	text-transform: none;
}

.ti-arrival__action,
.ti-arrival__action a {
	width: fit-content;
	max-width: 100%;
	min-width: 0;
	margin: 0;
	padding: 0.8rem 1.05rem;
	background: var( --ti-accent );
	/* The label belongs to the fill, never to the field — see the note on `--ti-on-accent`. */
	color: var( --ti-on-accent ) !important;
	transition: background-color 180ms cubic-bezier( 0.16, 1, 0.3, 1 ), transform 100ms cubic-bezier( 0.16, 1, 0.3, 1 );
}

/*
 * Hover deepens the same red rather than changing hue. The palette used to carry a second
 * warm colour for this — `sea`, the ember red — and it was retired when red became the brand's
 * own accent: two adjacent warm reds is a muddle, not a system, and a state of a colour should
 * be derived from that colour rather than declared as a separate token.
 */
.ti-arrival__action:hover,
.ti-arrival__action a:hover {
	background: var( --ti-accent );
	filter: brightness( 0.88 );
}

.ti-arrival__action:active,
.ti-arrival__action a:active {
	transform: translateY( 1px );
}

.ti-arrival__action::after,
.ti-arrival__action a::after,
.ti-section__more a::after {
	content: "";
	width: 0.5rem;
	height: 0.5rem;
	border-top: 1px solid currentcolor;
	border-right: 1px solid currentcolor;
	transform: rotate( 45deg );
}

/* 6. Section heads ----------------------------------------------------- */

/*
 * Title, then lede under it, contained.
 *
 * It was `display: flex; align-items: baseline` with the lede on `flex: 1 1 22rem`, which
 * measured like this at 1440: box 1376px wide, title 96px ("Notícias"), lede 396px starting
 * at x160, content ending at x556. That is 852px of dead horizontal space, twice on the home
 * page. `align-items: baseline` in a row that wraps also aligns on the last line, so the
 * pairing was fragile as well as empty.
 */
.ti-section__head {
	margin: 0 0 clamp( 1.5rem, 2.5vw, 2.25rem );
	padding-block-end: 0.9rem;
	border-bottom: 1px solid var( --ti-line );
}

.ti-section__title {
	margin: 0;
	font-family: var( --wp--preset--font-family--display );
	font-size: clamp( 1.5rem, 2vw, 1.9rem );
	font-weight: 400;
	line-height: 1.1;
	letter-spacing: -0.015em;
}

.ti-section__lede {
	max-width: 56ch;
	margin: 0.6rem 0 0;
	color: var( --ti-muted );
	font-size: 0.92rem;
	line-height: 1.6;
	text-wrap: pretty;
}

.ti-section__more {
	margin: clamp( 1.75rem, 3vw, 2.5rem ) 0 0;
}

.ti-section__more a {
	width: min( 12rem, 100% );
	min-width: 0;
	padding-block: 0.65rem;
	color: var( --ti-strong );
	border-bottom: 1px solid var( --ti-accent );
	overflow-wrap: anywhere;
}

.ti-section__more a:hover {
	border-bottom-color: var( --ti-accent );
}

/* 7. Route head -------------------------------------------------------- */

/*
 * One column, lede under the title and contained.
 *
 * It was `minmax(13rem, 0.72fr) minmax(18rem, 1fr)`, which reserved a 547px column for the
 * word "Fotos" and parked the lede at x657. Measured at 1440: content ended at x1061 inside
 * a 1376px box, with roughly 467px of nothing between the title glyphs and the lede. The
 * project standard names this exact failure — a section description goes under the title,
 * contained; throwing it to the far right creates dead space.
 */
.ti-route-head {
	position: relative;
	display: grid !important;
	grid-template-columns: minmax( 0, 1fr );
	gap: clamp( 1rem, 2vw, 1.5rem );
	padding-block: clamp( 2.5rem, 4vw, 3.75rem );
}

.ti-route-head > * {
	width: auto !important;
	max-width: none !important;
	margin-inline: 0 !important;
}

/*
 * "Eventos", "Notícias", "Fotos" are labels, not statements, and they were the largest type
 * on the whole site.
 *
 * Measured before this change: 54.4px here, against 40.32px for the flagship event's own
 * name and 43.2px for the home headline. The site shouted a navigation noun 26% louder than
 * anything it actually had to say. The scale is inverted by bringing the label down rather
 * than by pushing an editorial title up: those titles come from the database, their length
 * is unpredictable, and display sizes are for copy we control.
 */
.ti-route-head__title {
	max-width: 16ch;
	margin: 0;
	font-family: var( --wp--preset--font-family--display );
	font-size: clamp( 1.6rem, 2.4vw, 2.1rem );
	font-weight: 300;
	line-height: 1.05;
	letter-spacing: -0.028em;
}

.ti-route-head__title::after {
	content: "";
	display: block;
	width: clamp( 3rem, 6vw, 5rem );
	height: 2px;
	margin-block-start: clamp( 1rem, 2vw, 1.5rem );
	background: var( --ti-accent );
}

/* Spacing is the grid gap, in one place, so the phone query does not need a second rule. */
.ti-route-head__lede {
	max-width: 52ch !important;
	margin: 0 !important;
	color: var( --ti-muted ) !important;
	font-size: 0.98rem;
	line-height: 1.6;
}

/*
 * The search carries no gap of its own, because the section under it already has one.
 *
 * It used to end with `clamp( 2.5rem, 4vw, 3.75rem )`, and the index section below opens with
 * `clamp( 3.25rem, 5vw, 5rem )`. Two full gaps, stacked: measured at 1440 on `/events/`, 57.6px
 * plus 72px, so 130px of nothing sat between a field and the list it filters — and 341px of page
 * separated the route's lede from its first row. `/news/` and `/photos/` have the same shape.
 *
 * A tool and the list it acts on are one thing. One gap says so; two say they are unrelated.
 */
.ti-search-placement {
	padding-block: 0;
}

/* 8. Index: dense editorial rows --------------------------------------- */

/*
 * No background declared. The field is decided by the class the section carries, which is how a
 * surface changes polarity in one place instead of in every component below it.
 */

/*
 * The head is as wide as the rows it introduces, and both are as wide as their content.
 *
 * Three attempts are recorded here because the first two were wrong and the reasons are worth
 * keeping. Measured at 1440 originally: a row's title started at x32 and its own thumbnail at
 * x1258, so one row's two halves stopped reading as one row and the thumbnails became a strip
 * of stamps down the right edge.
 *
 * Capping the head and the row separately in CSS put them 8px apart, because the constrained
 * layout centres whatever it caps and the two were capped against different boxes. Declaring a
 * 64rem `wideSize` on the section fixed the alignment but introduced a third container width,
 * which the project forbids — 86rem wide and 44rem reading are the only two measures.
 *
 * So the width is derived from content instead of declared: the row shrinks to fit, the rule
 * that separates rows belongs to the row and therefore never promises width the content does
 * not use, and the section keeps the project's 86rem.
 */
 * `fit-content` was the third wrong answer: it sized every row to its own content, so at 390
 * four rows produced four different rule lengths. A list separator has to be uniform or it
 * reads as damage.
 *
 * The answer that holds: rows keep the band's full width, so every rule is identical, and a
 * third empty grid track absorbs the slack so the thumbnail sits immediately after the reading
 * column instead of at the far edge. Measured after: thumbnail x768 instead of x1258 at 1440.
 */

/*
 * The rule belongs to the row, not to the list item, because the row is narrower than the
 * band it sits in. A separator drawn at 1376px above content that stops at 1024px is the
 * defect already recorded on the music route: a ruled width the content never uses reads as
 * an unfinished table.
 */
.ti-index__list > li + li > .ti-index {
	border-top: 1px solid var( --ti-line );
}

/*
 * The photograph sits next to the text it belongs to, and the row ends where the row ends.
 *
 * This was `minmax( 0, 44rem ) clamp( 6.5rem, 11vw, 9.5rem ) minmax( 0, auto )`: a copy
 * column frozen at 704px whatever the text measured, then the thumbnail, then a third track
 * that held nothing at all. Measured at 1440: the text stopped at x=605 and the thumbnail
 * started at x=768, so 153px of nothing separated a caption from its own photograph, and
 * 488px of nothing followed the photograph to the edge of the row. Three stamps stranded in
 * the middle of a band — which is what the owner saw and called distant.
 *
 * Two tracks now, image first. The lead row above already leads with its photograph, so the
 * compact rows below it line up on the same left rail instead of inventing a second reading
 * order. The copy takes what is left and its own elements keep their measure: the title at
 * 36ch and the excerpt at 74ch decide where the line breaks, not a frozen column.
 */
.ti-index {
	display: grid;
	grid-template-columns: clamp( 6.5rem, 11vw, 9.5rem ) minmax( 0, 1fr );
	align-items: center;
	gap: 1.25rem clamp( 1.25rem, 2.5vw, 2rem );
	margin: 0;
	padding-block: clamp( 1rem, 1.6vw, 1.35rem );
}

.ti-index__image {
	grid-column: 1;
	aspect-ratio: 4 / 3;
	margin: 0;
	overflow: hidden;
	background: var( --ti-well );
}

.ti-index__image a,
.ti-index__image img {
	width: 100%;
	height: 100%;
}

.ti-index__image img {
	object-fit: cover;
	filter: saturate( 0.94 );
	transition: filter 200ms cubic-bezier( 0.16, 1, 0.3, 1 );
}

.ti-index:hover .ti-index__image img,
.ti-index:focus-within .ti-index__image img {
	filter: saturate( 1.05 );
}

.ti-index__copy {
	grid-column: 2;
	grid-row: 1;
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	min-width: 0;
	margin: 0;
}

.ti-index__date {
	margin: 0;
	color: var( --ti-muted ) !important;
}

.ti-index__title {
	max-width: 36ch;
	margin: 0;
	font-family: var( --wp--preset--font-family--body );
	font-size: clamp( 1rem, 1.35vw, 1.22rem );
	font-weight: 500;
	line-height: 1.2;
	letter-spacing: -0.015em;
	text-wrap: balance;
}

.ti-index__excerpt {
	display: -webkit-box;
	max-width: 74ch;
	margin: 0;
	overflow: hidden;
	color: var( --ti-muted );
	font-size: 0.88rem;
	line-height: 1.55;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
}

/*
 * The first row is the newest dispatch. The extra scale reports recency, which
 * the reader needs; it is never a decorative exception.
 */
.ti-index__list > li:first-child .ti-index {
	grid-template-columns: minmax( 0, 1.08fr ) minmax( 0, 1fr );
	align-items: center;
	gap: clamp( 1.25rem, 2.5vw, 2.25rem );
	padding-block: 0 clamp( 1.25rem, 2vw, 1.75rem );
}

.ti-index__list > li:first-child .ti-index__image {
	grid-column: 1;
	grid-row: 1;
	aspect-ratio: 16 / 9;
}

.ti-index__list > li:first-child .ti-index__copy {
	grid-column: 2;
	gap: 0.5rem;
}

.ti-index__list > li:first-child .ti-index__title {
	max-width: 24ch;
	font-size: clamp( 1.3rem, 2.1vw, 1.9rem );
	font-weight: 400;
	line-height: 1.1;
	letter-spacing: -0.022em;
}

.ti-index__list > li:first-child .ti-index__excerpt {
	font-size: 0.95rem;
	-webkit-line-clamp: 3;
}

/*
 * The News route reads as cards; the home keeps the dense index.
 *
 * Two surfaces, two jobs. On the home the section is a teaser between the stage and the
 * footer, and a dense list is the honest shape for four rows. The News route is where a
 * visitor goes to browse, and the owner asked for the shape every publication uses there.
 *
 * The card carries no fill, no border and no shadow. What makes it a card is the photograph
 * on top of its own text with air around the pair — the project rule is that photography is
 * documentary and unboxed, and a panel drawn behind a photograph boxes it. The regular 16:9
 * crop is what makes the grid read as a grid.
 *
 * Recency still earns scale: the newest item spans the full row and keeps the two-column
 * feature layout. That is the one hierarchy this project allows, and it is the difference
 * between a card grid and a grid of equal cards, which is the default this repository
 * refuses.
 */
/*
 * 21rem is the track that yields three columns at 1440 and two at 768. It was 17rem first,
 * and 17rem measured four columns of 320px on the desktop — four photographs per row turns a
 * news index into a contact sheet, and each frame lost a third of its width for nothing.
 */

/*
 * On a wide screen the three compact rows under the leader become three columns.
 *
 * As rows they spanned 1376px while their text stopped at the title's 36ch and the excerpt's
 * clamp, so the right half of every row was empty — measured at 1440 on the home, copy boxes of
 * 1192px holding lines about 700px long, three times. Three items of equal rank take equal
 * columns, each with its 4:3 photograph on top; the leader keeps the only larger treatment,
 * which is the one difference in scale that recency earns. Below 64rem they stay rows.
 */
@media ( min-width: 64rem ) {
	.ti-dispatches .ti-index__list {
		display: grid;
		grid-template-columns: repeat( 3, minmax( 0, 1fr ) );
		column-gap: clamp( 1.5rem, 3vw, 2.5rem );
	}

	.ti-dispatches .ti-index__list > li:first-child {
		grid-column: 1 / -1;
	}

	.ti-dispatches .ti-index__list > li:not( :first-child ) .ti-index {
		grid-template-columns: minmax( 0, 1fr );
		align-items: start;
		gap: 0.9rem;
		height: 100%;
		padding-block: clamp( 1.25rem, 2vw, 1.75rem ) 0;
	}

	.ti-dispatches .ti-index__list > li:not( :first-child ) .ti-index__image,
	.ti-dispatches .ti-index__list > li:not( :first-child ) .ti-index__copy {
		grid-column: 1;
		grid-row: auto;
	}

	.ti-dispatches .ti-index__list > li:not( :first-child ) .ti-index__title {
		max-width: none;
	}
}

/*
 * `min( 21rem, 100% )` and not `21rem`. A bare minimum is a floor the track cannot go under,
 * so at 320px the cards stayed 336px wide and pushed the page 126px past the viewport — the
 * reflow gate caught it at 320 while a check at 390 showed nothing, because 336 plus the
 * gutters still fits there. The `min()` lets the track give way on a narrow screen and keeps
 * the ideal everywhere else.
 */
.ti-news-archive .ti-index__list {
	display: grid;
	grid-template-columns: repeat( auto-fill, minmax( min( 21rem, 100% ), 1fr ) );
	gap: clamp( 2rem, 3.5vw, 3rem ) clamp( 1.25rem, 2.5vw, 2rem );
}

.ti-news-archive .ti-index__list > li:first-child {
	grid-column: 1 / -1;
}

/* No rule between cards: the gap separates them, and a line would cut the grid. */
.ti-news-archive .ti-index__list > li + li > .ti-index {
	border-top: 0;
}

.ti-news-archive .ti-index {
	grid-template-columns: minmax( 0, 1fr );
	align-items: start;
	gap: 0.85rem;
	padding-block: 0;
}

.ti-news-archive .ti-index__image {
	grid-column: 1;
	grid-row: 1;
	aspect-ratio: 16 / 9;
}

.ti-news-archive .ti-index__copy {
	grid-column: 1;
	grid-row: 2;
}

/* A card is as wide as its column, so the title has no reason to stop early. */
.ti-news-archive .ti-index__title {
	max-width: none;
	font-size: clamp( 1rem, 1.15vw, 1.12rem );
}

.ti-news-archive .ti-index__excerpt {
	max-width: none;
}

/*
 * The lead card keeps the wide feature shape, and only it reads two columns.
 *
 * `align-items: start` puts the copy at the top edge of the photograph instead of floating it
 * in the middle: measured on the home, a 149px block centred against a 391px image left about
 * 120px of void above and below it.
 *
 * The media query is not decoration. Without it this rule outranks the narrow-screen stack
 * further down the file, and the phone got the feature in two columns — measured at 390: a
 * 171px photograph beside a 158px column of text, which is neither a card nor a feature.
 */
@media ( min-width: 48rem ) {
	.ti-news-archive .ti-index__list > li:first-child .ti-index {
		grid-template-columns: minmax( 0, 1.08fr ) minmax( 0, 1fr );
		align-items: start;
		padding-block: 0 clamp( 0.5rem, 1vw, 1rem );
	}

	.ti-news-archive .ti-index__list > li:first-child .ti-index__copy {
		grid-column: 2;
		grid-row: 1;
	}

	.ti-news-archive .ti-index__list > li:first-child .ti-index__title {
		max-width: 24ch;
	}
}

/*
 * A list title is the only link in its row, so it carries the whole tap target.
 * Measured at 19px tall before this rule; the box now reaches the 44px floor
 * while the type keeps its own line height.
 */
.ti-index__title a,
.ti-ledger__title a {
	display: flex;
	align-items: center;
	min-height: 44px;
	color: inherit;
	text-decoration: none;
}

.ti-index__title a:hover,
.ti-ledger__title a:hover {
	text-decoration: underline;
	text-decoration-color: var( --ti-accent );
	text-decoration-thickness: 2px;
}

/* 9. Ledger: the event record --------------------------------------- */

/* The field comes from the section's class — see the note on `.ti-dispatches`. */

.ti-ledger__list {
	border-top: 1px solid var( --ti-line );
}

.ti-ledger__list > li {
	border-bottom: 1px solid var( --ti-line );
}

/*
 * The third track was 3.5rem, sized for a month abbreviation, while the block
 * printed the whole date and broke it over four lines. The block now renders day
 * and month only, and the track grows to its own content instead of guessing.
 */
/*
 * Year, title, place, date, thumbnail.
 *
 * The place column was added because the archive listed seven crossings without naming a
 * single one of their settings: every row opened with "Techno Inspire", and the useful word
 * arrived fourth to sixth or not at all. The brand's own positioning is that the setting is
 * the differentiator, so it belongs in the column the eye runs down.
 */
.ti-ledger__row {
	display: grid;
	grid-template-columns: 4.5rem minmax( 0, 1fr ) minmax( 0, 11rem ) minmax( 3.5rem, max-content ) clamp( 5.5rem, 9vw, 8rem );
	align-items: center;
	gap: clamp( 1rem, 2vw, 1.75rem );
	margin: 0;
	padding-block: 0.7rem;
}

.ti-ledger__year {
	margin: 0;
	color: var( --ti-muted ) !important;
	font-family: var( --wp--preset--font-family--display ) !important;
	font-size: 1.15rem !important;
	font-weight: 400 !important;
	letter-spacing: 0 !important;
	text-transform: none !important;
}

.ti-ledger__title {
	max-width: 42ch;
	margin: 0;
	font-family: var( --wp--preset--font-family--body );
	font-size: clamp( 1rem, 1.4vw, 1.28rem );
	font-weight: 500;
	line-height: 1.2;
	letter-spacing: -0.015em;
}

.ti-ledger__month {
	margin: 0;
	color: var( --ti-muted ) !important;
	font-variant-numeric: tabular-nums;
	text-align: right;
	white-space: nowrap;
}

.ti-ledger__image {
	aspect-ratio: 4 / 3;
	margin: 0;
	overflow: hidden;
	background: var( --ti-well );
}

.ti-ledger__image a,
.ti-ledger__image img {
	width: 100%;
	height: 100%;
}

.ti-ledger__image img {
	object-fit: cover;
	filter: saturate( 0.94 );
	transition: filter 200ms cubic-bezier( 0.16, 1, 0.3, 1 );
}

.ti-ledger__row:hover,
.ti-ledger__row:focus-within {
	background: var( --ti-well );
}

.ti-ledger__row:hover .ti-ledger__title,
.ti-ledger__row:focus-within .ti-ledger__title {
	color: var( --ti-accent );
}

.ti-ledger__row:hover .ti-ledger__image img,
.ti-ledger__row:focus-within .ti-ledger__image img {
	filter: saturate( 1.05 );
}

/*
 * The event rail was here — a horizontal scroll-snap carousel of small event cells, headed
 * "Upcoming events", between the next-event band and the news section. Removed, not hidden:
 * the pattern, this section, the rail-only query filters and the gettext strings all went.
 * See D52 in `docs/08-decisoes.md`.
 *
 * It was the only surface on the home page that repeated content already shown elsewhere,
 * and it only had anything to show because four future events had been fabricated to fill
 * it. Events are represented once on the home page now, by `.ti-upcoming`.
 */

/* 10. Article ---------------------------------------------------------- */

/*
 * `min-height`, not `height`.
 *
 * With a fixed height the panel inside it overflowed and the stage clipped the overflow, which
 * ate the panel's own bottom padding: measured on the event page once the presenting credit
 * became a third line, the panel ran 24px past the stage and the credit ended up 17px from the
 * bottom edge instead of the 40px the padding declares. Text that close to the edge of a
 * photograph reads as a mistake.
 *
 * The clamp was always the intended size of the stage, and as a floor it still is — nothing
 * gets shorter. It only grows when the billing needs the room, which is the one case a fixed
 * height could not express.
 *
 * The grid is what keeps the content on the floor of the stage. `min-height` alone moved it to
 * the top — measured: the panel ended 323px above the stage's bottom edge — because the panel
 * anchored itself with `height: 100%`, and a percentage height against a parent that no longer
 * has a definite one resolves to `auto`. `align-content: end` states the anchoring on the
 * container instead of borrowing it from a height, so it holds whether the stage is at its
 * floor or grown past it.
 */
.ti-article__stage {
	position: relative;
	display: grid;
	align-content: end;
	min-height: clamp( 22rem, 56svh, 32rem );
	margin: 0;
	padding: 0 !important;
	overflow: hidden;
	background: var( --ti-well );
	isolation: isolate;
}

.ti-article__image {
	position: absolute;
	inset: 0;
	z-index: 0;
	margin: 0;
}

.ti-article__image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * The aimed crop. The measurement that chose 30% is in `inc/responsive-images.php`, beside the
 * filter that decides which images get this class: a centred slice of a portrait cover showed
 * 38% to 62% of the photograph, which on the flagship event is the torso below the subject's
 * face. This is also what keeps the burned-in brand mark in the bottom 11% out of frame.
 */
.ti-article__image img.ti-stage-image--portrait {
	object-position: 50% 30%;
}

.ti-article__stage::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(
		to top,
		color-mix( in srgb, var( --ti-shade ) 95%, transparent ) 0,
		color-mix( in srgb, var( --ti-shade ) 88%, transparent ) 40%,
		color-mix( in srgb, var( --ti-shade ) 34%, transparent ) 76%,
		color-mix( in srgb, var( --ti-shade ) 26%, transparent ) 100%
	);
}

.ti-article__panel {
	position: relative;
	z-index: 2;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	align-items: flex-start;
	gap: 0.85rem;
	width: 100%;
	max-width: 86rem;
	margin-inline: auto;
	padding: 0 var( --wp--preset--spacing--40 ) clamp( 2rem, 4.5vh, 3rem );
	color: var( --ti-on-image );
}

.ti-article__title {
	max-width: 26ch;
	margin: 0;
	color: var( --ti-on-image );
	font-family: var( --wp--preset--font-family--body );
	font-size: clamp( 1.7rem, 2.8vw, 2.6rem );
	font-weight: 400;
	line-height: 1.08;
	letter-spacing: -0.026em;
	text-wrap: balance;
	hyphens: manual;
}

.ti-article__meta {
	margin: 0;
	color: var( --ti-on-image );
}

.ti-article__meta :where( a, time ),
.ti-article__meta .wp-block-post-date,
.ti-article__meta .wp-block-post-terms {
	color: inherit !important;
}

/*
 * The category link sits on its own in the meta row, so it is not exempt as text
 * inside a sentence. Measured at 16px tall; the box now reaches 44px.
 */
.ti-article__meta .wp-block-post-terms a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

/* The field comes from the section's class — see the note on `.ti-dispatches`. */

/*
 * `.ti-article__send` is the row that closes an article. It is listed beside the post
 * content everywhere below, rather than styled on its own, because anything appended to
 * the reading has to answer to the same left edge as the reading — and that edge is
 * computed here, in one place. A separate rule that recomputed it would drift.
 *
 * Measured before this pairing, at 1440px: the prose started at x=183 and the share row
 * at x=32, three different left edges on one page counting the title.
 */
.ti-prose .wp-block-post-content > *,
.ti-prose .ti-article__send > * {
	max-width: 70ch;
}

/*
 * The article keeps its 70ch reading measure but leaves the mathematical
 * centre on desktop. The offset reconnects the prose with the left-aligned
 * cover while preserving a deliberate right field for visual breathing room.
 */
@media ( min-width: 1024px ) {
	.ti-prose > .wp-block-post-content,
	.ti-prose > .ti-article__send {
		margin-inline-start: max(
			var( --wp--preset--spacing--40 ),
			10vw,
			calc( ( 100vw - 86rem ) / 2 + 4rem )
		) !important;
		margin-inline-end: auto !important;
	}
}

.ti-prose .wp-block-post-content > * + * {
	margin-block-start: 1.5em;
}

.ti-prose :where( h2, h3 ) {
	max-width: 24ch;
	margin-block-start: 2.2em;
	font-family: var( --wp--preset--font-family--display );
	font-size: 1.35rem;
	font-weight: 400;
	line-height: 1.2;
}

.ti-prose strong {
	color: var( --ti-strong );
	font-weight: 600;
}

/*
 * Editorial figures reserve their space while an image decodes or fails. The
 * neutral well prevents layout collapse without pretending missing media exists.
 */
.ti-prose figure {
	margin-inline: 0;
}

.ti-prose :where( figure, .wp-block-image ) img {
	width: 100%;
	height: auto;
	min-height: 4rem;
	background: var( --ti-well );
}

/* 11. Events, provided by the ti-events plugin --------------------- */

/*
 * There used to be a per-block retune here, naming every plugin block that needed light-field
 * values. It existed only because the blocks declared the contract tokens on their own classes,
 * and a declaration on the element outranks inheritance — so the theme had to reach in with a
 * descendant selector for each one, and every new block meant editing this list.
 *
 * The blocks now declare nothing and read the contract, so they inherit the field they are in.
 * The list, and the maintenance it required, are gone.
 */

/*
 * The next event used to run on `ti-counter-field`, the cream field.
 *
 * Measured at 390x844, the pinned device: the hero stage was 544px and this band was
 * 595px. The band below the hero was larger than the hero, it was the only high-luminance
 * field on the route, and the scene this site is designed for is a dark room at night. So
 * three commitments broke at once — one dominant element per page, phone first at night,
 * and the visitor understanding in one screen that there is a next party.
 *
 * It now runs on ember black, one step off the page so the band still reads as its own
 * field, and the orange ticket button is the only light in it. Cream stays where a light
 * field earns its keep: long-form reading.
 */
/*
 * The next event is where the page turns light, and the red rule is what marks the turn.
 *
 * The band carries no background of its own: it runs on the light field like the two sections
 * below it, so the home reads as one dark arrival followed by a light page. What separates it
 * from the news underneath is the rule, not a third value — the accent doing the one job it has
 * on this site, which is marking where the action is. 2px, because 1px across a 525px band reads
 * as a seam rather than as a mark.
 *
 * Two earlier attempts are worth leaving recorded. It was the cream field, then it was made dark
 * because cream was the only high-luminance field on the route and measured taller than the hero
 * on a phone — a white flash in the dark room this site is designed for. Then the news and the
 * events rail both went light, which removed that argument and created the opposite one: on the
 * Well it measured 1.0:1 against the hero, because the hero uses the same token as its image
 * placeholder, so the band and the arrival were one continuous mass.
 */
.ti-upcoming {
	border-block-start: 2px solid var( --ti-accent );
}

.ti-upcoming .ti-event-card__title {
	font-family: var( --wp--preset--font-family--body );
	font-weight: 400;
}

/*
 * The cards used to be stacked here, one under the other with a rule between them. That was
 * a misreading of the brief: the owner asked for one card with a carousel through it, and
 * three copies only made the page 1100px longer. The block owns the carousel now, so the
 * theme has nothing to say about how the set is arranged — see D54.
 */

/*
 * The passage band, between the photographic stage and the reading.
 *
 * It closes with a rule rather than opening with one, so it reads as belonging to the stage
 * above it instead of introducing the prose below. No field colour: the band is a line of
 * type on the page's own black, which is what keeps it from becoming a strip of furniture.
 */
.ti-event__passage {
	padding-block: 0;
	border-block-end: 1px solid var( --ti-line );
}

/* The event date sits on the photographic stage, so it stays white. */
.ti-event__stage-date {
	width: 100%;
	max-width: 100%;
	margin-inline: 0;
}

.ti-event__stage-date :where( time, a, span ) {
	max-width: 100%;
	overflow-wrap: anywhere;
}

.ti-event__stage-date,
.ti-event__stage-date .ti-event-status {
	color: var( --ti-on-image );
}

/*
 * The gutter lives on the base rule, not only inside the desktop grid query.
 * Without it the fact sheet rules and labels ran to x=0 on a phone while the
 * gallery below kept its gutter.
 */
.ti-event__layout {
	padding: clamp( 2.5rem, 4vw, 4rem ) var( --wp--preset--spacing--40 );
}

.ti-event__layout .ti-prose {
	padding-block: 0;
	background: transparent;
}

/*
 * The article template offsets its prose from the left gutter to escape the
 * mathematical centre of a full-width page. Inside this two-column grid that
 * offset has nothing to escape: it pushed the intro about 200px right of the
 * gallery and the rail, giving the band three different left edges.
 */
.ti-event__layout .ti-prose > .wp-block-post-content {
	margin-inline: 0 !important;
}

.ti-event__record {
	padding-block: 0 clamp( 3rem, 5vw, 5rem );
}

.ti-event__record > * + * {
	margin-block-start: clamp( 2.5rem, 4vw, 4rem );
}

/*
 * After movie and sponsors are a coda, not sidebar furniture.
 *
 * They lived in the fact rail and made it about 1.100px tall next to a two-line
 * intro, which left the reading column as a black void of the same height. A
 * 9:16 film and a credit list are also both full-width artefacts by nature, so
 * they now close the page as their own band.
 */
.ti-event__coda {
	display: grid;
	gap: clamp( 2.5rem, 5vw, 4rem );
	width: 100%;
	max-width: calc( 86rem + 2 * var( --wp--preset--spacing--40 ) );
	margin-inline: auto;
	padding: clamp( 2.5rem, 4vw, 3.5rem ) var( --wp--preset--spacing--40 ) clamp( 3rem, 5vw, 4.5rem );
	border-block-start: 1px solid var( --ti-line );
}

/*
 * `:empty` does not work here: both blocks render an empty string, but the
 * template's own newlines remain as text nodes, so the group is never empty in
 * the CSS sense. It printed a rule and a band of padding on every event with
 * no film and no sponsor. `:has()` looks at element children only.
 */
.ti-event__coda:not( :has( * ) ) {
	display: none;
}

/*
 * The after movie is a field of its own, because a 9:16 artefact cannot fill a 1376px row.
 *
 * Third attempt, and the two before it are worth keeping because they were both reasonable.
 * First the film sat in the fact rail and made it 1.100px tall beside a two-line intro. Then it
 * moved into the coda as a full-width band capped at 14rem — which removed the tall rail and
 * replaced it with the same void rotated 90 degrees: measured on the flagship event at 1440,
 * the coda was 1424 wide and the poster 224x426 at x32, so roughly 1150x426px of empty page
 * sat beside it. A grid row cannot fix it either, because nothing else on the page is 426px
 * tall: pairing the film with the credits and the share row still leaves about 250px of void.
 *
 * The mistake in all of it was treating the width beside a vertical film as space to fill.
 * There is nothing honest to put there. So the width becomes field instead of gap: the film
 * closes the page on the counter field, centred, and the dark around it reads as a cinema
 * plate rather than as an unfinished row. This is also what the pinned direction asks for —
 * a warm white page with photography in dark bands.
 *
 * The frame keeps its own cap rather than filling the band: at 24rem the 9:16 piece would be
 * 683px tall and the band would own more of the page than the album it belongs to.
 */
.ti-event__film {
	padding-block: clamp( 2.5rem, 4vw, 3.5rem );
	padding-inline: var( --wp--preset--spacing--40 );
}

/*
 * Same reason as the coda below: the block renders an empty string when the event has no film,
 * but the template's newlines survive as text nodes, so `:empty` never matches and the band
 * printed as a dark stripe of padding on every event without one. `:has()` counts elements.
 */
.ti-event__film:not( :has( * ) ) {
	display: none;
}

.ti-event__film > .ti-after-movie {
	align-items: center;
	text-align: center;
}

.ti-event__film .ti-after-movie__frame {
	width: min( 100%, 16rem );
}

/*
 * Ledger cells are placed explicitly. An event without an event date renders
 * no date block at all, and implicit placement would then shift the title into
 * the year column and blow the thumbnail up to the full row.
 */
.ti-ledger__year {
	grid-column: 1;
}

.ti-ledger__title {
	grid-column: 2;
}

.ti-ledger__place {
	grid-column: 3;
}

.ti-ledger__month {
	grid-column: 4;
	justify-content: flex-end;
}

.ti-ledger__image {
	grid-column: 5;
}

.ti-ledger__filters {
	margin-block-end: clamp( 1.5rem, 3vw, 2.5rem );
}

@media ( min-width: 64rem ) {
	.ti-event__layout {
		display: grid;
		grid-template-columns: minmax( 0, 1fr ) minmax( 15rem, 20rem );
		gap: clamp( 2.5rem, 5vw, 5rem );
		width: 100%;
		max-width: calc( 86rem + 2 * var( --wp--preset--spacing--40 ) );
		margin-inline: auto;
	}

	.ti-event__aside {
		position: sticky;
		top: calc( 4.75rem + 1.5rem );
		align-self: start;
		display: flex;
		flex-direction: column;
		gap: 2rem;
	}
}

/* 12. Adjacent article previews ---------------------------------------- */

.ti-adjacent {
	width: 100%;
	max-width: calc( 86rem + 2 * var( --wp--preset--spacing--40 ) );
	margin-inline: auto;
	padding: 0 var( --wp--preset--spacing--40 ) clamp( 3rem, 5vw, 4.5rem );
}

.ti-adjacent__grid {
	display: grid;
	grid-template-columns: repeat( 2, minmax( 0, 1fr ) );
	border-block: 1px solid var( --ti-line );
}

.ti-adjacent__card {
	display: grid;
	grid-template-columns: minmax( 8rem, 0.42fr ) minmax( 0, 0.58fr );
	align-items: stretch;
	gap: clamp( 1rem, 2vw, 1.75rem );
	min-height: 10rem;
	padding-block: 1.25rem;
	color: var( --ti-strong );
	text-decoration: none;
}

.ti-adjacent__card--previous {
	padding-inline-end: clamp( 1rem, 2vw, 1.75rem );
}

.ti-adjacent__card--next {
	grid-template-columns: minmax( 0, 0.58fr ) minmax( 8rem, 0.42fr );
	padding-inline-start: clamp( 1rem, 2vw, 1.75rem );
	border-inline-start: 1px solid var( --ti-line );
}

.ti-adjacent__card--next .ti-adjacent__media {
	grid-column: 2;
}

.ti-adjacent__card--next .ti-adjacent__copy {
	grid-column: 1;
	grid-row: 1;
	align-items: flex-end;
	text-align: right;
}

/*
 * One neighbour takes the whole row.
 *
 * It used to sit in the second column, which is where "next" belongs when "previous" exists
 * beside it. Alone, that left half the band empty: measured at 1440 on the upcoming event, the
 * only card started at x720 and x32–720 carried nothing. With one card there is no pair to
 * balance, so it reads left to right like every other row — photograph, then direction and title.
 */
@media ( min-width: 782px ) {
	.ti-adjacent--single .ti-adjacent__grid {
		grid-template-columns: minmax( 0, 1fr );
	}

	.ti-adjacent--single .ti-adjacent__card,
	.ti-adjacent--single .ti-adjacent__card--next {
		grid-column: 1;
		grid-template-columns: minmax( 10rem, 22rem ) minmax( 0, 1fr );
		padding-inline: 0;
		border-inline-start: 0;
	}

	.ti-adjacent--single .ti-adjacent__card--next .ti-adjacent__media {
		grid-column: 1;
	}

	.ti-adjacent--single .ti-adjacent__card--next .ti-adjacent__copy {
		grid-column: 2;
		align-items: flex-start;
		text-align: left;
	}

	.ti-adjacent--single .ti-adjacent__card--textonly,
	.ti-adjacent--single .ti-adjacent__card--textonly .ti-adjacent__copy {
		grid-template-columns: minmax( 0, 1fr );
		grid-column: 1;
	}
}

/*
 * A post with no featured image renders no media element, so the card must
 * collapse to a single column. Keeping the two-column track printed an empty
 * dark rectangle where the image would be.
 */
.ti-adjacent__card--textonly {
	grid-template-columns: minmax( 0, 1fr );
}

.ti-adjacent__card--textonly .ti-adjacent__copy {
	grid-column: 1;
}

.ti-adjacent__media {
	width: 100%;
	min-width: 0;
	align-self: center;
	aspect-ratio: 4 / 3;
	overflow: hidden;
	background: var( --ti-well );
}

.ti-adjacent__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	filter: saturate( 0.9 );
	transition: filter 180ms cubic-bezier( 0.16, 1, 0.3, 1 );
}

.ti-adjacent__copy {
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: flex-start;
	gap: 0.65rem;
	min-width: 0;
}

.ti-adjacent__direction {
	display: inline-flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.55rem;
	max-width: 100%;
	overflow-wrap: anywhere;
	color: var( --ti-muted );
	font-size: var( --ti-label-size, 0.8125rem );
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
}

.ti-adjacent__arrow {
	width: 1rem;
	height: 1rem;
	flex: 0 0 auto;
}

.ti-adjacent__title {
	max-width: 24ch;
	font-family: var( --wp--preset--font-family--body );
	font-size: clamp( 1rem, 1.5vw, 1.3rem );
	font-weight: 500;
	line-height: 1.18;
	overflow-wrap: anywhere;
	letter-spacing: -0.015em;
	text-wrap: balance;
}

.ti-adjacent__card:hover .ti-adjacent__image,
.ti-adjacent__card:focus-visible .ti-adjacent__image {
	filter: saturate( 1.06 );
}

.ti-adjacent__card:hover .ti-adjacent__title,
.ti-adjacent__card:focus-visible .ti-adjacent__title {
	color: var( --ti-accent );
}

.ti-pagination a {
	color: var( --ti-strong );
	text-decoration: none;
}

/*
 * The current page is marked by a rule, not by red letters.
 *
 * `color: var( --ti-accent )` covered both the hover and the current page here, and it broke the
 * project's own rule: in a dark field the red is a fill, a rule or a mark, never a letter. Measured
 * with the dark palette, `.page-numbers.current` at 12px/600 came to 3.59:1 against the page — under
 * the 4.5:1 floor — while the same declaration measures 5.15:1 on the light page and so never showed
 * up. A failure only one of two palettes reveals is still a failure.
 *
 * Hue was also the only signal, which WCAG 1.4.1 asks it not to be. Full-strength text with an
 * accent rule beneath says "you are here" twice: in colour and in form.
 */
.ti-pagination .current {
	color: var( --ti-strong );
	box-shadow: inset 0 -2px 0 var( --ti-accent );
}

.ti-pagination a:hover,
.ti-pagination a:focus-visible {
	color: var( --ti-strong );
	box-shadow: inset 0 -1px 0 var( --ti-accent );
}

.ti-pagination a,
.ti-pagination .current {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	padding-inline: 0.5rem;
}

.ti-pagination {
	margin-block-start: 2.5rem;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* 13. Footer ---------------------------------------------------------- */

/*
 * The footer closes the page on the counter field. It is the one non-photographic surface that
 * earns the dark treatment: a light page that ends in more light page has no ending, and the
 * brand mark and the account row read better out of the reading field.
 */
.ti-footer {
	--ti-footer-pad: clamp( 3rem, 5vw, 4.5rem );

	/*
	 * Equal box padding does not read as equal space here, and the difference is measured
	 * rather than felt: at 1440 there were 72px between the footer's top edge and the first
	 * ink, and 87px between the last ink and its bottom edge.
	 *
	 * The cause is the closing line. `.ti-footer__legalLink` carries the 44px tap-target
	 * floor around an 11.2px line, so roughly 14px of that box sits below the glyphs with
	 * nothing in it. The floor is an accessibility requirement and stays; the bottom padding
	 * absorbs the overhang instead, which is the only place it can be corrected without
	 * shrinking a target.
	 */
	--ti-footer-overhang: 14px;

	position: relative;
	padding-block: var( --ti-footer-pad )
		calc( var( --ti-footer-pad ) - var( --ti-footer-overhang ) );
}

/*
 * Identity on the left across both rows; the link columns fill the rest.
 *
 * Two earlier attempts failed the same way, and the record is here so neither comes back.
 * First a `space-between` row: a mark on one side, a menu on the other, nothing between.
 * Then a three-column grid — mark, menu, accounts — which only moved the hole: measured at
 * 1440, the mark ended at x=109 and the first menu label started at x=352, so 243px of the
 * 256px identity column was empty, and the accounts sat flush against the container's right
 * edge with 64px of nothing before them.
 *
 * Three items of three different natures cannot be aligned against each other. What fixes
 * it is a column each item belongs to: the mark and the closing band share the identity
 * column, and the links form columns of their own, so every left edge has a reason.
 *
 * Measured target at 1440, container 1376px: identity 372px, three link columns 286px each,
 * the last one reaching the container's right edge. No band wider than the column gap is
 * left empty anywhere.
 */
.ti-footer__grid {
	display: grid;
	grid-template-columns: minmax( 18rem, 1.3fr ) repeat( 3, minmax( 0, 1fr ) );
	grid-template-rows: auto 1fr;
	column-gap: clamp( 2rem, 4vw, 3rem );
	row-gap: clamp( 2.5rem, 4vw, 3.5rem );
}

/*
 * The grid owns the spacing between its children, and nothing else may add to it.
 *
 * This is the measured cause of the misalignment the owner reported, not a precaution.
 * The container is a flow-layout block group, so WordPress emits
 * `> * + * { margin-block-start: var( --wp--style--block-gap ) }` — 1.5rem. Grid honours
 * margins on its items, so the second and third children were pushed 24px below the first
 * while `align-items: start` was still in force: the mark's row box started at y=656 and
 * the menu's at y=680. The grid read as misaligned because two authorities were setting
 * the same distance.
 */
.ti-footer__grid > * {
	margin-block: 0;
}

.ti-footer__brand {
	grid-column: 1;
	grid-row: 1;
}

.ti-footer__brand .ti-brand__mark {
	height: 1.4rem;
}

/*
 * The link columns, as a grid of their own rather than three cells of the outer grid.
 *
 * Nesting is what lets the identity column keep a different width from the link columns
 * while the links stay equal to each other, and it keeps the phone rule to one line.
 */
.ti-footer__directory {
	display: grid;
	grid-column: 2 / -1;
	grid-row: 1 / span 2;
	grid-template-columns: repeat( 3, minmax( 0, 1fr ) );
	column-gap: clamp( 1.5rem, 3vw, 3rem );
	row-gap: 2.25rem;
	align-content: start;
}

.ti-footer__group {
	min-width: 0;
}

/*
 * The label is quieter than the links it names, which is the whole hierarchy here.
 *
 * It cannot repeat the links' own setting: the navigation labels in this theme are 0.7rem
 * uppercase at 0.13em, and a label in the same setting directly above them reads as a
 * fourth link. Smaller, wider tracking and `--ti-muted` separates the two; `--ti-muted` on
 * the counter field measures 6.28:1, above the 4.5:1 floor this size needs.
 */
.ti-footer__groupLabel {
	margin: 0 0 0.5rem;
	color: var( --ti-muted );
	font-size: var( --ti-label-size, 0.8125rem );
	font-weight: 600;
	letter-spacing: 0.16em;
	line-height: 1.4;
	text-transform: uppercase;
}

.ti-footer__links {
	margin: 0;
	padding: 0;
	list-style: none;
}

.ti-footer__linkItem {
	margin: 0;
}

/*
 * Sentence case and 0.9rem, not the uppercase tracked label the row used to be.
 *
 * In a row of seven, uppercase at 0.7rem with 0.13em tracking was the only setting that
 * held them apart. In a column of three under a label, the same setting is just harder to
 * read for no gain. The 44px floor is kept by the box, not by padding around the text, so
 * the left edge of the word still lands on the column's left edge.
 */
.ti-footer__link {
	display: flex;
	align-items: center;
	min-height: 44px;
	color: var( --ti-quiet );
	font-size: 0.9rem;
	line-height: 1.4;
	text-decoration: none;
	text-decoration-color: transparent;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.24em;
	transition: color 160ms ease;
}

.ti-footer__link:hover,
.ti-footer__link:focus-visible {
	color: var( --ti-strong );
	text-decoration: underline;
	text-decoration-color: var( --ti-accent );
}

.ti-footer__link:focus-visible {
	outline: 2px solid var( --ti-accent );
	outline-offset: 3px;
}

/*
 * The closing band sits at the bottom of the identity column, so the mark tops that column
 * and the quiet lines close it. `align-self: end` is what pairs the band with the bottom of
 * the tallest link column instead of leaving a floating full-width row after a gap — the
 * 48px dead band the previous `margin-block-start` produced.
 */
.ti-footer__close {
	display: flex;
	flex-direction: column;
	grid-column: 1;
	grid-row: 2;
	align-self: end;
	gap: 0.25rem;
}

/*
 * The account marks are centred inside a 44px tap target, so their painted edge starts
 * 12px inside the list box while the mark above and the copyright below start at 0.
 * Measured, not estimated: `.ti-social__link` is `min-width: 44px` around a `1.25rem`
 * glyph. Pulling the list back by that 12px is what puts the three left edges of the
 * identity column on one line. It is corrected here, in the theme that composes the block,
 * because the padding is the plugin's own tap-target floor and is right on its own terms.
 */
.ti-footer__close .ti-social {
	margin-inline-start: -0.75rem;
}

/*
 * One running line, and the separation between its parts is space — no bullet
 * glyph. A middot between spans is a character doing a border's job, and it ends
 * up read aloud in the middle of the sentence.
 */
.ti-footer__legal {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0 1.25rem;
	margin: 0;
	color: var( --ti-muted );
	font-size: var( --ti-label-size, 0.8125rem );
	line-height: 1.5;
}

.ti-footer__copyright {
	letter-spacing: 0.1em;
	text-transform: uppercase;
}

.ti-footer__legalLink {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	color: var( --ti-muted );
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.22em;
}

.ti-footer__legalLink:hover,
.ti-footer__legalLink:focus-visible {
	color: var( --ti-strong );
	text-decoration-color: var( --ti-accent );
}

/* --- Theme choice ------------------------------------------------------- */

/*
 * A trigger in the footer's quiet band, and a dialog that shows the themes.
 *
 * The band is where a utility control belongs: used once and then never again, and the header has no
 * room to spare — measured at 390, the navigation overlay already fills the viewport.
 *
 * The trigger names the current answer instead of only naming the control, so a reader learns which
 * theme is in force without opening anything.
 */
.ti-theme {
	margin-block-start: 0.5rem;
}

.ti-theme[hidden] {
	display: none;
}

.ti-theme__trigger {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	min-height: 44px;
	margin: 0;
	padding: 0;
	color: var( --ti-muted );
	background: transparent;
	border: 0;
	font: inherit;
	font-size: var( --ti-label-size, 0.8125rem );
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	cursor: pointer;
}

.ti-theme__trigger:hover,
.ti-theme__trigger:focus-visible {
	color: var( --ti-strong );
}

/*
 * The current theme reads at full strength against the label, so the pair is a quiet word followed by
 * an answer rather than two equal words. The rule under it is the same accent rule the rest of the
 * site uses for a live value.
 */
.ti-theme__value {
	color: var( --ti-strong );
	box-shadow: inset 0 -1px 0 var( --ti-accent );
}

/* --- The dialog --------------------------------------------------------- */

/*
 * `<dialog>` and `showModal()`, the same element the gallery viewer uses. Focus containment, Escape
 * and the backdrop come with it; none of the three is worth reimplementing.
 */
.ti-theme__dialog {
	width: min( 100% - 2rem, 30rem );
	max-height: calc( 100vh - 4rem );
	margin: auto;
	padding: 0;
	overflow: auto;
	color: var( --ti-strong );
	background: var( --ti-field );
	border: 1px solid var( --ti-line );
	border-radius: 0.25rem;
}

.ti-theme__dialog::backdrop {
	background: var( --ti-veil );
}

.ti-theme__panel {
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
	padding: clamp( 1.25rem, 4vw, 1.75rem );
}

.ti-theme__head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
}

/*
 * `h2` because the footer sits under the page's own `h1` and this dialog is not a section of the
 * document outline — it is a panel. The level is sequential, not chosen for its size: the size comes
 * from the declaration below.
 */
.ti-theme__title {
	margin: 0;
	color: var( --ti-strong );
	font-size: 1.05rem;
	font-weight: 500;
	letter-spacing: -0.01em;
}

.ti-theme__close {
	display: inline-flex;
	flex: none;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	margin: -0.6rem -0.6rem 0 0;
	padding: 0;
	color: var( --ti-muted );
	background: transparent;
	border: 0;
	cursor: pointer;
}

.ti-theme__close:hover,
.ti-theme__close:focus-visible {
	color: var( --ti-strong );
}

.ti-theme__close svg {
	width: 0.875rem;
	height: 0.875rem;
	fill: none;
	stroke: currentcolor;
	stroke-width: 1.5;
	stroke-linecap: round;
}

/* --- The options -------------------------------------------------------- */

.ti-theme__options {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

/*
 * Off screen rather than hidden: a `display: none` radio cannot be focused, and the keyboard movement
 * of a radio group is the reason these are native inputs.
 */
.ti-theme__input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	padding: 0;
	overflow: hidden;
	clip-path: inset( 50% );
	white-space: nowrap;
}

.ti-theme__option {
	display: grid;
	grid-template-columns: auto minmax( 0, 1fr ) auto;
	align-items: center;
	gap: 0.9rem;
	min-height: 44px;
	padding: 0.75rem;
	border: 1px solid transparent;
	border-radius: 0.1875rem;
	cursor: pointer;
}

.ti-theme__option:hover {
	background: var( --ti-well );
}

/*
 * Selected is said three ways: the border, the check mark, and the name at full strength. None of
 * them is hue alone, which is what WCAG 1.4.1 asks of a state.
 */
.ti-theme__input:checked + .ti-theme__option {
	border-color: var( --ti-accent );
	background: var( --ti-well );
}

.ti-theme__input:focus-visible + .ti-theme__option {
	outline: 2px solid var( --ti-accent );
	outline-offset: 2px;
}

.ti-theme__text {
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
	min-width: 0;
}

.ti-theme__name {
	color: var( --ti-quiet );
	font-size: 0.95rem;
	font-weight: 500;
}

.ti-theme__input:checked + .ti-theme__option .ti-theme__name {
	color: var( --ti-strong );
}

.ti-theme__hint {
	color: var( --ti-muted );
	font-size: 0.8rem;
	line-height: 1.45;
}

.ti-theme__check {
	width: 1rem;
	height: 1rem;
	fill: none;
	stroke: var( --ti-accent );
	stroke-width: 1.75;
	stroke-linecap: round;
	stroke-linejoin: round;
	opacity: 0;
}

.ti-theme__input:checked + .ti-theme__option .ti-theme__check {
	opacity: 1;
}

/* --- The swatches ------------------------------------------------------- */

/*
 * The one thing the two earlier controls never did: show the theme.
 *
 * Page colour, a bar of its primary text and a shorter bar of the brand — three slots, which is what
 * makes a world recognisable at this size. The colours arrive as `--ti-swatch-*` from
 * `inc/color-scheme.php`, generated from `theme.json` and `styles/dark.json`, so a retune of either
 * palette is reflected here with nothing to edit.
 */
.ti-theme__swatch {
	position: relative;
	display: flex;
	flex: none;
	flex-direction: column;
	justify-content: flex-end;
	gap: 0.3rem;
	width: 3.5rem;
	height: 2.5rem;
	padding: 0.5rem;
	overflow: hidden;
	background: var( --ti-swatch-page );
	border: 1px solid var( --ti-line );
	border-radius: 0.125rem;
}

.ti-theme__swatchInk,
.ti-theme__swatchAccent {
	height: 0.1875rem;
	border-radius: 999px;
}

.ti-theme__swatchInk {
	width: 100%;
	background: var( --ti-swatch-ink );
}

.ti-theme__swatchAccent {
	width: 45%;
	background: var( --ti-swatch-accent );
}

/*
 * The system swatch is the two palettes side by side, because that option is not a third look — it is
 * whichever of these two the device asks for. The right half is painted over the left one, and the
 * bars inside it carry the alternate colours through a second copy clipped to that half.
 */
.ti-theme__swatch--system::after {
	content: "";
	position: absolute;
	inset: 0 0 0 50%;
	background:
		linear-gradient( to right, var( --ti-swatch-accent-alt ) 0 45%, transparent 45% ) no-repeat 0.5rem calc( 100% - 0.5rem ) / calc( 100% - 1rem ) 0.1875rem,
		linear-gradient( to right, var( --ti-swatch-ink-alt ) 0 100%, transparent 100% ) no-repeat 0.5rem calc( 100% - 0.9875rem ) / calc( 100% - 1rem ) 0.1875rem,
		var( --ti-swatch-page-alt );
}

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

/*
 * The knob's travel is the state changing, so with motion off it must still arrive — it simply
 * arrives at once. Removing the transition is enough; nothing here starts from invisible.
 */
@media ( prefers-reduced-motion: reduce ) {
	.ti-scheme__knob {
		transition: none;
	}
}

@media ( prefers-reduced-motion: reduce ) {
	.ti-footer__link {
		transition-duration: 0.01ms;
	}
}


/* 14. Responsive ------------------------------------------------------- */

@media ( max-width: 782px ) {
	.admin-bar .wp-site-blocks {
		--ti-admin-bar-offset: 46px;
	}

	.admin-bar .wp-site-blocks > header.wp-block-template-part {
		top: 46px;
	}
}

@media ( max-width: 781px ) {
	.ti-header {
		min-height: 4rem;
		padding-block: 0.7rem;
	}

	.ti-brand__mark {
		height: 1.3rem;
	}

	.ti-arrival__stage {
		height: clamp( 26rem, 72svh, 34rem );
	}

	.ti-arrival__title {
		max-width: 20ch;
		font-size: clamp( 1.5rem, 6.5vw, 2.1rem );
	}

	.ti-section,
	.ti-prose {
		padding-block: 3.25rem;
	}

	.ti-route-head {
		gap: 1.1rem;
		padding-block: 3rem;
	}

	/* Kept in step with the desktop label; it used to grow to 2.8rem here. */
	.ti-route-head__title {
		font-size: clamp( 1.5rem, 6vw, 1.9rem );
	}

	/* Compact rows keep the thumbnail, on the same left rail as the desktop. */
	.ti-index {
		grid-template-columns: 5.5rem minmax( 0, 1fr );
		gap: 0.9rem;
		padding-block: 0.9rem;
	}

	.ti-index__list > li:first-child .ti-index {
		grid-template-columns: minmax( 0, 1fr );
		gap: 0.85rem;
		padding-block: 0 1.25rem;
	}

	.ti-index__list > li:first-child .ti-index__image,
	.ti-index__list > li:first-child .ti-index__copy {
		grid-column: 1;
	}

	.ti-index__list > li:first-child .ti-index__image {
		grid-row: 1;
		aspect-ratio: 16 / 9;
	}

	.ti-index__list > li:first-child .ti-index__copy {
		grid-row: 2;
	}

	.ti-index__list > li:first-child .ti-index__title {
		font-size: clamp( 1.2rem, 5.5vw, 1.55rem );
	}

	.ti-ledger__row {
		grid-template-columns: 3.5rem minmax( 0, 1fr ) 4.5rem;
		gap: 0.9rem;
		padding-block: 0.65rem;
	}

	/* The date shares the title track on narrow screens; four columns do not fit. */
	.ti-ledger__year {
		grid-column: 1;
		grid-row: 1 / span 2;
	}

	.ti-ledger__title {
		grid-column: 2;
		grid-row: 1;
	}

	.ti-ledger__place {
		grid-column: 2;
		grid-row: 2;
		margin-block-start: 0.25rem;
	}

	/* The place takes row 2 on a phone, so the date follows it in row 3. */
	.ti-ledger__month {
		grid-column: 2;
		grid-row: 3;
		margin-block-start: 0.2rem;
		font-size: 0.78rem;
		/* The element is a flex box, so text-align alone does nothing here. */
		justify-content: flex-start;
		text-align: left;
	}

	.ti-ledger__image {
		grid-column: 3;
		grid-row: 1 / span 3;
	}

	.ti-adjacent {
		padding-block-end: 3.25rem;
	}

	.ti-adjacent__grid {
		grid-template-columns: 1fr;
	}

	.ti-adjacent__card,
	.ti-adjacent__card--next {
		grid-template-columns: min( 7.5rem, 32vw ) minmax( 0, 1fr );
		gap: 1rem;
		min-height: 0;
		padding: 1rem 0;
		border-inline-start: 0;
	}

	.ti-adjacent__card--next {
		border-block-start: 1px solid var( --ti-line );
	}

	.ti-adjacent__card--next .ti-adjacent__media {
		grid-column: 1;
	}

	.ti-adjacent__card--next .ti-adjacent__copy {
		grid-column: 2;
		grid-row: 1;
		align-items: flex-start;
		text-align: left;
	}

	.ti-adjacent__card--textonly,
	.ti-adjacent__card--next.ti-adjacent__card--textonly {
		grid-template-columns: minmax( 0, 1fr );
	}

	.ti-adjacent__card--textonly .ti-adjacent__copy,
	.ti-adjacent__card--next.ti-adjacent__card--textonly .ti-adjacent__copy {
		grid-column: 1;
	}

	.ti-adjacent--single .ti-adjacent__card--next {
		grid-column: 1;
	}
}

/*
 * The footer stacks before the theme's tablet breakpoint, and the number is the account
 * row, not a device.
 *
 * `.ti-social` in the compact presentation is six 44px targets plus five 0.25rem gaps:
 * 284px, measured. The identity column holds that on one line only while it stays wider
 * than 284px, which at the 1376px container and a 3rem gap stops happening a little above
 * 1024. Below that the identity column takes the full width and the accounts keep their
 * single line, instead of breaking into two rows inside a narrow column.
 */
@media ( max-width: 1079px ) {
	/*
	 * No overhang correction in the stack. Here the brand mark is the first ink, and it is
	 * centred inside its own 44px link box, so the top carries an inset of its own: measured
	 * 62 against 67 at 1024, and 59 against 63 at 390. Subtracting 14px from the bottom
	 * would tip the surface the other way.
	 */
	.ti-footer {
		--ti-footer-overhang: 0px;
	}

	.ti-footer__grid {
		grid-template-columns: minmax( 0, 1fr );
		grid-template-rows: auto auto auto;
		row-gap: clamp( 2rem, 4vw, 2.75rem );
	}

	.ti-footer__brand {
		grid-row: 1;
	}

	/*
	 * Order matters once it is a stack: navigation before the closing band. The
	 * copyright is what ends the page, so it cannot come before the links.
	 */
	.ti-footer__directory {
		grid-column: 1;
		grid-row: 2;
	}

	.ti-footer__close {
		grid-row: 3;
		align-self: start;
	}
}

/*
 * One column on a phone, and the two rejected alternatives are recorded because both
 * looked reasonable in the markup and failed on the capture.
 *
 * Three columns give each about 98px at 390, and "Patrocinadores" measures roughly 105px
 * at this setting, so the longest name in every group would wrap — the same failure as the
 * row this replaced, one level down.
 *
 * Two columns do not wrap, but three groups do not divide by two: measured at 390, the
 * third group landed alone in the left cell of a second row with its right cell empty,
 * which is the dead band the desktop grid exists to remove, reintroduced on the smallest
 * screen. One column has no such cell.
 */
@media ( max-width: 600px ) {
	.ti-footer__directory {
		grid-template-columns: minmax( 0, 1fr );
		row-gap: 1.75rem;
	}
}

@media ( max-width: 520px ) {
	.ti-index__excerpt {
		display: none;
	}

	.ti-index__list > li:first-child .ti-index__excerpt {
		display: -webkit-box;
		-webkit-line-clamp: 2;
	}
}

/* ---------------------------------------------------------------------------
 * Music route (ti-music)
 *
 * Kept in its own section, deliberately away from the article prose rules, so
 * that work on the article surfaces and work on this route do not edit the same
 * lines. Nothing here changes article behaviour: the opt-out below applies only
 * to a container that carries `ti-prose--in-column`, and no article template
 * uses that class.
 * ------------------------------------------------------------------------- */

/*
 * The desktop offset on `.ti-prose > .wp-block-post-content` assumes the cover
 * photograph it was written to pair with. A music entry has no cover: its prose
 * sits in the constrained column under a heading, with the player card below,
 * so it inherited a misalignment instead of an intentional offset.
 *
 * Measured at 1440px before this opt-out: the paragraph started at x=168 while
 * the title and the card both started at x=368. After it, all three share 368.
 */
@media ( min-width: 1024px ) {
	.ti-prose--in-column > .wp-block-post-content,
	.ti-prose--in-column > .ti-article__send {
		margin-inline: auto !important;
	}
}

/* ti-music: inicio da seção. Remoção deve ser delimitada por estes marcadores. */

/*
 * O retune do card de música em campo claro saiu daqui.
 *
 * Ele existia porque o bloco declarava os tokens na própria classe, e declaração no elemento
 * vence herança — então o tema precisava alcançá-lo por seletor descendente. O bloco agora só
 * lê o contrato, herda o campo em que está, e `--ti-quiet` virou token de primeira classe
 * declarado nos dois campos. Nada precisa nomear este bloco.
 */

/* ti-music: fim da seção. */

/* 15. Artists, provided by the ti-artists plugin ---------------------- */

/*
 * The plugin owns the line-up block and its own tokens. What lives here is the
 * two surfaces the theme composes: the directory and one artist's page.
 */

/*
 * The directory itself is the plugin's `ti-artists/directory` block, which shares
 * its presentation with the line-up. The theme used to style a core Query Loop
 * here; that version showed four names in a void because the core featured-image
 * block emits nothing when a post has no image.
 */

/*
 * One artist, read as a portfolio.
 *
 * The page answers two readers, and the layout is built on that split rather than
 * on decoration. Someone who came to listen wants the sets and the nights; someone
 * deciding a line-up wants the press kit. Industry guidance on press kits is
 * consistent that these are different errands, so the press kit carries the weight
 * of an action while the listening links stay quiet labels beside it.
 *
 * The portrait is a column, not a banner: a person reads at portrait ratio, and a
 * wide crop of somebody on a dark stage is mostly stage.
 */
/*
 * The width is owned here rather than by a `constrained` block layout. With the
 * core layout on the same element, its `> * { max-width; margin-inline: auto }`
 * centres every grid cell — measured on the 1440 capture as a portrait pinned
 * left and the name starting 400px away from it, with the columns themselves
 * doing nothing. Grid and that layout cannot share an element.
 */
/*
 * One grid for the whole page, not one per band.
 *
 * The previous version stacked three bands that each chose their own origin:
 * measured at 1440, the portrait began at 56px, the name at 432px, the bio at
 * 71px and the nights heading at 56px, with roughly 540px of the right side
 * carrying nothing. Unifying the left padding was not enough, because the problem
 * was never the padding — there was no structure for the bands to share.
 *
 * So the page declares twelve columns once and every band places itself on them.
 * The portrait holds the first five, the text the rest, and the balance is
 * asymmetric by design: heavy image on one side, type and negative space on the
 * other. The narrow left column below the stage becomes the margin-note zone that
 * editorial grids use, which is what gives the lower bands the same axis as the
 * name instead of collapsing back to the page edge.
 */
.ti-artist-page {
	--ti-artist-columns: repeat( 12, minmax( 0, 1fr ) );
	--ti-artist-gutter: clamp( 1.25rem, 2.5vw, 2.25rem );
}

.ti-artist-page > * {
	display: grid;
	grid-template-columns: var( --ti-artist-columns );
	column-gap: var( --ti-artist-gutter );
	width: 100%;
	max-width: 86rem;
	margin-inline: auto;
	padding-inline: var( --wp--preset--spacing--40, 1.5rem );
}

/*
 * Two rows, and the portrait spans both.
 *
 * `align-items: end` used to pin the name to the bottom of the portrait, which
 * left the top right of the page empty and then, once the portrait ended, left the
 * left column empty for 400px while the bio ran on alone. Aligning to the top and
 * letting the image span both rows keeps both columns occupied for the same
 * vertical distance. This is why the bio group sits inside this stage in
 * `templates/single-ti_artist.html` instead of forming a band of its own.
 *
 * That explanation used to be an HTML comment inside the stage group in the
 * template, and it cost three block validation errors on every load of the artist
 * editor: a plain comment inside a block's inner content is recorded in
 * `innerContent`, the `save` output does not reproduce it, and the block is
 * declared invalid. Reasoning about a rule belongs next to the rule, not inside
 * block markup.
 */
.ti-artist-page__stage {
	align-items: start;
	row-gap: clamp( 1.75rem, 3vw, 2.75rem );
	padding-block: clamp( 2rem, 4vw, 3.5rem ) clamp( 2.5rem, 5vw, 4rem );
}

/* The portrait is the mass the rest of the page balances against. */
.ti-artist-page__portrait {
	grid-column: 1 / span 5;
	grid-row: 1 / span 2;
	margin: 0;
}

.ti-artist-page__intro {
	grid-column: 6 / -1;
	grid-row: 1;
}

.ti-artist-page__bio {
	grid-column: 6 / -1;
	grid-row: 2;
}

.ti-artist-page > * > * {
	min-width: 0;
}

.ti-artist-page__lede {
	max-width: 46ch;
	margin: 0;
	color: var( --ti-muted );
	font-size: clamp( 1rem, 1.6vw, 1.15rem );
	line-height: 1.5;
}

/*
 * The one action for the business-facing reader. Bordered rather than filled: it
 * has to outrank the listening labels without competing with the photograph.
 */
.ti-artist-page__kit {
	margin: 0.4rem 0 0;
}

.ti-artist-page__kitLink {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.15rem 0.7rem;
	min-height: 44px;
	padding: 0.7rem 1.2rem;
	border: 1px solid var( --ti-line );
	color: var( --ti-strong );
	font-size: var( --ti-label-size, 0.8125rem );
	letter-spacing: 0.12em;
	text-decoration: none;
	text-transform: uppercase;
}

.ti-artist-page__kitLink:hover,
.ti-artist-page__kitLink:focus-visible {
	border-color: var( --ti-accent );
	color: var( --ti-accent );
}

.ti-artist-page__kitLink:focus-visible {
	outline: 2px solid var( --ti-accent );
	outline-offset: 3px;
}

/*
 * Format and weight, stated before the click. Tabular figures so a column of
 * sizes lines up, which is the one place this page shows measured data.
 */
.ti-artist-page__kitDetail {
	color: var( --ti-muted );
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.08em;
	text-transform: none;
}

/*
 * The bio sits on the same axis as the name above it.
 *
 * `.ti-prose` carries a deliberate left offset for articles, where the prose is
 * the whole page. Here it put the bio on a different axis from the heading it
 * belongs to — name on the right, bio far left, measured on the same capture. The
 * offset is cancelled for this surface only.
 */
/*
 * The bio is a cell of the stage grid, so the grid owns its rhythm.
 *
 * `.ti-prose` carries `padding-block` of up to 80px for article pages, where it is
 * the band itself. Inside a grid row that padding is not rhythm, it is a hole:
 * measured at 175px of nothing between the listening links and the first line of
 * the bio, with the portrait still running alongside.
 */
.ti-artist-page__bio {
	padding-block: 0;
}

.ti-artist-page__bio > .wp-block-post-content {
	max-width: 42rem;
	margin-inline: 0 !important;
	padding-inline: 0 !important;
}

/*
 * The closing band splits: the heading takes the margin-note column the portrait
 * vacated, the list runs on the text column. That is what stops the page from
 * reading as everything pushed left.
 */
.ti-artist-page__events {
	align-items: start;
	row-gap: 1rem;
	padding-block: clamp( 1.75rem, 3vw, 2.5rem ) clamp( 2.5rem, 5vw, 4rem );
	border-block-start: 1px solid var( --ti-line );
}

.ti-artist-page__events > .ti-section__title {
	grid-column: 1 / span 4;
	margin: 0;
	font-size: 1.1rem;
}

.ti-artist-page__events > .ti-artist-page__eventList {
	grid-column: 6 / -1;
}

.ti-artist-page__portrait img {
	width: 100%;
	height: auto;
	object-fit: cover;
	background: var( --ti-well );
}

.ti-artist-page__intro {
	display: flex;
	flex-direction: column;
	gap: 1rem;
}

/*
 * Larger than before, because the name is what balances the portrait's mass. Still
 * capped: an artist name comes from the database, so its length is not something
 * this page controls, and display type with unpredictable input is how a heading
 * ends up hyphenated across four lines.
 */
.ti-artist-page__name {
	max-width: 20ch;
	margin: 0;
	font-family: var( --wp--preset--font-family--display );
	font-size: clamp( 2.2rem, 5vw, 4rem );
	font-weight: 300;
	line-height: 1.08;
	letter-spacing: -0.026em;
	text-wrap: balance;
}

.ti-artist-page__listen,
.ti-artist-page__eventList {
	display: flex;
	flex-wrap: wrap;
	gap: 0.3rem 1.5rem;
	margin: 0;
	padding: 0;
	list-style: none;
}

.ti-artist-page__eventList {
	flex-direction: column;
	gap: 0;
}

.ti-artist-page__listenLink {
	display: inline-flex;
	align-items: center;
	min-width: 44px;
	min-height: 44px;
	color: var( --ti-muted );
	font-size: var( --ti-label-size, 0.8125rem );
	letter-spacing: 0.12em;
	text-transform: uppercase;
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.22em;
}

.ti-artist-page__listenLink:hover,
.ti-artist-page__listenLink:focus-visible {
	color: var( --ti-strong );
	text-decoration-color: var( --ti-accent );
}

.ti-artist-page__event {
	margin: 0;
	border-block-start: 1px solid var( --ti-line );
}

.ti-artist-page__eventLink {
	display: flex;
	align-items: center;
	min-height: 44px;
	padding-block: 0.9rem;
	color: var( --ti-strong );
	font-size: 1.05rem;
	text-decoration: none;
}

.ti-artist-page__eventLink:hover,
.ti-artist-page__eventLink:focus-visible {
	color: var( --ti-accent );
}

/*
 * Below the tablet breakpoint the twelve columns collapse to one. A twelve-column
 * grid on a 390px screen gives each column 25px, which is not a column.
 */
@media ( max-width: 781px ) {
	.ti-artist-page {
		--ti-artist-columns: minmax( 0, 1fr );
	}

	.ti-artist-page__stage {
		align-items: start;
	}

	.ti-artist-page__portrait,
	.ti-artist-page__intro,
	.ti-artist-page__bio,
	.ti-artist-page__events > .ti-section__title,
	.ti-artist-page__events > .ti-artist-page__eventList {
		grid-column: 1 / -1;
		grid-row: auto;
	}

	.ti-artist-page__portrait {
		max-width: 18rem;
	}
}

/* Contact --------------------------------------------------------------- */

.ti-contact__content {
	padding-block: 0 clamp( 3rem, 5vw, 5rem );
}

.ti-contact__channels {
	border-block-start: 1px solid var( --ti-line );
}

.ti-contact__title,
.ti-contact__lede,
.ti-contact__channels .ti-social--named {
	width: min( 100%, 44rem );
	margin-inline: auto;
}

.ti-contact__title {
	margin-block: 0;
	font-size: clamp( 1.4rem, 2.5vw, 2rem );
	font-weight: 450;
	line-height: 1.1;
	letter-spacing: -0.025em;
}

.ti-contact__lede {
	margin-block: 0.75rem 1.5rem;
	color: var( --ti-muted );
	line-height: 1.6;
}

/* 16. Gallery route, provided by the ti-gallery plugin ---------------- */

/*
 * `/photos/<slug>/` reads one album as a gallery. The plugin owns the album, its details and
 * its viewer; what lives here is the head of the route and the rhythm between the two.
 */

.ti-gallery__head {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 0.6rem;
}

/*
 * The way back sits above the title, which is the one place a "go back" belongs: before the
 * thing it leaves. It is a link and not a kicker — the prohibition on labels above titles is
 * about decoration, and this is navigation a guest arriving from a shared URL depends on.
 */
.ti-gallery__back {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	min-height: 44px;
	margin: 0;
	font-size: 0.95rem;
	font-weight: 500;
	letter-spacing: 0.01em;
	text-transform: none;
}

.ti-gallery__back a {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	min-height: 44px;
	color: var( --ti-muted );
	text-decoration: none;
}

.ti-gallery__back a:hover,
.ti-gallery__back a:focus-visible {
	color: var( --ti-strong );
}

.ti-gallery__date {
	margin: 0;
	color: var( --ti-muted );
}

/*
 * Details then photographs, both starting on the container's own left edge.
 *
 * The first version of this was `display: flex` on the section, and it produced three
 * different left edges on one page — measured at 1440: the title at x=24, the photo grid at
 * x=160 and the details panel at x=560. The cause is the same class of mistake as the footer's
 * block-gap: two authorities setting the same thing. A `constrained` group makes WordPress
 * emit `margin-inline: auto` and `max-width: <content-size>` on every child, and turning the
 * section into a flex container does not remove those declarations — it turns them into auto
 * margins on flex items, which is centring.
 *
 * `.ti-route-head` above already solved this, so the override is the theme's existing idiom
 * rather than a new invention: take the layout back explicitly, then state the widths.
 */
.ti-gallery__body {
	display: grid !important;
	grid-template-columns: minmax( 0, 1fr );
	gap: clamp( 1.75rem, 4vw, 2.75rem );
}

.ti-gallery__body > * {
	width: auto !important;
	max-width: none !important;
	margin-inline: 0 !important;
}

/*
 * The details keep a reading measure while the album takes the full field. Two words of credit
 * set across 86rem is not a line of text, it is a caption lost in a margin.
 */
.ti-gallery__body .ti-album-info {
	max-width: 44rem !important;
	padding-block-end: clamp( 1.25rem, 3vw, 2rem );
	border-block-end: 1px solid var( --ti-line );
}

/* 17. Presenting credit on the event stage ---------------------------- */

/*
 * The billing sits under the event's name, on the photograph.
 *
 * Under and not above: a small label above a title is the kicker this project forbids, and it
 * would also push the name down the stage. Under the title the credit reads as what it is —
 * who is presenting the night that was just named.
 *
 * The block itself carries no colour and reads `currentColor`, so it inherits the stage's
 * `--ti-on-image` from `.ti-article__panel` with nothing to override here. What the theme adds
 * is the distance: closer to the title than the panel's own rhythm, because a credit belongs to
 * the line above it rather than floating as a third item in the stack.
 */
.ti-event__presented {
	margin-block-start: -0.35rem;
}

/*
 * The credit is the quietest thing on the stage and must stay that way: measured against the
 * scrim's own floor, the name at 500 weight in `--ti-on-image` clears the text floor, and the
 * role word at 78% of it clears the floor for its size. Anything dimmer fails, so the dimming
 * is declared once in the block and not retuned here.
 */
@media ( max-width: 47.99rem ) {
	/*
	 * On a phone the stage is short and the panel already carries the controls of the slideshow
	 * on the home page. The credit keeps its own line rather than sharing one with the title's
	 * descenders.
	 */
	.ti-event__presented {
		margin-block-start: 0;
	}
}

/* 18. Sponsor brandmarks on this theme's dark surfaces ---------------- */

/*
 * The brandmark contract, answered for every background this theme owns.
 *
 * ti-sponsors emits all of a sponsor's representations and resolves a light field by itself. The
 * dark ones are the theme's to declare, because only the theme knows which surfaces they are:
 * the dark colour scheme, which inverts the page base from #F7F6F2 to #080808 at runtime; the
 * counter field, which is dark whatever the scheme; and a photographic stage.
 *
 * Grouped in one place on purpose. Three surfaces, one answer, so a fourth dark surface added
 * later has one rule to join rather than a behaviour to rediscover.
 */
:where( [data-ti-scheme="dark"] ) .ti-brandmark,
.ti-counter-field .ti-brandmark,
.ti-article__stage .ti-brandmark {
	--ti-brandmark-light: none;
	--ti-brandmark-dark: inline-block;
	--ti-brandmark-name: inline;
}

/*
 * With a knockout supplied the mark takes over from the name. Without one the name stays, which
 * is the whole reason the name is always in the markup.
 */
:where( [data-ti-scheme="dark"] ) .ti-brandmark:has( .ti-brandmark__image--dark ),
.ti-counter-field .ti-brandmark:has( .ti-brandmark__image--dark ),
.ti-article__stage .ti-brandmark:has( .ti-brandmark__image--dark ) {
	--ti-brandmark-name: none;
}

/*
 * Billing is smaller than instinct says.
 *
 * A logo reads louder than type of the same height, so at the size the sponsor name is set the
 * mark would outweigh the event title directly above it — and then it is not billing, it is an
 * advertisement. 1.75rem measured against the card's 33.6px title and the stage's own h1.
 */
.ti-presented__mark {
	--ti-brandmark-height: 1.75rem;
}

/* A plate in a credit row can be larger: there is no title beside it to outrank. */
.ti-sponsors__list--lead .ti-sponsor__mark {
	--ti-brandmark-height: clamp( 3.75rem, 7vw, 5.5rem );
}

.ti-sponsors__list:not( .ti-sponsors__list--lead ) .ti-sponsor__mark {
	--ti-brandmark-height: 3.25rem;
}

/* 19. Offer, experience and earlier events ---------------- */

/*
 * There was a next-date line here, between the slideshow and the season band. The owner removed
 * it on 26/09/2026: the upcoming-events carousel directly below already shows the next event with
 * its ticket, so the line said the same thing twice. The season band keeps its red rule.
 */

/*
 * Earlier events: a dark photographic band. The pinned direction is a warm white page with
 * photography in dark bands, and this is the band that proves the setting.
 */
.ti-past-events-band:not( :has( * ) ) {
	display: none;
}

/* The offer sits on the photograph, so its facts are the on-image white. */
.ti-event__offer {
	margin-block-start: 0.5rem;
	color: var( --ti-on-image );
}

/*
 * The experience follows the reading band directly. The band already closes with its own
 * padding, so a second top padding here stacked into a 144px hole between the fact sheet and
 * this heading — measured at 1440 on the upcoming event.
 */
.ti-event__experience {
	padding-block: 0;
}

.ti-event__experience:not( :has( * ) ) {
	display: none;
}

/*
 * An upcoming event has no album yet, so it has nothing to search. The photo search only
 * belongs beside an album that exists; without one the whole record band goes. `display: none`
 * also removes the field from the accessibility tree, so a screen reader is not offered a
 * search over nothing.
 *
 * The earlier-events band is not placed on the event page. It was, for a day, and the owner
 * rejected it: on an event page it read as a foreign block in the middle of the article.
 */
.ti-event__record:not( :has( .ti-album ) ) {
	display: none;
}

/* 20. Artists: the portfolio ------------------------------------------- */

/*
 * The artist page is a portfolio, and it runs on the dark field.
 *
 * A DJ's page is linked from the artist's own profile and read by bookers and by fans, both in
 * the evening. The light page made it read like a staff directory. On the near-black field the
 * portrait becomes the room's light source, the name can be set at display scale, and the brand
 * orange can mark the details it passes on (6.18:1 on this field).
 */
.ti-artist-page__stage {
	padding-block: clamp( 2.5rem, 6vw, 6rem ) clamp( 3rem, 6vw, 6rem );
}

.ti-artist-page__portrait {
	position: relative;
}

.ti-artist-page__portrait img {
	aspect-ratio: 4 / 5;
	filter: saturate( 0.92 ) contrast( 1.04 );
}

.ti-artist-page__intro {
	gap: clamp( 1.1rem, 2vw, 1.6rem );
	padding-block-start: clamp( 0rem, 4vw, 3rem );
}

/* A name is short and fixed by the artist, which is what earns display scale here. */
.ti-artist-page__name {
	max-width: 14ch;
	font-size: clamp( 2.8rem, 7vw, 5.75rem );
	font-weight: 300;
	line-height: 0.98;
	letter-spacing: -0.035em;
	overflow-wrap: anywhere;
}

.ti-artist-page__lede {
	max-width: 40ch;
	color: var( --ti-quiet );
	font-size: clamp( 1.05rem, 1.6vw, 1.25rem );
	line-height: 1.55;
}

/* Listening links as a quiet row of named destinations, each with a flare underline. */
.ti-artist-page__listen {
	gap: 0.25rem 2rem;
	padding-block-start: 1rem;
	border-block-start: 1px solid var( --ti-line );
}

.ti-artist-page__listenLink {
	color: var( --ti-strong );
	font-size: 0.95rem;
	letter-spacing: 0.01em;
	text-transform: none;
	text-decoration-color: var( --ti-flare );
	text-underline-offset: 0.35em;
}

.ti-artist-page__listenLink:hover,
.ti-artist-page__listenLink:focus-visible {
	color: var( --ti-flare );
	text-decoration-color: currentcolor;
}

/* The press kit is the booker's action: framed, sentence case, the only boxed thing on the page. */
.ti-artist-page__kitLink {
	padding: 0.85rem 1.4rem;
	border-color: color-mix( in srgb, var( --ti-strong ) 40%, transparent );
	font-size: 0.95rem;
	letter-spacing: 0.01em;
	text-transform: none;
}

.ti-artist-page__kitDetail {
	font-size: var( --ti-label-size, 0.8125rem );
	letter-spacing: 0.02em;
}

.ti-artist-page__bio {
	color: var( --ti-quiet );
}

/* Nights played: a ledger of photograph, date and name, the date tabular in the brand orange. */
.ti-artist-page__events > .ti-section__title {
	font-size: clamp( 1.6rem, 2.6vw, 2.25rem );
	font-weight: 300;
	letter-spacing: -0.02em;
}

.ti-artist-page__eventLink {
	display: grid;
	grid-template-columns: 5.5rem minmax( 0, 1fr );
	align-items: center;
	gap: 0.25rem 1.25rem;
	padding-block: 1rem;
}

.ti-artist-page__eventLink:has( .ti-artist-page__eventMedia ) {
	grid-template-columns: 5.5rem 7rem minmax( 0, 1fr );
}

.ti-artist-page__eventDate {
	color: var( --ti-flare );
	font-size: 0.95rem;
	font-variant-numeric: tabular-nums;
}

.ti-artist-page__eventDate--none {
	color: var( --ti-muted );
}

.ti-artist-page__eventMedia {
	display: block;
	overflow: hidden;
	aspect-ratio: 4 / 3;
	background: var( --ti-well );
}

.ti-artist-page__eventMedia img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.ti-artist-page__eventName {
	font-family: var( --wp--preset--font-family--display );
	font-size: clamp( 1.1rem, 1.8vw, 1.45rem );
	font-weight: 400;
	line-height: 1.2;
}

.ti-artist-page__eventLink:hover .ti-artist-page__eventName,
.ti-artist-page__eventLink:focus-visible .ti-artist-page__eventName {
	color: var( --ti-flare );
}

.ti-artist-page__eventItems {
	margin: 0;
	padding: 0;
	list-style: none;
}

.ti-artist-page__eventGroup + .ti-artist-page__eventGroup {
	margin-block-start: 2rem;
}

.ti-artist-page__eventGroupTitle {
	margin: 0 0 0.5rem;
	color: var( --ti-muted );
	font-size: 1rem;
	font-weight: 500;
}

@media ( max-width: 781px ) {
	.ti-artist-page__portrait {
		max-width: none;
	}

	.ti-artist-page__eventLink,
	.ti-artist-page__eventLink:has( .ti-artist-page__eventMedia ) {
		grid-template-columns: 4.5rem minmax( 0, 1fr );
	}

	.ti-artist-page__eventMedia {
		display: none;
	}
}

/*
 * The roster: the directory as a list of names, not a grid of equal cards.
 *
 * Five identical cells with a placeholder plate each read as a staff page. A roster set in the
 * display face, one name per row with its portrait beside it, reads as a label's artist list —
 * which is the reference for a booker. Rows are equal because the artists are of equal rank;
 * the order is the plugin's.
 */
.ti-artist-index {
	padding-block-start: 0;
}

.ti-lineup--directory .ti-lineup__list {
	display: flex;
	flex-direction: column;
	gap: 0;
	border-block-start: 1px solid var( --ti-line );
}

.ti-lineup--directory .ti-artist {
	display: grid;
	grid-template-columns: clamp( 5rem, 9vw, 8rem ) minmax( 0, 1fr );
	align-items: center;
	gap: 0 clamp( 1.25rem, 3vw, 2.5rem );
	max-width: none;
	padding-block: clamp( 1rem, 2vw, 1.5rem );
	border-block-end: 1px solid var( --ti-line );
}

.ti-lineup--directory .ti-artist__cover {
	border-radius: 0;
}

.ti-lineup--directory .ti-artist__copy {
	display: grid;
	grid-template-columns: minmax( 0, 1fr );
	gap: 0.35rem;
}

@media ( min-width: 64rem ) {
	.ti-lineup--directory .ti-artist__copy {
		/*
		 * A fixed third track, not `auto`. Each row is its own grid, so an auto track sized to
		 * that row's links moved the note column: measured at 1440, x876 on rows without links
		 * and x754 on rows with them.
		 */
		grid-template-columns: minmax( 0, 1.1fr ) minmax( 0, 1fr ) 15rem;
		align-items: center;
		gap: 2.5rem;
	}
}

.ti-lineup--directory .ti-artist__name {
	min-width: 0;
	overflow-wrap: anywhere;
	font-family: var( --wp--preset--font-family--display );
	font-size: clamp( 1.7rem, 4vw, 3.25rem );
	font-weight: 300;
	line-height: 1.02;
	letter-spacing: -0.03em;
}

.ti-lineup--directory .ti-artist__name a:hover,
.ti-lineup--directory .ti-artist__name a:focus-visible {
	color: var( --ti-flare );
}

.ti-lineup--directory .ti-artist__note {
	max-width: 44ch;
	color: var( --ti-quiet );
	font-size: 0.95rem;
}

.ti-lineup--directory .ti-artist__listen {
	justify-content: flex-end;
}

.ti-lineup--directory .ti-artist__listenLink {
	color: var( --ti-strong );
	font-size: 0.9rem;
	letter-spacing: 0.01em;
	text-transform: none;
	text-decoration-color: var( --ti-flare );
}

/*
 * Below 30rem the roster stacks: portrait over name. Side by side, a name at 200% text measured
 * 46px past a 320px viewport, because the portrait column is sized in rem and doubles with it.
 */
@media ( max-width: 30rem ) {
	.ti-lineup--directory .ti-artist {
		grid-template-columns: minmax( 0, 1fr );
		gap: 0.75rem;
	}

	.ti-lineup--directory .ti-artist__cover {
		width: min( 6rem, 40% );
	}
}
