/*
 * Kivijalka — component and behaviour CSS.
 * theme.json owns global/element/block defaults. This file owns only the
 * components and behaviours WordPress cannot express. See DESIGN.md.
 */

:root {
	--kivijalka-section-y: var(--wp--preset--spacing--70);
	--kivijalka-radius: 2px;
	--kivijalka-icon-mail: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 7 10 6 10-6"/></svg>');
	--kivijalka-icon-phone: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .3 1.9.6 2.8a2 2 0 0 1-.4 2.1L8 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.5 2.8.6a2 2 0 0 1 1.7 2Z"/></svg>');

	/* Neutral line icons, 1.6 stroke to sit with the type. */
	--kivijalka-icon-draft: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>');
	--kivijalka-icon-plan: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1.5"/><path d="M3 10h7V3M14 21v-7h7"/></svg>');
	--kivijalka-icon-build: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V9l7-5 7 5v12"/><path d="M10 21v-6h4v6"/></svg>');
	--kivijalka-icon-inspect: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="2.6"/></svg>');
	--kivijalka-icon-interior: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 18v-5a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v5"/><path d="M6 10V7a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v3"/><path d="M3 18h18M5 18v2M19 18v2"/></svg>');
	--kivijalka-icon-talk: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H8l-5 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z"/></svg>');
	--kivijalka-icon-home: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="m3 10 9-7 9 7v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z"/><path d="M9 21v-7h6v7"/></svg>');
	--kivijalka-icon-key: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="7.5" cy="15.5" r="4.5"/><path d="m10.8 12.2 9-9M17 4l2.5 2.5M14.5 6.5 17 9"/></svg>');
}

/* ---------------------------------------------------------------- layout --
 * Sections sit flush: kill the root layout gap so full-width bands never
 * show a blank sliver between them (local-gotchas #6).
 */
.wp-site-blocks > header,
.wp-site-blocks > main,
.wp-site-blocks > footer,
.kivijalka-header-sentinel {
	margin-block-start: 0 !important;
}

/* Every full-width section shares one vertical padding value. */
.kivijalka-section {
	padding-block: var(--kivijalka-section-y);
}

/* An intro is a reading-width column that starts at its section's own left
 * edge. Without the explicit start margin, core's layout centres it inside the
 * wider container and it sits indented from everything else in the section —
 * visible as soon as the viewport is wider than the wide size. */
.kivijalka-intro {
	max-width: 54rem;
	margin-inline-start: 0;
	margin-inline-end: auto;
}

/* ------------------------------------------------------------ light band --
 * The ground is dark, so a light section is a deliberate light square placed
 * on it. Everything inside has to reclaim the ink text colours that the dark
 * global default would otherwise inherit in.
 */
.kivijalka-band-light {
	color: var(--wp--preset--color--ink);
}
.kivijalka-band-light :is(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--preset--color--ink);
}
.kivijalka-band-light .kivijalka-eyebrow {
	color: var(--wp--preset--color--accent);
}
.kivijalka-band-light a:not(.wp-block-button__link) {
	color: var(--wp--preset--color--accent-dark);
}
/* A card on a light square is the white square inside it. */
.kivijalka-band-light .kivijalka-card,
.kivijalka-band-light .kivijalka-step {
	background: var(--wp--preset--color--white);
	border-color: var(--wp--preset--color--line);
	color: var(--wp--preset--color--muted);
}
.kivijalka-band-light .kivijalka-card :is(h1, h2, h3, h4, h5, h6),
.kivijalka-band-light .kivijalka-step :is(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--preset--color--ink);
}

/* ------------------------------------------------------------- typography */
.kivijalka-eyebrow {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
	margin-block-end: var(--wp--preset--spacing--30);
}

/* On dark bands the eyebrow uses the soft accent so it stays legible. */
.has-ink-background-color .kivijalka-eyebrow,
.kivijalka-band-dark .kivijalka-eyebrow {
	color: var(--wp--preset--color--accent-soft);
}

/* ------------------------------------------------------------------ grid --
 * auto-fit (not auto-fill) so a short row never leaves a dead track
 * (local-gotchas #5), and zero the is-layout-flow child margin so every card
 * in a row shares the same top (local-gotchas #12).
 */
.kivijalka-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
	gap: var(--wp--preset--spacing--40);
	align-items: stretch;
}
.kivijalka-grid-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
/* One column, whatever the room. For an ordered set of three beside a text
 * column: `auto-fit` would place them 2 + 1 and leave a hole where the fourth
 * would be, which reads as a missing item rather than as a list. */
.kivijalka-grid-1 { grid-template-columns: 1fr; }
.kivijalka-grid > *,
.kivijalka-grid-2 > * {
	margin-top: 0 !important;
	margin-block-start: 0 !important;
}
@media (max-width: 600px) {
	.kivijalka-grid,
	.kivijalka-grid-2 { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------ asymmetric split --
 * An intro column standing beside its content instead of stacked above it.
 * The intro stays put while the content scrolls past it, which is what makes
 * the asymmetry read as deliberate rather than as a stray margin.
 *
 * Auto-flow does the placement: intro top-left, content beside it, and a
 * trailing button row lands back under the intro — which is where it belongs.
 * Below the breakpoint it collapses to the ordinary stack.
 */
@media (min-width: 981px) {
	.kivijalka-split {
		display: grid;
		grid-template-columns: 5fr 7fr;
		/* Equal flexible rows top and bottom centre the intro and its button as
		 * one pair against the taller content column, while keeping them
		 * adjacent — `align-items: center` cannot do this, because they are two
		 * separate grid items and would each centre on their own. Row gap is 0
		 * so the flexible rows are the only thing setting the spacing; the
		 * button carries its own margin. */
		grid-template-rows: 1fr auto auto 1fr;
		column-gap: clamp(2.5rem, 5vw, 5.5rem);
		row-gap: 0;
	}
	.kivijalka-split > .kivijalka-intro { grid-column: 1; grid-row: 2; }
	.kivijalka-split > .wp-block-buttons {
		grid-column: 1;
		grid-row: 3;
		align-self: start;
		margin-top: clamp(1.75rem, 3vw, 2.5rem) !important;
	}
	/* Everything that is neither the intro nor the trailing buttons is the
	 * content column, and it spans the full height so the pair centres on it. */
	.kivijalka-split > *:not(.kivijalka-intro):not(.wp-block-buttons) {
		grid-column: 2;
		grid-row: 1 / -1;
	}
	/* Beats the inline spacing the editor writes onto section children, which
	 * only makes sense while they are stacked. */
	.kivijalka-split > * { margin-block: 0 !important; }
	/* Not sticky: the intro scrolls with its content. A pinned column was tried
	 * here and cut. */
	.kivijalka-split > .kivijalka-intro {
		max-width: none;
	}
}

/* --------------------------------------------------------------- squares --
 * The board enters as *structure*, not as pictures of pieces: a row of items
 * alternates light and dark the way a rank does. This is the whole chess
 * vocabulary for content — glyph icons of pieces read as clip-art and are
 * banned. See DESIGN.md.
 */
.kivijalka-rank > * {
	position: relative;
	border: 1px solid rgba(250, 249, 247, 0.14);
	padding: 2.1rem 1.75rem 2.25rem;
	height: 100%;
	display: flex;
	flex-direction: column;
}
/* Alternating squares. Odd children are the light squares. */
.kivijalka-rank > *:nth-child(odd) {
	background: var(--wp--preset--color--base);
	border-color: var(--wp--preset--color--line);
	color: var(--wp--preset--color--muted);
}
.kivijalka-rank > *:nth-child(odd) :is(h2, h3, h4) { color: var(--wp--preset--color--ink); }
.kivijalka-rank > *:nth-child(odd) .kivijalka-sq__n { color: var(--wp--preset--color--accent); }
.kivijalka-rank > *:nth-child(even) {
	background: rgba(250, 249, 247, 0.035);
	color: var(--wp--preset--color--muted-on-dark);
}
.kivijalka-rank > *:nth-child(even) :is(h2, h3, h4) { color: var(--wp--preset--color--on-dark); }
.kivijalka-rank > *:nth-child(even) .kivijalka-sq__n { color: var(--wp--preset--color--accent-soft); }
.kivijalka-rank > * > * { margin-block-start: 0; }

/* The ordinal, set large like a square's coordinate. */
/* Scoped to the rank so it out-specifies `.kivijalka-rank p` below — the
 * ordinal is a paragraph, so the body-copy rule would otherwise shrink it to
 * 15px and the numbering would disappear. */
.kivijalka-sq__n,
.kivijalka-rank .kivijalka-sq__n {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: clamp(3.5rem, 5.5vw, 5rem);
	line-height: 0.9;
	letter-spacing: -0.03em;
	margin-block-end: 1rem;
}
.kivijalka-rank h3,
.kivijalka-rank h4 { font-size: 1.18rem; margin-block-end: 0.55rem; }
.kivijalka-rank p { font-size: 0.9375rem; line-height: 1.62; }

/* Explicit square colours, for when the meaning is the colour itself rather
 * than the position in the row. These beat the nth-child alternation. */
.kivijalka-rank > .kivijalka-sq--light,
.kivijalka-rank > .kivijalka-sq--light:nth-child(odd),
.kivijalka-rank > .kivijalka-sq--light:nth-child(even) {
	background: var(--wp--preset--color--base);
	border-color: var(--wp--preset--color--line);
	color: var(--wp--preset--color--muted);
}
.kivijalka-rank > .kivijalka-sq--light :is(h2, h3, h4) { color: var(--wp--preset--color--ink); }
.kivijalka-rank > .kivijalka-sq--light .kivijalka-sq__n { color: var(--wp--preset--color--accent); }
.kivijalka-rank > .kivijalka-sq--dark,
.kivijalka-rank > .kivijalka-sq--dark:nth-child(odd),
.kivijalka-rank > .kivijalka-sq--dark:nth-child(even) {
	background: #0C0A09;
	border-color: rgba(250, 249, 247, 0.16);
	color: var(--wp--preset--color--muted-on-dark);
}
.kivijalka-rank > .kivijalka-sq--dark :is(h2, h3, h4) { color: var(--wp--preset--color--on-dark); }
.kivijalka-rank > .kivijalka-sq--dark .kivijalka-sq__n { color: var(--wp--preset--color--accent-soft); }

/* A square carries its own surface, so the eyebrow inside it follows the
 * square — not the band. Without this a light square inside a dark band keeps
 * the band's soft accent and washes out.
 *
 * Order matters: the positional alternation comes first, then the explicit
 * modifiers override it at equal specificity. Written the other way round the
 * nth-child rule wins and paints the wrong accent on an explicit square. */
.kivijalka-band-dark .kivijalka-rank > *:nth-child(odd) .kivijalka-eyebrow,
.kivijalka-band-light .kivijalka-rank > *:nth-child(odd) .kivijalka-eyebrow {
	color: var(--wp--preset--color--accent);
}
.kivijalka-band-dark .kivijalka-rank > *:nth-child(even) .kivijalka-eyebrow,
.kivijalka-band-light .kivijalka-rank > *:nth-child(even) .kivijalka-eyebrow {
	color: var(--wp--preset--color--accent-soft);
}
.kivijalka-band-dark .kivijalka-rank > .kivijalka-sq--light .kivijalka-eyebrow,
.kivijalka-band-light .kivijalka-rank > .kivijalka-sq--light .kivijalka-eyebrow {
	color: var(--wp--preset--color--accent);
}
.kivijalka-band-dark .kivijalka-rank > .kivijalka-sq--dark .kivijalka-eyebrow,
.kivijalka-band-light .kivijalka-rank > .kivijalka-sq--dark .kivijalka-eyebrow {
	color: var(--wp--preset--color--accent-soft);
}

/* A centred component wants a centred heading above it: a left-aligned intro
 * over a centred slider reads as a mistake. Scoped to the compare section so
 * it does not centre editorial text elsewhere. */
.kivijalka-pattern-compare .kivijalka-intro {
	max-width: 60rem;
	margin-inline: auto;
	text-align: center;
}

/* ------------------------------------------------------------ light panel --
 * A light band with nothing but white behind it reads as unfinished. This
 * gives one a warm ground, a board lip along the top edge, and frames the
 * content as a white square sitting on that ground — the same square language
 * the rest of the site uses, rather than a decorative flourish.
 */
.kivijalka-band-light.kivijalka-panelled {
	position: relative;
	background: var(--wp--preset--color--surface) !important;
}

.kivijalka-panelled .kivijalka-panel {
	background: var(--wp--preset--color--white);
	border: 1px solid var(--wp--preset--color--line);
	padding: clamp(1.75rem, 3.2vw, 3rem);
}
.kivijalka-panelled .kivijalka-panel > * { margin-block-start: 0; }


/* ------------------------------------------------------------------ stat --
 * One number carrying a section. Display family, very large, accent-marked.
 */
.kivijalka-stat {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: clamp(5rem, 13vw, 11rem);
	line-height: 0.86;
	letter-spacing: -0.035em;
	color: var(--wp--preset--color--ink);
	margin-block-end: 0.75rem;
}
.kivijalka-band-dark .kivijalka-stat { color: var(--wp--preset--color--on-dark); }
.kivijalka-stat em {
	font-style: normal;
	color: var(--wp--preset--color--accent);
}

/* Two figures read side by side. A pair needs to be smaller than a lone stat —
 * at the solo size they fight each other and overflow a phone. */
.kivijalka-statpair {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: flex-start;
	gap: clamp(2rem, 7vw, 5.5rem);
	margin-block: clamp(2rem, 4vw, 3rem);
}
.kivijalka-statpair > * { text-align: center; }
/* Core's flow layout puts a block-gap margin on every child after the first.
 * These children are flex items now, so that vertical margin would drop the
 * second figure below the first instead of spacing a stack. */
.kivijalka-statpair > *,
.kivijalka-statpair > * > * { margin-block: 0; }
.kivijalka-statpair .kivijalka-stat {
	font-size: clamp(3.5rem, 9vw, 7rem);
	margin-block-end: 0.35rem;
}
/* A rule between the figures, drawn on the gap so it needs no extra element. */
.kivijalka-statpair > * + *::before {
	content: "";
	position: absolute;
	inset-block: 12%;
	left: calc(-1 * clamp(1rem, 3.5vw, 2.75rem));
	width: 1px;
	background: var(--wp--preset--color--line);
}
.kivijalka-statpair > * + * { position: relative; }
.kivijalka-band-dark .kivijalka-statpair > * + *::before {
	background: rgba(250, 249, 247, 0.18);
}
.kivijalka-stat__label {
	font-family: var(--wp--preset--font-family--display);
	font-size: 0.8125rem;
	font-weight: 600;
	letter-spacing: 0.17em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}
.kivijalka-band-dark .kivijalka-stat__label {
	color: var(--wp--preset--color--muted-on-dark);
}
@media (max-width: 460px) {
	/* Stacked on a phone, so the vertical rule between them is meaningless. */
	.kivijalka-statpair > * + *::before { display: none; }
}

/* ------------------------------------------------------------------ icons --
 * A small neutral line-icon set, drawn with CSS masks so every icon inherits
 * `currentColor` and therefore always contrasts its surface. Icons are named
 * by what they depict, never by a customer's service names.
 */
.kivijalka-icon {
	display: inline-block;
	width: 28px;
	height: 28px;
	background-color: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	flex: none;
}
.kivijalka-icon--draft   { -webkit-mask-image: var(--kivijalka-icon-draft);   mask-image: var(--kivijalka-icon-draft); }
.kivijalka-icon--plan    { -webkit-mask-image: var(--kivijalka-icon-plan);    mask-image: var(--kivijalka-icon-plan); }
.kivijalka-icon--build   { -webkit-mask-image: var(--kivijalka-icon-build);   mask-image: var(--kivijalka-icon-build); }
.kivijalka-icon--inspect { -webkit-mask-image: var(--kivijalka-icon-inspect); mask-image: var(--kivijalka-icon-inspect); }
.kivijalka-icon--interior{ -webkit-mask-image: var(--kivijalka-icon-interior);mask-image: var(--kivijalka-icon-interior); }
.kivijalka-icon--talk    { -webkit-mask-image: var(--kivijalka-icon-talk);    mask-image: var(--kivijalka-icon-talk); }
.kivijalka-icon--home    { -webkit-mask-image: var(--kivijalka-icon-home);    mask-image: var(--kivijalka-icon-home); }
.kivijalka-icon--key     { -webkit-mask-image: var(--kivijalka-icon-key);     mask-image: var(--kivijalka-icon-key); }

/* ------------------------------------------------------------- piece mark --
 * An oversized mark watermarked into the corner of a square. Drawn as a mask
 * so it takes `currentColor` and therefore always contrasts the square it sits
 * on — the artwork itself can be a single flat file for every surface. The URL
 * is a token the site layer supplies; the theme ships no artwork of its own.
 *
 * It is decoration: the square's heading carries the meaning, so the mark is
 * aria-hidden and must never be the only thing saying something.
 */
.kivijalka-piece {
	grid-column: 1;
	/* An explicit span, not `1 / -1`: these rows are all *implicit*, and `-1`
	 * resolves against the explicit grid — which here is a single line — so
	 * `1 / -1` silently collapses to one row and the mark stops spanning the
	 * copy. The span is deliberately larger than any square will ever need;
	 * surplus implicit rows are zero-height at this row-gap. */
	grid-row: 1 / span 20;
	/* Stretched, not fixed: the mark stands exactly as tall as the copy beside
	 * it. A fixed height taller than the copy also forced the spanned rows to
	 * grow, which is what opened the gap between the eyebrow and the heading —
	 * the space belonged to the mark, not to the type. */
	align-self: stretch;
	width: clamp(3.25rem, 5.5vw, 4.5rem);
	height: auto;
	flex: none;
	pointer-events: none;
	background-color: currentColor;
	/* The mark stands *beside* the copy rather than under it, so nothing is
	 * composited over it and the old contrast ceiling no longer applies — it can
	 * be an emblem at full strength instead of a faint watermark. Text over the
	 * mark was the whole reason it had to be nearly invisible before. */
	opacity: 0.9;
	/* The fallback is a fully transparent image, not `none`: an unmasked element
	 * would paint as a solid `currentColor` rectangle. With no site layer to
	 * supply artwork the component must render as nothing at all. */
	-webkit-mask-image: var(--kivijalka-piece, url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/%3E'));
	mask-image: var(--kivijalka-piece, url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/%3E'));
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	transition: transform 0.6s ease;
}
/* A square holding a mark becomes two columns: the mark on the left, the whole
 * copy stack to its right. The mark is the first grid column and spans every
 * row, so headings and paragraphs flow beside it and never across it. */
.kivijalka-rank > *:has(.kivijalka-piece) {
	display: grid;
	grid-template-columns: auto 1fr;
	column-gap: clamp(1.1rem, 2.2vw, 1.9rem);
	/* `min-content` rows keep the copy at its natural rhythm. Without it the
	 * row-spanning mark distributes its own extra height between the rows and
	 * the eyebrow, heading and paragraph drift apart. */
	grid-auto-rows: min-content;
	align-items: start;
	align-content: center;
}
.kivijalka-rank > *:has(.kivijalka-piece) > *:not(.kivijalka-piece) {
	grid-column: 2;
}
.kivijalka-rank > *:hover .kivijalka-piece {
	transform: translateY(-0.35rem);
}
/* The mark takes the square's *heading* colour, not its body colour — inheriting
 * the muted body tone left it reading as flat grey on both squares rather than
 * as a piece standing on a square. */
.kivijalka-rank > .kivijalka-sq--dark .kivijalka-piece {
	background-color: var(--wp--preset--color--on-dark);
}
.kivijalka-rank > .kivijalka-sq--light .kivijalka-piece {
	background-color: var(--wp--preset--color--ink);
}
@media (max-width: 600px) {
	/* Stack on a phone: two columns leave the copy too narrow to read.
	 * The selector must carry the same `:not()` as the rule it answers — without
	 * it the content stays pinned to column 2, grid quietly creates the second
	 * column again, and the square never actually stacks. */
	.kivijalka-rank > *:has(.kivijalka-piece) { grid-template-columns: 1fr; }
	.kivijalka-rank > *:has(.kivijalka-piece) > *:not(.kivijalka-piece) { grid-column: 1; }
	.kivijalka-piece {
		/* The mark is last in the markup, so stacking alone would drop it under
		 * the copy. `order` puts it back on top without reordering the source. */
		grid-row: auto;
		order: -1;
		justify-self: start;
		margin-block-end: 1rem;
	}
}

/* The same mark filling the dead space at the foot of a short column — the
 * slack left when the column beside it is taller. It is positioned *out of
 * flow* so it can occupy that slack without touching the spacing of the copy
 * above it. It holds still: a scroll-linked version was built and cut as too
 * busy for what is meant to be a quiet tail piece. */
.kivijalka-piece--block {
	position: absolute;
	bottom: 0;
	/* Centred on the column, under whatever sits above it. Pinning to `left: 50%`
	 * would need a transform to re-centre, and transform is the parallax's
	 * property — the auto margins keep this independent of it. */
	left: 0;
	right: 0;
	margin-inline: auto;
	grid-column: auto;
	grid-row: auto;
	align-self: auto;
	order: 0;
	width: clamp(9rem, 17vw, 15rem);
	height: clamp(12rem, 23vw, 20rem);
	margin-block: 0;
	opacity: 0.17;
	-webkit-mask-position: bottom center;
	mask-position: bottom center;
}
/* Its host supplies the positioning context and clips the travel. Clipping is
 * safe here: the parallax is observed through this element, not through the
 * mark, so a mark that drifts out of view still keeps moving. */
.kivijalka-fill-column {
	position: relative;
	overflow: hidden;
}
/* Once the columns stack there is no slack left to fill — the column is exactly
 * as tall as its own text — so an out-of-flow mark anchored to its foot lands
 * straight on the copy. It exists only for the two-column layout. */
@media (max-width: 781px) {
	.kivijalka-piece--block { display: none; }
}

/* The same mark at band scale, drifting across its section on scroll rather
 * than sitting in a square. Pair it with `data-kivijalka-parallax` and
 * `data-kivijalka-parallax-x` so it travels on a diagonal — for a bishop that
 * is its own move, which is the whole point of showing it moving at all.
 * Atmosphere only: it sits behind the content and never carries meaning. */
/* Anchored at the *centre* of its band, because the travel is symmetrical about
 * zero: at the moment the band is centred in the viewport every mark sits at
 * its base position with no transform. Anchoring at the centre is therefore
 * what makes two marks with opposite factors cross here rather than merely
 * pass somewhere off-stage. The small offsets keep the pair from landing
 * exactly on top of each other as they meet. */
/* Anchored low and right, so its travel up and to the left crosses almost the
 * whole band as the visitor scrolls past — the bishop's own move, made at the
 * length of the section rather than as a twitch in one corner. A single mark:
 * a counter-moving twin was tried here and cut. */
.kivijalka-piece--band {
	position: absolute;
	left: calc(50% + clamp(2rem, 20vw, 24rem));
	top: calc(50% - clamp(3rem, 8vw, 8rem));
	width: clamp(13rem, 26vw, 25rem);
	height: clamp(13rem, 26vw, 25rem);
	/* Centring uses `translate`, not `transform`: the parallax owns `transform`,
	 * and these are separate properties that compose rather than overwrite. */
	translate: -50% -50%;
	opacity: 0.11;
	z-index: 0;
	transition: none;
}
/* A band hosting one needs the positioning context and its content lifted
 * clear — same contract as `.kivijalka-drift`. */
.kivijalka-has-piece { position: relative; overflow: hidden; }
.kivijalka-has-piece > *:not(.kivijalka-piece--band) { position: relative; z-index: 1; }
.kivijalka-rank > *:hover .kivijalka-piece--band { transform: none; }
@media (max-width: 780px) {
	.kivijalka-piece--band { opacity: 0.05; }
}

/* ---------------------------------------------------------- gallery caption --
 * Core overlays gallery captions on the image: white text on a dark gradient.
 * That only works on dark imagery. On light images — architectural drawings,
 * anything on white paper — the caption disappears and covers the artwork's own
 * annotations. So captions sit below the image in the normal text palette.
 */
.wp-block-gallery.has-nested-images figure.wp-block-image figcaption {
	position: static;
	background: none;
	color: var(--wp--preset--color--muted);
	text-shadow: none;
	padding: 0.65rem 0 0;
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	text-align: left;
	max-height: none;
	overflow: visible;
	flex-grow: 0;
}
/* Core makes the figure a flex column and gives the image `flex-basis: 0`, which
 * only works while the caption is absolutely positioned. With the caption back
 * in flow the image collapses to zero height, so the figure becomes a plain
 * block and the image sizes itself. `contain` keeps whole drawings visible —
 * `cover` would crop the very annotations the drawing exists to show. */
.wp-block-gallery.has-nested-images figure.wp-block-image {
	display: block;
}
.wp-block-gallery.has-nested-images figure.wp-block-image img {
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: contain;
	background: var(--wp--preset--color--white);
	flex: 0 0 auto;
}
/* On a dark band the same caption follows the dark palette instead. */
.kivijalka-band-dark .wp-block-gallery.has-nested-images figure.wp-block-image figcaption,
.has-ink-background-color .wp-block-gallery.has-nested-images figure.wp-block-image figcaption {
	color: var(--wp--preset--color--muted-on-dark);
}

/* ------------------------------------------------------------------ steps --
 * A numbered process row: icon medallion, connector line running to the next
 * step, number, heading and copy. Horizontal on desktop, vertical on mobile,
 * with the connector following the axis.
 */
.kivijalka-steps {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: var(--wp--preset--spacing--40);
	align-items: start;
	counter-reset: kivijalka-step;
}
.kivijalka-steps > * { margin-top: 0 !important; margin-block-start: 0 !important; }

/* Each step is its own surface, so the sequence reads as four distinct,
 * countable things rather than loose columns of text. */
.kivijalka-step {
	position: relative;
	display: flex;
	flex-direction: column;
	height: 100%;
	background: rgba(250, 249, 247, 0.035);
	border: 1px solid rgba(250, 249, 247, 0.16);
	border-radius: var(--kivijalka-radius);
	padding: 1.6rem 1.5rem 1.75rem;
}
.kivijalka-step > * { margin-block-start: 0; }

/* The icon sits alone at the top of the box; the step's number belongs to
 * the title, where it reads as an ordinal rather than as decoration. */
.kivijalka-step__head {
	display: flex;
	align-items: center;
	margin-block-end: 1.1rem;
	padding-block-end: 1.1rem;
	border-bottom: 1px solid var(--wp--preset--color--line);
}
.kivijalka-step__head > * { margin-block-start: 0 !important; }

.kivijalka-step__icon {
	position: relative;
	z-index: 2;
	width: 46px;
	height: 46px;
	border-radius: 50%;
	display: grid;
	place-items: center;
	background: var(--wp--preset--color--base);
	border: 1.5px solid var(--wp--preset--color--line);
	color: var(--wp--preset--color--accent);
	flex: none;
}
.kivijalka-step__icon .kivijalka-icon { width: 22px; height: 22px; }

/* The ordinal inside a step heading, e.g. the "1." in "1. First step". */
.kivijalka-band-dark .kivijalka-step__n,
.has-ink-background-color .kivijalka-step__n {
	color: var(--wp--preset--color--accent-soft);
}
.kivijalka-step__n {
	color: var(--wp--preset--color--accent);
	font-variant-numeric: tabular-nums;
	margin-inline-end: 0.15em;
}

/* Connector: runs across the gap from this box to the next. */
.kivijalka-step::before {
	content: "";
	position: absolute;
	z-index: 1;
	top: 47px;
	left: 100%;
	width: var(--wp--preset--spacing--40);
	height: 1.5px;
	background: repeating-linear-gradient(
		to right,
		var(--wp--preset--color--line) 0 6px,
		transparent 6px 11px
	);
}
.kivijalka-step:last-child::before { display: none; }

/* Stack on narrow screens and turn the connector vertical. */
@media (max-width: 720px) {
	.kivijalka-steps { grid-template-columns: 1fr; gap: var(--wp--preset--spacing--40); }
	.kivijalka-step::before {
		top: 100%;
		left: 46px;
		width: 1.5px;
		height: var(--wp--preset--spacing--40);
		background: repeating-linear-gradient(
			to bottom,
			var(--wp--preset--color--line) 0 6px,
			transparent 6px 11px
		);
	}
}

/* On a surface band the boxes need a touch more separation. */
.has-surface-background-color .kivijalka-step { background: var(--wp--preset--color--white); }

/* On a dark band the medallion and connector invert. */
.kivijalka-band-dark .kivijalka-step__icon,
.has-ink-background-color .kivijalka-step__icon {
	background: transparent;
	border-color: rgba(250, 249, 247, 0.3);
	color: var(--wp--preset--color--accent-soft);
}

/* ------------------------------------------------------------------ card */
.kivijalka-card {
	background: var(--wp--preset--color--white);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--kivijalka-radius);
	padding: 2.25rem 2rem;
	display: flex;
	flex-direction: column;
	height: 100%;
}
.kivijalka-card > * { margin-block-start: 0; }
/* Heading and body only — the icon band was removed. The spacing has to be set
 * here rather than inherited, or the two run together without it. */
.kivijalka-card :is(h2, h3, h4) { margin-block-end: 0.75rem; }
.kivijalka-card p { margin-block-end: 0; }
/* A card stays white on a dark band rather than inverting to a translucent
 * tint. It reads as a light square laid on the dark ground — the same move the
 * numbered steps make — and it is what the client chose. theme.json colours
 * headings with `ink`, so only the body colour has to be reclaimed. */
.kivijalka-band-dark .kivijalka-card,
.has-ink-background-color .kivijalka-card {
	background: var(--wp--preset--color--white);
	border-color: var(--wp--preset--color--line);
	color: var(--wp--preset--color--muted);
}
.kivijalka-band-dark .kivijalka-card :is(h1, h2, h3, h4, h5, h6),
.has-ink-background-color .kivijalka-card :is(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--preset--color--ink);
}
/* The preset colour classes an editor applies carry `!important`, so a card on
 * a dark band would keep its on-dark body colour against the white fill. */
.kivijalka-band-dark .kivijalka-card .has-muted-on-dark-color,
.has-ink-background-color .kivijalka-card .has-muted-on-dark-color {
	color: var(--wp--preset--color--muted) !important;
}
.kivijalka-band-dark .kivijalka-card__icon,
.has-ink-background-color .kivijalka-card__icon {
	border-color: var(--wp--preset--color--line);
	color: var(--wp--preset--color--accent);
}

/* A single oversized glyph used as a card's emblem. Sized in its own right
 * rather than inheriting body type. */
/* Pairs a filled glyph with an outlined one: the modifier lifts the second to
 * full contrast so the two emblems stay tellable apart on a dark surface. */


/* The icon sits in its own band at the top of the card, mirroring a step. */
.kivijalka-card__icon {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	color: var(--wp--preset--color--accent);
	margin-block-end: 1.1rem;
	padding-block-end: 1.1rem;
	border-bottom: 1px solid var(--wp--preset--color--line);
}
.kivijalka-card__icon .kivijalka-icon { width: 30px; height: 30px; }

/* --------------------------------------------------------------- buttons --
 * Both background and text are set explicitly on every variant, including
 * hover, and buttons are excluded from generic link colouring
 * (local-gotchas #13).
 */
.wp-block-button__link:focus-visible,
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
	outline: 3px solid var(--wp--preset--color--accent);
	outline-offset: 3px;
}

.wp-block-button.is-style-outline > .wp-block-button__link {
	background-color: transparent;
	border: 1.5px solid var(--wp--preset--color--ink);
	color: var(--wp--preset--color--ink);
}
.wp-block-button.is-style-outline > .wp-block-button__link:hover,
.wp-block-button.is-style-outline > .wp-block-button__link:focus {
	background-color: var(--wp--preset--color--ink);
	border-color: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--base);
}

/* Outline button placed on a dark band. */
.wp-block-button.is-style-outline-on-dark > .wp-block-button__link {
	background-color: transparent;
	border: 1.5px solid rgba(250, 249, 247, 0.5);
	color: var(--wp--preset--color--on-dark);
}
.wp-block-button.is-style-outline-on-dark > .wp-block-button__link:hover,
.wp-block-button.is-style-outline-on-dark > .wp-block-button__link:focus {
	background-color: var(--wp--preset--color--on-dark);
	border-color: var(--wp--preset--color--on-dark);
	color: var(--wp--preset--color--ink);
}

/* Never let a section link rule repaint a button label. */
.kivijalka-card a:not(.wp-block-button__link),
.kivijalka-section a:not(.wp-block-button__link) {
	text-underline-offset: 0.18em;
}

/* ---------------------------------------------------------------- header */
/* The header template part renders its own <header> wrapper, and a sticky
 * element can only stick inside its parent's box. So the wrapper is what
 * sticks; the inner group carries the styling. */
.wp-site-blocks > header {
	position: sticky;
	top: 0;
	z-index: 100;
}
.kivijalka-header {
	/* No backdrop-filter/transform: it would trap fixed overlays (gotcha #3). */
	background-color: rgba(250, 249, 247, 0.94);
	border-bottom: 1px solid var(--wp--preset--color--line);
	transition: box-shadow 0.25s ease;
}
/* The header keeps one height at every scroll position — resizing it as you
 * scroll makes the page feel unstable. Sticking only adds a shadow. */
.kivijalka-header.is-stuck {
	box-shadow: 0 6px 24px rgba(20, 17, 15, 0.09);
}
/* The sentinel sits in normal flow *before* the sticky wrapper, so it scrolls
 * away while the header stays put — that is what tells us the header stuck.
 * The negative margin keeps it out of the layout. */
.kivijalka-header-sentinel {
	height: 1px;
	margin-bottom: -1px;
	pointer-events: none;
	visibility: hidden;
}
.kivijalka-header .wp-block-site-logo img {
	height: 58px;
	width: auto;
	/* A fixed height inside a flex row that can shrink the logo's width will
	 * squash it sideways; `contain` keeps the proportions whatever the row does. */
	object-fit: contain;
	object-position: left center;
}
/* A wide two-line logo has to step down on the narrowest phones, or it and the
 * menu button fight for the row. */
@media (max-width: 480px) {
	.kivijalka-header .wp-block-site-logo img { height: 46px; }
}
.kivijalka-header .wp-block-navigation {
	gap: 2.25rem;
}

/* The header call to action is an ordinary primary button, identical to the
 * hero's. A chess icon and a full checkerboard fill were both tried here and
 * cut — the header is not where the motif belongs. */
.kivijalka-header .kivijalka-cta .wp-block-button__link {
	display: inline-flex;
	align-items: center;
	font-size: 0.9375rem;
	letter-spacing: 0.01em;
	padding: 0.95rem 1.9rem;
}
.kivijalka-header .wp-block-navigation .wp-block-navigation-item__content {
	font-size: 0.9375rem;
	font-weight: 500;
	letter-spacing: 0.02em;
	color: var(--wp--preset--color--ink);
	text-decoration: none;
	position: relative;
	padding-block: 0.35rem;
}
.kivijalka-header .wp-block-navigation .wp-block-navigation-item__content::after {
	content: "";
	position: absolute;
	left: 0;
	right: 100%;
	bottom: 0;
	height: 2px;
	background: var(--wp--preset--color--accent);
	transition: right 0.28s ease;
}
.kivijalka-header .wp-block-navigation .wp-block-navigation-item__content:hover::after,
.kivijalka-header .wp-block-navigation .current-menu-item > .wp-block-navigation-item__content::after {
	right: 0;
}

/* Styled desktop submenu — override the inherited overlay preset colours
 * with !important, and bridge the offset so hover never drops (gotchas #14, #8).
 */
@media (min-width: 782px) {
	.kivijalka-header .wp-block-navigation__submenu-container {
		background-color: var(--wp--preset--color--white) !important;
		color: var(--wp--preset--color--ink) !important;
		border: 1px solid var(--wp--preset--color--line) !important;
		border-radius: var(--kivijalka-radius);
		box-shadow: 0 14px 38px rgba(20, 17, 15, 0.16);
		padding: 0.4rem !important;
		min-width: 264px;
		gap: 1px !important;
		margin-top: 6px;
	}
	.kivijalka-header .wp-block-navigation__submenu-container::before {
		content: "";
		position: absolute;
		left: 0;
		right: 0;
		top: -8px;
		height: 8px;
	}
	.kivijalka-header .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
		color: var(--wp--preset--color--ink) !important;
		padding: 0.6rem 0.85rem !important;
		border-radius: var(--kivijalka-radius);
		white-space: nowrap;
		font-weight: 500;
	}
	.kivijalka-header .wp-block-navigation__submenu-container .wp-block-navigation-item__content::after {
		display: none;
	}
	.kivijalka-header .wp-block-navigation__submenu-container .wp-block-navigation-item__content:hover {
		background-color: var(--wp--preset--color--surface);
	}
}

/* --------------------------------------------------- mobile nav drawer --
 * A designed custom overlay rather than the raw core one (gotcha #16).
 * The breakpoint is raised well above core's 600px so the inline nav is
 * swapped for the hamburger *before* it can wrap to two rows.
 */
.kivijalka-navtoggle,
.kivijalka-drawer { display: none; }

.kivijalka-navtoggle {
	appearance: none;
	background: transparent;
	border: 0;
	padding: 0.5rem;
	cursor: pointer;
	color: var(--wp--preset--color--ink);
	line-height: 0;
}

@media (max-width: 900px) {
	.kivijalka-header .wp-block-navigation { display: none; }
	.kivijalka-navtoggle { display: inline-flex; }
	/* The drawer is nested inside the header, so the rule above would hide its
	 * navigation too — the drawer's own list must stay visible. */
	.kivijalka-header .kivijalka-drawer .wp-block-navigation { display: flex; }
}

.kivijalka-drawer {
	position: fixed;
	inset: 0;
	/* The drawer is a child of the header group, so WordPress's flow layout
	 * would otherwise give it a block-gap margin and push the fixed overlay
	 * down off the viewport corner. */
	margin: 0 !important;
	z-index: 200;
	background: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--on-dark);
	flex-direction: column;
	padding: clamp(1.25rem, 4vw, 2.5rem);
	overflow-y: auto;
}
.kivijalka-drawer.is-open { display: flex; }
body.kivijalka-nav-open { overflow: hidden; }

.kivijalka-drawer__top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	min-height: 44px;
	margin-block-end: 2.5rem;
}
.kivijalka-drawer__top img { height: 40px; width: auto; }
.kivijalka-drawer__close {
	appearance: none;
	background: transparent;
	border: 1px solid rgba(250, 249, 247, 0.3);
	border-radius: var(--kivijalka-radius);
	color: var(--wp--preset--color--on-dark);
	width: 44px;
	height: 44px;
	display: grid;
	place-items: center;
	cursor: pointer;
	font-size: 1.25rem;
	line-height: 1;
}
.kivijalka-drawer__close:hover { background: rgba(250, 249, 247, 0.1); }

.kivijalka-drawer nav ul {
	list-style: none;
	margin: 0;
	padding: 0;
}
.kivijalka-drawer nav li { border-bottom: 1px solid rgba(250, 249, 247, 0.14); }
.kivijalka-drawer nav a {
	display: block;
	padding: 1.05rem 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.5rem;
	font-weight: 600;
	color: var(--wp--preset--color--on-dark);
	text-decoration: none;
}
.kivijalka-drawer nav a:hover { color: var(--wp--preset--color--accent-soft); }

/* The drawer lives inside the header element, so the header's dark-on-light
 * navigation colours would otherwise apply here and render the links
 * ink-on-ink. Re-assert the on-dark treatment and drop the underline sweep. */
.kivijalka-header .kivijalka-drawer .wp-block-navigation-item__content {
	color: var(--wp--preset--color--on-dark);
	font-size: 1.5rem;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	padding-block: 1.05rem;
}
.kivijalka-header .kivijalka-drawer .wp-block-navigation-item__content:hover {
	color: var(--wp--preset--color--accent-soft);
}
.kivijalka-header .kivijalka-drawer .wp-block-navigation-item__content::after {
	display: none;
}
.kivijalka-drawer .wp-block-navigation,
.kivijalka-drawer .wp-block-navigation__container {
	gap: 0 !important;
}
.kivijalka-drawer .wp-block-navigation__container {
	display: block;
	width: 100%;
}
.kivijalka-drawer .wp-block-navigation-item {
	display: block;
	width: 100%;
	border-bottom: 1px solid rgba(250, 249, 247, 0.14);
}
.kivijalka-drawer__foot {
	margin-block-start: auto;
	padding-block-start: 2.5rem;
	display: flex;
	flex-direction: column;
	gap: 1rem;
}
.kivijalka-drawer__foot a { color: var(--wp--preset--color--on-dark); }

.kivijalka-header .kivijalka-cta-label { display: inline; }
@media (max-width: 600px) {
	/* No CTA in the phone header. It used to shrink to a round mail icon beside
	 * the logo, which read as a stray button rather than an action — the call
	 * and mail actions live in the fixed action bar at the bottom instead,
	 * where a thumb actually reaches them. */
	.kivijalka-header .kivijalka-cta { display: none; }
}

/* ------------------------------------------------------- mobile actions --
 * A fixed bar on phones carrying the two things a visitor on a phone actually
 * wants: call, and mail. Icons only — the targets are 48px, above the 44px
 * minimum, and each carries its own accessible name since there is no label.
 */
.kivijalka-actionbar { display: none; }
@media (max-width: 600px) {
	.kivijalka-actionbar {
		position: fixed;
		z-index: 90;
		left: 0;
		right: 0;
		bottom: 0;
		display: flex;
		/* Call sits on the right, under the thumb. */
		flex-direction: row-reverse;
		justify-content: flex-start;
		gap: 0.6rem;
		padding: 0.6rem 0.9rem calc(0.6rem + env(safe-area-inset-bottom));
		/* No bar behind the buttons — they float over the page. Each one
		 * therefore has to carry its own opaque fill: the bar is no longer
		 * there to guarantee what is behind them, and they pass over both
		 * light and dark sections as the page scrolls. */
		background: none;
		pointer-events: none;
	}
	.kivijalka-actionbar a {
		pointer-events: auto;
		width: 52px;
		height: 52px;
		display: grid;
		place-items: center;
		border-radius: 50%;
		color: var(--wp--preset--color--on-dark);
		background: var(--wp--preset--color--ink);
		border: 1px solid rgba(250, 249, 247, 0.22);
		box-shadow: 0 6px 18px rgba(10, 8, 8, 0.35);
		text-decoration: none;
	}
	.kivijalka-actionbar a.is-primary {
		background: var(--wp--preset--color--accent);
		border-color: var(--wp--preset--color--accent);
	}
	.kivijalka-actionbar a::before {
		content: "";
		width: 22px;
		height: 22px;
		background-color: currentColor;
		-webkit-mask-position: center;
		mask-position: center;
		-webkit-mask-repeat: no-repeat;
		mask-repeat: no-repeat;
		-webkit-mask-size: contain;
		mask-size: contain;
	}
	.kivijalka-actionbar a.is-call::before {
		-webkit-mask-image: var(--kivijalka-icon-phone);
		mask-image: var(--kivijalka-icon-phone);
	}
	.kivijalka-actionbar a.is-mail::before {
		-webkit-mask-image: var(--kivijalka-icon-mail);
		mask-image: var(--kivijalka-icon-mail);
	}
	/* The bar floats over the page, so the last section needs room to clear it
	 * or the footer's final line sits underneath. */
	body { padding-bottom: 4.5rem; }
}

/* ------------------------------------------------------------------ hero --
 * A full-bleed photographic band with the copy set on it directly — no panel.
 * Three layers, bottom to top:
 *   1. .kivijalka-hero__media  — the photographs, crossfaded, each panning
 *   2. ::after on that layer   — the scrim, which IS the contrast mechanism
 *   3. .kivijalka-hero__inner  — the copy
 */
.kivijalka-hero {
	position: relative;
	background: var(--wp--preset--color--ink);
	overflow: hidden;
}

/* --- layer 1: the photographs ------------------------------------------- */
.kivijalka-hero .kivijalka-hero__media {
	position: absolute;
	inset: 0;
	margin: 0;
}
/* The slides are ordinary image blocks, so the <figure> wrapper is what has to
 * be stacked and stretched — not just the <img> inside it. Oversized because
 * the layer parallaxes: the extra height keeps it covering the hero at both
 * ends of its travel. */
.kivijalka-hero .kivijalka-hero__media > figure,
.kivijalka-hero .kivijalka-hero__media .wp-block-image {
	position: absolute;
	left: 0;
	right: 0;
	top: -9%;
	margin: 0;
	width: 100%;
	height: 118%;
	opacity: 0;
	transition: opacity 1.4s ease;
}
.kivijalka-hero__media .wp-block-image.is-active { opacity: 1; }
/* Without JS the first slide simply shows; once JS owns visibility it sets the
 * flag and takes the fallback away. */
.kivijalka-hero__media .wp-block-image:first-child { opacity: 1; }
.kivijalka-has-slides .kivijalka-hero__media .wp-block-image:first-child { opacity: 0; }
.kivijalka-has-slides .kivijalka-hero__media .wp-block-image.is-active { opacity: 1; }
.kivijalka-hero .kivijalka-hero__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	margin: 0;
	border-radius: 0;
}
/* Only the active slide pans, so an off-screen slide costs nothing and every
 * slide starts its move from the same place. The animation must sit on the
 * <img>: the <figure> is the parallax target, and a CSS animation always beats
 * an inline transform, so sharing one element silently kills the parallax. */
.kivijalka-hero__media .wp-block-image img { animation: none; }
.kivijalka-hero__media .wp-block-image.is-active img {
	animation: kivijalka-kenburns 11s ease-out forwards;
	transform-origin: 70% 58%;
	will-change: transform;
}

/* --- layer 2: the scrim -------------------------------------------------- */
/* This is the contrast mechanism, not decoration. It is weighted to the side
 * the copy occupies rather than to the floor of the frame, because the copy is
 * centred vertically — a bottom-weighted fade leaves centred text unprotected
 * and measured 3.56:1 over a sunset sky. Releasing the right-hand side also
 * stops the photograph competing with the type. Re-measure against composited
 * pixels, for every slide and both ends of the pan, after any change to hero
 * alignment, height or type scale. */
.kivijalka-hero .kivijalka-hero__media::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	background:
		linear-gradient(
			to right,
			rgba(10, 8, 8, 0.93) 0%,
			rgba(10, 8, 8, 0.90) 42%,
			rgba(10, 8, 8, 0.72) 64%,
			rgba(10, 8, 8, 0.42) 82%,
			rgba(10, 8, 8, 0.30) 100%
		),
		linear-gradient(
			to top,
			rgba(10, 8, 8, 0.55) 0%,
			rgba(10, 8, 8, 0.20) 45%,
			rgba(10, 8, 8, 0.32) 100%
		);
}
@media (max-width: 780px) {
	/* On a phone the copy spans the full width, so a sideways scrim would leave
	 * its right-hand end unprotected. Flat and heavy instead. */
	.kivijalka-hero .kivijalka-hero__media::after {
		background: linear-gradient(
			to top,
			rgba(10, 8, 8, 0.95) 0%,
			rgba(10, 8, 8, 0.90) 50%,
			rgba(10, 8, 8, 0.82) 78%,
			rgba(10, 8, 8, 0.66) 100%
		);
	}
}

/* --- layer 3: the copy --------------------------------------------------- */
/* An inner page's hero is shorter: it introduces a page rather than opening the
 * site, and a full-height band would push the actual content below the fold. */
.kivijalka-hero--short .kivijalka-hero__inner {
	min-height: clamp(22rem, 52vh, 34rem);
}
/* It also holds still. The pan belongs to the front page, where the hero is the
 * whole first screen; on an inner page it competes with the content underneath.
 * This has to be switched off *here* rather than by dropping the Ken Burns
 * class in content — the animation hangs off `.is-active`, which every visible
 * slide carries, so removing the class alone changes nothing. */
.kivijalka-hero--short .kivijalka-hero__media .wp-block-image.is-active img {
	animation: none;
	transform: none;
}
.kivijalka-hero__inner {
	position: relative;
	z-index: 2;
	min-height: clamp(38rem, 94vh, 62rem);
	/* Grid, not flex. A flex child shrink-wraps to its widest item and core's
	 * `margin-inline: auto` then centres that shrunken box, which strands the
	 * copy as a narrow column floating mid-frame. */
	display: grid;
	align-content: center;
	padding-block: clamp(4rem, 9vh, 7rem);
}
/* The copy sits on exactly the same axis as every section below it, at every
 * width — capped at the wide size and centred, like any `.alignwide`.
 *
 * `width: 100%` is load-bearing: an `auto` inline margin makes a grid item size
 * to its content rather than stretch (the same trap as flex), so core's
 * centring rule alone shrink-wraps the copy. Pinning to the padding edge
 * instead looks right only while the viewport is narrower than the wide size;
 * past that the sections start centring and the hero drifts left of them. */
.kivijalka-hero__copy {
	width: 100%;
	max-width: var(--wp--style--global--wide-size);
	margin-inline: auto !important;
	background: none;
	padding: 0;
}
/* The copy column is the same measure as the "Näin ajattelen" band's intro, so
 * the two read as one column running down the page. The container itself stays
 * at the wide size — that is what holds the shared axis — so the cap lands on
 * its children. The verse is exempt: its width comes from its own line breaks,
 * so a cap can only clip it. */
/* The copy column is 50rem (800px) and the type is scaled so the copy actually
 * *fills* it.
 *
 * This is the whole reason the hero kept looking half-empty in a wide column:
 * the verse has hard line breaks, so its width is not set by the container at
 * all — it is font-size x characters, and nothing else. At body size its
 * longest line measures ~400px, so in an 800px column half the row is empty no
 * matter what the container does. The band lower down fills its column because
 * that paragraph *wraps*; this one cannot. So the size is derived from the
 * target width: longest line is 40 characters, which at this face needs roughly
 * 0.0494em per character — ~2.5rem to reach 800px. Change the copy or the
 * column and this has to be re-derived, not nudged. */
.kivijalka-hero__copy > * { max-width: 60rem; }
/* The verse is exempt from every cap, including the `58ch` reading measure that
 * protects ordinary paragraphs. Its line breaks are the author's, so a cap can
 * only re-wrap them — which is exactly what silently re-broke it into four
 * visual lines when this rule went missing. */
.kivijalka-hero__copy .kivijalka-verse { max-width: none; }

.kivijalka-hero__inner h1 {
	font-size: clamp(2.4rem, 5vw, 4.5rem);
	line-height: 1.02;
	letter-spacing: -0.03em;
	max-width: 18ch;
}
.kivijalka-hero__inner p { max-width: 58ch; }
/* The verse carries the positioning statement, so it is set above body size.
 * Below 720px the metre-preserving rule in the motion section takes over —
 * there the line breaks matter more than the scale. */
@media (min-width: 721px) {
	.kivijalka-hero__copy .kivijalka-verse {
		font-size: clamp(1rem, 1.6vw, 1.45rem) !important;
		line-height: 1.5;
	}
}
.kivijalka-hero__copy .wp-block-buttons { margin-top: clamp(2.25rem, 4vh, 3.25rem); }
/* The hero eyebrow is a plain line of type — a rule and a separator were both
 * tried here and cut. */
/* Staggered entrance for the hero copy. Above the fold, so it runs on load
 * rather than waiting for an intersection. Gated on the reveal flag so it is
 * skipped entirely without JS or under reduced motion. */
.kivijalka-has-reveal .kivijalka-hero__inner .wp-block-group > * {
	opacity: 0;
	animation: kivijalka-hero-in 0.9s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
.kivijalka-has-reveal .kivijalka-hero__inner .wp-block-group > *:nth-child(1) { animation-delay: 0.15s; }
.kivijalka-has-reveal .kivijalka-hero__inner .wp-block-group > *:nth-child(2) { animation-delay: 0.28s; }
.kivijalka-has-reveal .kivijalka-hero__inner .wp-block-group > *:nth-child(3) { animation-delay: 0.41s; }
.kivijalka-has-reveal .kivijalka-hero__inner .wp-block-group > *:nth-child(4) { animation-delay: 0.54s; }
@keyframes kivijalka-hero-in {
	from { opacity: 0; transform: translateY(22px); }
	to   { opacity: 1; transform: none; }
}

/* Scroll progress: a hairline across the bottom of the sticky header. */
.kivijalka-progress {
	position: absolute;
	left: 0;
	bottom: -1px;
	height: 2px;
	width: 100%;
	transform: scaleX(0);
	transform-origin: 0 50%;
	background: var(--wp--preset--color--accent);
	pointer-events: none;
	z-index: 2;
}

/* -------------------------------------------------- before/after compare */
.kivijalka-compare {
	position: relative;
	max-width: 60rem;
	/* max-width alone leaves the slider hard against the left edge of a wider
	 * container, stranding empty space on the right. */
	margin-inline: auto;
	overflow: hidden;
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--kivijalka-radius);
	aspect-ratio: 4 / 3;
	background: #000;
	user-select: none;
	touch-action: pan-y;
}
/* The two layers are ordinary image blocks, so the figure wrappers are the
 * elements that must be stacked. */
.kivijalka-compare > figure,
.kivijalka-compare > .wp-block-image {
	position: absolute;
	inset: 0;
	margin: 0;
	width: 100%;
	height: 100%;
}
.kivijalka-compare img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	margin: 0;
	border-radius: 0;
}
.kivijalka-compare .kivijalka-compare__after {
	clip-path: inset(0 0 0 var(--kivijalka-split, 50%));
}
.kivijalka-compare__handle {
	position: absolute;
	top: 0;
	bottom: 0;
	left: var(--kivijalka-split, 50%);
	width: 2px;
	background: #fff;
	box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
	pointer-events: none;
}
.kivijalka-compare__knob {
	position: absolute;
	top: 50%;
	left: var(--kivijalka-split, 50%);
	transform: translate(-50%, -50%);
	width: 46px;
	height: 46px;
	border-radius: 50%;
	background: #fff;
	color: var(--wp--preset--color--ink);
	display: grid;
	place-items: center;
	box-shadow: 0 4px 18px rgba(0, 0, 0, 0.3);
	pointer-events: none;
	font-size: 0.9rem;
}
.kivijalka-compare input[type="range"] {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	opacity: 0;
	margin: 0;
	cursor: ew-resize;
}
.kivijalka-compare__tag {
	position: absolute;
	top: 1rem;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: 0.75rem;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: #fff;
	background: rgba(20, 17, 15, 0.65);
	padding: 0.35rem 0.75rem;
	border-radius: var(--kivijalka-radius);
	pointer-events: none;
}
.kivijalka-compare__tag--before { left: 1rem; }
.kivijalka-compare__tag--after { right: 1rem; }

/* ----------------------------------------------------------- image hover --
 * A slow push-in on hover for gallery and editorial imagery. The wrapper clips,
 * so only the picture moves and the frame stays put.
 */
.wp-block-gallery .wp-block-image,
.kivijalka-pattern-media-text .wp-block-image {
	overflow: hidden;
	border-radius: var(--kivijalka-radius);
}
.wp-block-gallery .wp-block-image img,
.kivijalka-pattern-media-text .wp-block-image img {
	transition: transform 0.7s cubic-bezier(0.22, 0.61, 0.36, 1);
	will-change: transform;
}

/* ------------------------------------------------------------------ form --
 * Generic styling for plugin-rendered form markup. No form IDs here.
 */
.kivijalka-form {
	background: var(--wp--preset--color--white);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--kivijalka-radius);
	padding: clamp(1.75rem, 3.5vw, 2.75rem);
}
.kivijalka-form .ff-el-group { margin-bottom: 1.25rem; }
.kivijalka-form .ff-el-input--label label {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.875rem;
	font-weight: 600;
	letter-spacing: 0.01em;
	margin-bottom: 0.45rem;
	color: var(--wp--preset--color--ink);
}
.kivijalka-form input[type="text"],
.kivijalka-form input[type="email"],
.kivijalka-form input[type="tel"],
.kivijalka-form select,
.kivijalka-form textarea {
	width: 100%;
	font-family: var(--wp--preset--font-family--body);
	font-size: 1rem;
	padding: 0.85rem 1rem;
	color: var(--wp--preset--color--ink);
	background: var(--wp--preset--color--base);
	border: 1.5px solid var(--wp--preset--color--line);
	border-radius: var(--kivijalka-radius);
	box-shadow: none;
	transition: border-color 0.18s, box-shadow 0.18s;
}
.kivijalka-form input:focus,
.kivijalka-form select:focus,
.kivijalka-form textarea:focus {
	outline: none;
	border-color: var(--wp--preset--color--accent);
	box-shadow: 0 0 0 3px rgba(179, 39, 30, 0.15);
}
.kivijalka-form textarea { min-height: 8rem; resize: vertical; }
.kivijalka-form .ff-btn-submit {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.875rem;
	font-weight: 600;
	letter-spacing: 0.02em;
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--white);
	border: 1.5px solid var(--wp--preset--color--accent);
	border-radius: var(--kivijalka-radius);
	padding: 0.95rem 1.8rem;
	cursor: pointer;
	transition: background 0.2s, border-color 0.2s;
}
.kivijalka-form .ff-btn-submit:hover {
	background: var(--wp--preset--color--accent-dark);
	border-color: var(--wp--preset--color--accent-dark);
	color: var(--wp--preset--color--white);
}
/* reCAPTCHA v3 is invisible by design: it scores the visitor in the background
 * and has nothing to show in the form. Only its error message must survive —
 * hiding the field outright would swallow the one thing it needs to say. The
 * badge itself is Google's, floated bottom-right, and their terms require it to
 * stay visible unless the notice is reproduced in the form. */
.kivijalka-form .kivijalka-recaptcha-hidden .ff-el-input--label,
.kivijalka-form .kivijalka-recaptcha-hidden .g-recaptcha,
.kivijalka-form .kivijalka-recaptcha-hidden > label { display: none; }
.kivijalka-form .kivijalka-recaptcha-hidden { margin-bottom: 0; }
.grecaptcha-badge {
	visibility: visible !important;
	right: 14px !important;
	bottom: 14px !important;
	z-index: 80;
}
@media (max-width: 900px) {
	/* Clear of the floating call and mail buttons. */
	.grecaptcha-badge { bottom: 84px !important; }
}

.kivijalka-form .ff-el-is-error .text-danger,
.kivijalka-form .error-text {
	color: var(--wp--preset--color--accent-dark);
	font-size: 0.875rem;
}

/* Contact card sitting beside the form, equal height. */
.kivijalka-contact-card {
	background: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--on-dark);
	border-radius: var(--kivijalka-radius);
	padding: clamp(1.75rem, 3.5vw, 2.75rem);
	/* The card ends after its last line rather than stretching to match the
	 * form beside it. The slack below it belongs to the column, which is where
	 * the drifting mark lives. */
	height: auto;
}
/* The card carries its own dark surface inside an otherwise light band, so it
 * must out-specify the band's colour rules — `:where()` has zero specificity
 * and loses to them. The preset colour classes WordPress emits carry
 * !important, so the overrides that answer them need it too. */
.kivijalka-band-light .kivijalka-contact-card,
.kivijalka-band-light .kivijalka-contact-card :is(h1, h2, h3, h4, h5, h6),
.kivijalka-band-light .kivijalka-contact-card :is(p, li, span, strong),
.kivijalka-band-light .kivijalka-contact-card a:not(.wp-block-button__link),
.kivijalka-contact-card,
.kivijalka-contact-card :is(h1, h2, h3, h4, h5, h6),
.kivijalka-contact-card a:not(.wp-block-button__link) {
	color: var(--wp--preset--color--on-dark);
}
.kivijalka-band-light .kivijalka-contact-card .has-muted-color,
.kivijalka-band-light .kivijalka-contact-card .has-ink-color,
.kivijalka-contact-card .has-muted-color,
.kivijalka-contact-card .has-ink-color {
	color: var(--wp--preset--color--muted-on-dark) !important;
}
.kivijalka-band-light .kivijalka-contact-card .kivijalka-eyebrow,
.kivijalka-contact-card .kivijalka-eyebrow {
	color: var(--wp--preset--color--accent-soft) !important;
}
.kivijalka-contact-card a:hover { color: var(--wp--preset--color--accent-soft); }

/* ---------------------------------------------------------------- footer */
.kivijalka-footer .wp-block-site-logo img { height: 40px; width: auto; }

/* One logo asset serves both surfaces: the mark and wordmark are neutral
 * greys on transparency, so inverting yields the light-on-dark variant. */
.kivijalka-footer .wp-block-site-logo img,
.kivijalka-drawer .wp-block-site-logo img {
	filter: invert(1);
}
/* On dark surfaces the logo is a separate light-on-dark artwork placed as an
 * image, not the site logo put through `invert()`. A logo with a colour in it —
 * here the wood tone of the log-corner mark — cannot be inverted: the colour
 * turns into its complement. */
.kivijalka-footer-logo,
.kivijalka-drawer-logo { margin: 0; line-height: 0; }
.kivijalka-footer-logo img,
.kivijalka-drawer-logo img { display: block; height: auto; max-width: 100%; border-radius: 0; }

/* Phone and mail lines carry their own icon. The lines live in one paragraph
 * separated by <br>, so the icon hangs off each link rather than the line —
 * `inline-flex` on the link is what lets it sit on the text baseline without
 * breaking the wrap. */
.kivijalka-contact-lines a {
	display: inline-flex;
	align-items: center;
	gap: 0.55em;
}
.kivijalka-contact-lines a::before {
	content: "";
	width: 1em;
	height: 1em;
	flex: none;
	background-color: currentColor;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	opacity: 0.8;
}
.kivijalka-contact-lines a.is-tel::before {
	-webkit-mask-image: var(--kivijalka-icon-phone);
	mask-image: var(--kivijalka-icon-phone);
}
.kivijalka-contact-lines a.is-mail::before {
	-webkit-mask-image: var(--kivijalka-icon-mail);
	mask-image: var(--kivijalka-icon-mail);
}

/* A third-party credit: quieter than the site's own footer links, and the mark
 * sits on the text baseline beside its label rather than under it. The <img>
 * carries width/height so it reserves its box before the SVG loads. */
.kivijalka-credit { gap: 0.55rem; opacity: 0.72; transition: opacity 0.2s ease; }
.kivijalka-credit:hover { opacity: 1; }
.kivijalka-credit > * { margin-block: 0; }
.kivijalka-credit .wp-block-image,
.kivijalka-credit figure { margin: 0; line-height: 0; }
/* The image block writes its size as an inline style, which beats a stylesheet
 * rule — so the mark's size is set in the markup and this only normalises it. */
.kivijalka-credit img {
	display: block;
	border-radius: 0;
}

.kivijalka-footer a { color: var(--wp--preset--color--on-dark); text-decoration: none; }
.kivijalka-footer a:hover { color: var(--wp--preset--color--accent-soft); }
.kivijalka-social a {
	display: inline-grid;
	place-items: center;
	width: 40px;
	height: 40px;
	border: 1px solid rgba(250, 249, 247, 0.3);
	border-radius: var(--kivijalka-radius);
	color: var(--wp--preset--color--on-dark);
}
.kivijalka-social a:hover {
	background: rgba(250, 249, 247, 0.1);
	color: var(--wp--preset--color--accent-soft);
}

/* ----------------------------------------------------------------- motion */
/* Reveal is opt-in: the hiding rule only applies once JS has confirmed it can
 * un-hide again. Without JS (or if the script fails) the content is simply
 * visible, never stranded at opacity 0. */
.kivijalka-has-reveal .kivijalka-reveal {
	opacity: 0;
	transform: translateY(10px);
	transition: opacity 0.7s ease, transform 0.7s ease;
	will-change: opacity, transform;
}
.kivijalka-has-reveal .kivijalka-reveal.is-visible { opacity: 1; transform: none; }

/* A continuous zoom-and-pan. The travel has to be big enough and the cycle
 * short enough to actually read as movement — a very slow, very small drift is
 * indistinguishable from a still image. */
/* The class sits on the image block's <figure>, but the animation must run on
 * the <img> itself — otherwise it competes with the parallax transform on the
 * same element, and a CSS animation always beats an inline transform. */
.kivijalka-kenburns img,
img.kivijalka-kenburns {
	animation: kivijalka-kenburns 11s ease-in-out infinite alternate;
	transform-origin: 70% 58%;
	will-change: transform;
}
/* The pan sits right and low in the frame. The *minimum* scale is what licenses
 * the travel: at scale S the image overhangs by (S-1)/2 each side, so a 3%
 * translate needs at least scale 1.06 or a bare edge slides into view. Raise
 * the translate and the floor scale has to rise with it. */
@keyframes kivijalka-kenburns {
	from { transform: scale(1.12) translate3d(3.5%, 3%, 0); }
	to   { transform: scale(1.3) translate3d(-1%, 0.5%, 0); }
}

/* An oversized decorative glyph that drifts behind a band's content. It is
 * purely atmospheric: it must be aria-hidden and never carry meaning. */
.kivijalka-drift {
	position: absolute;
	z-index: 0;
	line-height: 0.8;
	pointer-events: none;
	user-select: none;
	color: currentColor;
	opacity: 0.07;
}
.kivijalka-drift--right { right: -2%; top: 4%; }
.kivijalka-drift--left  { left: -3%;  bottom: 0; }
/* Any band hosting a drift needs a positioning context and content above it. */
.kivijalka-has-drift { position: relative; overflow: hidden; }
.kivijalka-has-drift > *:not(.kivijalka-drift) { position: relative; z-index: 1; }
@media (max-width: 780px) {
	.kivijalka-drift { opacity: 0.05; }
	.kivijalka-drift--right { right: -14%; }
}

.kivijalka-sequence { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.kivijalka-sequence > * { margin-block-start: 0 !important; }

/* The stagger must start when the row is scrolled to, not at page load —
 * otherwise a row below the fold has finished animating before it is seen.
 * Same gate as the reveal: without JS the tiles are simply visible. */
.kivijalka-has-reveal .kivijalka-sequence > * { opacity: 0; animation: none; }
.kivijalka-has-reveal .kivijalka-sequence.is-visible > * {
	animation: kivijalka-rise 0.7s ease forwards;
}

/* A small square motif tile, used inside a sequence row on a dark band. */
.kivijalka-motif {
	width: 34px;
	height: 34px;
	display: grid;
	place-items: center;
	border: 1px solid rgba(250, 249, 247, 0.25);
	color: var(--wp--preset--color--accent-soft);
	font-size: 1.25rem;
	line-height: 1;
}
.kivijalka-sequence > * {
	opacity: 0;
	animation: kivijalka-rise 0.7s ease forwards;
}
.kivijalka-sequence > *:nth-child(1) { animation-delay: 0.05s; }
.kivijalka-sequence > *:nth-child(2) { animation-delay: 0.18s; }
.kivijalka-sequence > *:nth-child(3) { animation-delay: 0.31s; }
.kivijalka-sequence > *:nth-child(4) { animation-delay: 0.44s; }
.kivijalka-sequence > *:nth-child(5) { animation-delay: 0.57s; }
.kivijalka-sequence > *:nth-child(6) { animation-delay: 0.70s; }
@keyframes kivijalka-rise {
	from { opacity: 0; transform: translateY(10px); }
	to   { opacity: 1; transform: none; }
}

/* A metered block of copy whose lines land one after another. The editor still
 * types one paragraph with ordinary line breaks; JS wraps each line so it can
 * carry its own delay. Without JS the <br>s alone still set the lines. */
/* A verse is metered: the line breaks are the author's, and a line that re-wraps
 * in a narrow column destroys the shape. So the type sizes itself down to the
 * longest line rather than letting the column decide where the breaks fall. */
.kivijalka-verse__line { display: block; }
/* Core sets preset sizes as `font-size: ... !important` on `.has-*-font-size`,
 * so overriding one takes `!important` rather than specificity. */
@media (max-width: 720px) {
	.kivijalka-verse {
		font-size: min( 1.0625rem, 4.1vw ) !important;
		line-height: 1.5;
	}
}
.kivijalka-has-reveal .kivijalka-verse__line {
	opacity: 0;
	animation: kivijalka-line-in 0.85s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
	animation-delay: calc(0.4s + var(--kivijalka-line, 0) * 0.13s);
}
@keyframes kivijalka-line-in {
	from { opacity: 0; transform: translateY(0.6em); }
	to   { opacity: 1; transform: none; }
}
/* The lines carry the entrance themselves, so the paragraph must not also fade
 * in around them — two nested opacity animations read as a double exposure. */
.kivijalka-has-reveal .kivijalka-hero__inner .wp-block-group > .kivijalka-verse {
	opacity: 1;
	animation: none;
}

/* Stagger a revealed row's children instead of the row as one block. The row
 * itself stops animating so the two never compound. */
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger {
	opacity: 1;
	transform: none;
	transition: none;
}
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger > * { opacity: 0; }
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > * {
	animation: kivijalka-rise-far 0.95s cubic-bezier(0.16, 0.84, 0.32, 1) forwards;
}
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > *:nth-child(1) { animation-delay: 0.04s; }
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > *:nth-child(2) { animation-delay: 0.16s; }
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > *:nth-child(3) { animation-delay: 0.28s; }
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > *:nth-child(4) { animation-delay: 0.40s; }
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > *:nth-child(5) { animation-delay: 0.52s; }
.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > *:nth-child(6) { animation-delay: 0.64s; }
/* A card arriving needs more than a fade to read as an arrival: it comes up
 * from further, slightly under-scaled, so the row assembles itself rather than
 * simply appearing. */
@keyframes kivijalka-rise-far {
	from { opacity: 0; transform: translateY(42px) scale(0.955); }
	to   { opacity: 1; transform: none; }
}

/* A line set in capitals as a statement. The text is typed in sentence case and
 * the capitals come from here, so screen readers read words rather than letters
 * and the editor can change the line without retyping it in caps. Capitals need
 * open tracking to read as a line and not a shout. */
.kivijalka-caps {
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
}

/* A counted number must not reflow the line while it climbs. */
.kivijalka-countup em,
.kivijalka-countup { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------- idle motion --
 * Scroll-triggered motion only lives while the visitor scrolls; a page that is
 * still between scrolls reads as a screenshot. These are continuous, and small
 * enough to register as life rather than as animation.
 *
 * They animate `translate` and `rotate`, NOT `transform`, on purpose: the same
 * elements carry a `transform`-based entrance, and two animations on one
 * property do not compose — the later simply wins and the entrance is lost.
 * As separate properties the two run together.
 */
@keyframes kivijalka-idle-drift {
	0%, 100% { translate: 0 0; rotate: 0deg; }
	50%      { translate: 0 -5px; rotate: 0.35deg; }
}
@keyframes kivijalka-idle-drift-alt {
	0%, 100% { translate: 0 0; rotate: 0deg; }
	50%      { translate: 0 -5px; rotate: -0.35deg; }
}
.kivijalka-idle > * {
	animation: kivijalka-idle-drift 7s ease-in-out infinite;
}
/* Neighbours lean opposite ways and start apart, so the row breathes instead
 * of sliding as one slab. */
.kivijalka-idle > *:nth-child(even) {
	animation-name: kivijalka-idle-drift-alt;
}
.kivijalka-idle > *:nth-child(2) { animation-delay: -1.6s; }
.kivijalka-idle > *:nth-child(3) { animation-delay: -3.2s; }
.kivijalka-idle > *:nth-child(4) { animation-delay: -4.8s; }
.kivijalka-idle > *:nth-child(5) { animation-delay: -2.4s; }
.kivijalka-idle > *:nth-child(6) { animation-delay: -4.0s; }


/* Eyebrows carry no underline. A drawn rule was tried here and cut — the
 * eyebrow is already set apart by colour, tracking and case. */

/* Buttons answer the pointer with a lift and a deeper shadow rather than only
 * a colour swap — the movement is what registers as responsive. */
.wp-block-button__link {
	transition: background-color 0.2s ease, border-color 0.2s ease,
		color 0.2s ease, transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1),
		box-shadow 0.25s ease;
}
.wp-block-button__link:hover {
	transform: translateY(-2px);
	box-shadow: 0 8px 22px rgba(10, 8, 8, 0.28);
}
.wp-block-button__link:active { transform: translateY(0); }

/* Editorial images lift slightly with their caption on hover. */
.kivijalka-compare { transition: box-shadow 0.35s ease; }
.kivijalka-compare:hover { box-shadow: 0 14px 40px rgba(10, 8, 8, 0.3); }
.wp-block-gallery .wp-block-image:hover img,
.kivijalka-pattern-media-text .wp-block-image:hover img { transform: scale(1.045); }

@media (prefers-reduced-motion: reduce) {
	.kivijalka-has-reveal .kivijalka-reveal { opacity: 1; transform: none; transition: none; }
	/* Matches the specificity of the reveal-gated hiding rules above, so no
	 * element can be left invisible when motion is suppressed. */
	.kivijalka-has-reveal .kivijalka-sequence > *,
	.kivijalka-has-reveal .kivijalka-sequence.is-visible > *,
	.kivijalka-kenburns,
	.kivijalka-kenburns img,
	img.kivijalka-kenburns,
	.kivijalka-sequence > * { animation: none !important; opacity: 1; transform: none; }
	.kivijalka-has-reveal .kivijalka-verse__line,
	.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger > *,
	.kivijalka-has-reveal .kivijalka-reveal.kivijalka-stagger.is-visible > * {
		animation: none !important;
		opacity: 1;
		transform: none;
	}
	.kivijalka-drift,
	.kivijalka-piece--band,
	.kivijalka-hero__media > figure { transform: none !important; }
	.kivijalka-piece,
	.kivijalka-rank > *:hover .kivijalka-piece { transition: none; transform: none; }
	/* Idle motion is the one thing a visitor cannot escape by not scrolling,
	 * so it must stop completely rather than merely slow down. */
	.kivijalka-idle > * {
		animation: none !important;
		translate: none;
		rotate: none;
	}
	.kivijalka-has-reveal .kivijalka-hero__inner .wp-block-group > * {
		opacity: 1;
		animation: none !important;
		transform: none;
	}
	.wp-block-gallery .wp-block-image img,
	.kivijalka-pattern-media-text .wp-block-image img { transition: none; }
	.wp-block-button__link,
	.kivijalka-compare { transition: none; }
	.wp-block-button__link:hover,
	.wp-block-button__link:active,
	.wp-block-gallery .wp-block-image:hover img,
	.kivijalka-pattern-media-text .wp-block-image:hover img { transform: none; }
	* { scroll-behavior: auto !important; }
}
