/*
 * Mestaruusliiga theme CSS — visual layer on top of theme.json tokens.
 * Every value traces to the Lovable source (source/src/index.css,
 * tailwind.config.ts, component JSX). Component sections get appended
 * as each ROADMAP page lands; this file starts with the global layer.
 */

/* ── Global guards (theme-json.md trap list) ─────────────────────── */

/* The site is light-only by design. Without a declared color-scheme, Chrome for
 * Android's Auto Dark Theme (and Samsung Internet's / in-app browsers' forced dark
 * modes) recolor the page — customer 2026-09-01: footer social icons rendered WHITE
 * on lime because the darkener flipped the dark-teal glyph color. `only light` is
 * the opt-out those browsers honor; plain `light` is not enough. Paired with the
 * <meta name="color-scheme"> tag in functions.php (some engines only read the meta). */
:root {
	color-scheme: only light;
}

/* Dismissed-this-session top banner — see ml_topbanner_dismiss_flag() in functions.php: the flag
 * is set in <head>, so this rule wins the paint race the plugin's DOM guard can lose in WebKit. */
html.ml-topbanner-dismissed .ml-topbanner {
	display: none !important;
}

/* Cookiebot's consent strip, suppressed pre-paint once the visitor has answered — the flash the
 * customer saw on every navigation (2026-09-01). `.ml-consent-known` is set on <html> by
 * ml_cookiebot_no_flash() in functions.php ONLY while a stored CookieConsent answer exists, and
 * removed as soon as Cookiebot reports it needs an answer again or the dialog is reopened from the
 * cookie declaration — read that function's note before touching either half. Cookiebot's markup
 * is injected by their script, so these ids are theirs, not ours. */
html.ml-consent-known #CybotCookiebotDialog,
html.ml-consent-known #CybotCookiebotDialogBodyUnderlay {
	display: none !important;
}

/* clip, not hidden — preserves sticky positioning (sticky header) */
html,
body {
	overflow-x: clip;
}

main {
	margin-top: 0 !important;
}

:root :where(.is-layout-constrained) > .alignfull,
:root :where(.is-layout-constrained) > .alignwide {
	margin-block-start: 0 !important;
}

/* ── Base (source/src/index.css @layer base) ─────────────────────── */

body {
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	-webkit-tap-highlight-color: transparent;
	/* Solid --background (#FAFAFA) base, matching the design's `body { bg-background }`,
	 * plus the .page-background radial/linear gradient stack the source layers over it
	 * (index.css .page-background). Longhand keeps the solid color as a fallback — the
	 * `background:` shorthand would reset background-color to transparent. */
	background-color: hsl(0 0% 98%);
	background-image:
		radial-gradient(ellipse 100% 80% at 50% -30%, hsl(182 100% 9% / 0.08), transparent 60%),
		radial-gradient(ellipse 80% 60% at 100% 100%, hsl(59 85% 47% / 0.10), transparent 50%),
		radial-gradient(ellipse 60% 50% at 0% 70%, hsl(182 100% 9% / 0.06), transparent 40%),
		linear-gradient(180deg, hsl(0 0% 98%) 0%, hsl(210 20% 96%) 100%);
	background-attachment: fixed;
}

/* Root padding: px-4 → sm:px-6 (source .main-container / MainContainer) */
@media (min-width: 640px) {
	body {
		--wp--style--root--padding-left: 1.5rem;
		--wp--style--root--padding-right: 1.5rem;
	}
}

/* ── Heading breakpoint steps (PageHero / section headings) ──────── */
/* h1: text-3xl sm:text-4xl md:text-5xl lg:text-[61px] (PageHero.tsx).
 * Low specificity on purpose — per-component classes may override
 * (e.g. the article title is its own component). */

@media (min-width: 640px) {
	h1 { font-size: 2.25rem; }
}
@media (min-width: 768px) {
	h1 { font-size: 3rem; }
	h2 { font-size: 1.875rem; }
	h3 { font-size: 1.5rem; }
	h4 { font-size: 1.25rem; }
}
@media (min-width: 1024px) {
	h1 { font-size: 3.8125rem; line-height: 1.1; }
}

/* ── Content links (BlockRenderer.tsx renderInline) ──────────────── */
/* font-semibold text-brand-teal underline decoration-brand-lime
 * decoration-2 underline-offset-4 hover:decoration-brand-teal */

.wp-block-post-content p a,
.wp-block-post-content li a,
.wp-block-post-content td a {
	font-weight: 600;
	color: var(--wp--preset--color--brand-teal);
	text-decoration: underline;
	text-decoration-color: var(--wp--preset--color--brand-lime);
	text-decoration-thickness: 2px;
	text-underline-offset: 4px;
	transition: text-decoration-color 0.2s;
}

.wp-block-post-content p a:hover,
.wp-block-post-content li a:hover,
.wp-block-post-content td a:hover {
	text-decoration-color: var(--wp--preset--color--brand-teal);
}

/* ── Buttons: design hover = lime bg + teal text ─────────────────── */
/* Matches BlockRenderer "buttons" primary: bg-brand-teal text-white
 * hover:bg-brand-lime hover:text-brand-teal, and the pill CTAs across
 * the design. !important needed to beat core's .has-X-color rules. */

.wp-block-button__link:hover {
	background-color: var(--wp--preset--color--brand-lime) !important;
	color: var(--wp--preset--color--brand-teal) !important;
}

/* Outline variant: border-2 border-brand-teal text-brand-teal,
 * hover fills teal with white text (BlockRenderer outline buttons). */

.wp-block-button.is-style-outline > .wp-block-button__link {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.75rem;
	font-weight: 800;
	line-height: 1.5;
	letter-spacing: 0.05em;
	background: transparent;
	border: 2px solid var(--wp--preset--color--brand-teal);
	color: var(--wp--preset--color--brand-teal);
}

.wp-block-button.is-style-outline > .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--brand-teal) !important;
	color: var(--wp--preset--color--brand-white) !important;
}

/* ── Shared primitives from source/src/index.css @layer components ── */

.ml-section-title {
	font-size: 1.5rem;
	font-weight: 800;
	color: var(--wp--preset--color--brand-teal);
}

@media (min-width: 768px) {
	.ml-section-title { font-size: 1.875rem; }
}

.ml-accent-underline {
	position: relative;
	display: inline-block;
}

.ml-accent-underline::after {
	content: '';
	position: absolute;
	bottom: 0;
	left: 0;
	width: 100%;
	height: 4px;
	border-radius: 9999px;
	background-color: var(--wp--preset--color--brand-lime);
}

/* ══════════════════════════════════════════════════════════════════
 * Site header chrome — 1:1 port of source SiteHeader.tsx
 * (sl-ml/site-header dynamic block; behaviours in site-header.js)
 * ══════════════════════════════════════════════════════════════════ */

/* .main-container equivalent (source/src/index.css) */
.ml-container {
	width: 100%;
	margin-inline: auto;
	padding-inline: 1rem;
	max-width: calc(1640px + 2rem);
}

@media (min-width: 640px) {
	.ml-container {
		padding-inline: 1.5rem;
		max-width: calc(1640px + 3rem);
	}
}

/* GLOBAL border-box reset — mirrors Tailwind Preflight, which the design gets for free.
 *
 * Every size in this theme is transcribed from the design, where `w-16` means a 16-unit
 * BOX. Under the browser default (content-box) any element that also has padding or a
 * border renders wider/taller than authored: the front-page club tile is `width: 4rem`
 * + 6px padding + 1px border → 78px instead of the design's 64px, so five logos no
 * longer fit a 390px row. The same arithmetic silently inflated drawer rows and team
 * boxes. It was previously patched per component (.ml-container, .ml-ftr__container,
 * .ml-nc, and the picker below), each with its own "no global reset" comment — this is
 * that fix applied once, at the root, so newly added components inherit it too.
 *
 * Scoped to `body *` rather than `*` so admin-bar/editor chrome outside the document
 * body keeps its own box model. The per-component resets below are now redundant but
 * harmless, and are kept so those components stay correct if this rule is ever scoped
 * differently. */
body,
body *,
body *::before,
body *::after { box-sizing: border-box; }

/* Form controls inherit their type — the OTHER half of the same missing reset.
 *
 * Tailwind's preflight makes button/input/select/textarea inherit font AND
 * `line-height` from their ancestor; the browser default instead forces
 * `line-height: normal` on form controls. So a design `<button>` whose size comes
 * from an ARBITRARY value (`text-[clamp(10px,0.9vw,14px)]` — arbitrary sizes pair
 * no line-height, unlike the named text-* scale) inherits 1.5, while the same
 * markup in WP fell back to `normal` (~1.2). Measured: the seven top-level nav
 * triggers rendered 32px tall against the design's 35.4px at 1440, shortening the
 * whole nav row; every other themed <button> had the same latent 3px error.
 *
 * font: inherit alone would NOT fix it — `normal` is a line-height keyword the UA
 * sets separately, so it must be named. */
body :is(button, input, optgroup, select, textarea) {
	font-family: inherit;
	font-size: 100%;
	font-weight: inherit;
	line-height: inherit;
	letter-spacing: inherit;
	color: inherit;
}

/* .ml-container's `max-width: calc(1640px + padding)` is authored for border-box
 * (padding sits INSIDE the cap → 1640 content); without a reset the padding added
 * OUTSIDE the cap, making every .ml-container ~48px too wide and clipping the header's
 * right-anchored content (utility pills, nav, search) at ≤~1490px — invisible at 1920,
 * which is why the gate missed it. Now covered by the global rule above; kept explicit. */
.ml-container,
.ml-container *,
.ml-container *::before,
.ml-container *::after { box-sizing: border-box; }

/* Inline Lucide icons */
.ml-icon { display: inline-block; flex-shrink: 0; }
.ml-icon--3 { width: 0.75rem; height: 0.75rem; }
.ml-icon--35 { width: 0.875rem; height: 0.875rem; }
.ml-icon--4 { width: 1rem; height: 1rem; }
.ml-icon--5 { width: 1.25rem; height: 1.25rem; }

/* ── Header root ── */
/* The FSE header template-part renders as <header class="wp-block-template-part">
 * wrapping the TopBanner + .ml-hdr. That wrapper would be .ml-hdr's sticky
 * containing block (only ~topbanner+header tall), so the header un-sticks after
 * ~200 px of scroll. Dissolve the wrapper with display:contents so .ml-hdr
 * sticks within .wp-site-blocks (full page), matching SiteHeader.tsx `sticky
 * top-0`. Scoped to the header element so the footer template-part is untouched. */
.wp-site-blocks > header.wp-block-template-part {
	display: contents;
}

.ml-hdr {
	position: sticky;
	top: 0;
	z-index: 50;
	width: 100%;
	background: var(--wp--preset--color--brand-teal);
	/* `backdrop-filter` is deliberately NOT in this list. It is set on __sheen, not here, and
	 * animating a backdrop filter is the one part of this transition WebKit does badly: Safari
	 * promotes the layer when the filter appears and drops it when it goes, so the header flickered
	 * on the toggle (customer 2026-09-01, macbook/iphone). The sheen keeps a stable compositing
	 * layer instead — see .ml-hdr__sheen below. */
	transition: background-color 0.3s, box-shadow 0.3s;
}

/* Scrolled state. The blur+translucent fill live on the __sheen background
 * layer, NOT on .ml-hdr itself: `backdrop-filter` on .ml-hdr would make it the
 * containing block for its position:fixed descendants (the mobile drawer +
 * scrim, which are direct children), clamping them to the ~64px header box
 * instead of the viewport → the drawer breaks once scrolled. __sheen is a
 * sibling of the drawer, so blurring it leaves the drawer viewport-anchored.
 * .ml-hdr goes transparent when scrolled so the sheen's blur reads the page. */
.ml-hdr.is-scrolled {
	background: transparent;
	box-shadow: 0 8px 24px -12px rgba(0, 0, 0, 0.45);
}

.ml-hdr.is-scrolled .ml-hdr__sheen {
	background: hsl(182 100% 9% / 0.95);
	-webkit-backdrop-filter: blur(12px);
	backdrop-filter: blur(12px);
}

.ml-hdr__sheen {
	pointer-events: none;
	position: absolute;
	inset: 0;
	background: linear-gradient(to right, hsl(182 100% 9%), hsl(182 100% 9% / 0.95), hsl(182 100% 9%));
	/* Own a compositing layer from the first paint, so switching the blur on at the scroll
	 * threshold is a background-colour change on an existing layer rather than a layer being
	 * created mid-transition (which is what Safari flickered on). translateZ is enough; the sheen
	 * is a sibling of the fixed drawer/scrim, so unlike a filter on .ml-hdr it cannot become their
	 * containing block — see the note on .ml-hdr.is-scrolled above. */
	transform: translateZ(0);
	transition: background-color 0.3s;
}

.ml-hdr__glow {
	pointer-events: none;
	position: absolute;
	top: -6rem;
	left: 33.333%;
	width: 40rem;
	height: 12rem;
	border-radius: 9999px;
	background: hsl(59 85% 47% / 0.06);
	filter: blur(64px);
}

.ml-hdr__inner { position: relative; }

/* ── Utility bar ── */
.ml-hdr__utility {
	display: none;
	border-bottom: 1px solid rgb(255 255 255 / 0.1);
	color: rgb(255 255 255 / 0.85);
	overflow: hidden;
	max-height: 3rem;
	opacity: 1;
	transition: max-height 0.3s, opacity 0.3s, border-color 0.3s;
}

@media (min-width: 768px) {
	.ml-hdr__utility { display: block; }
}

.ml-hdr.is-scrolled .ml-hdr__utility {
	max-height: 0;
	opacity: 0;
	pointer-events: none;
	border-bottom-color: transparent;
}

.ml-hdr__utility-row {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	height: 2.75rem;
	font-size: 11px;
	text-transform: uppercase;
	letter-spacing: 0.14em;
	font-weight: 800;
}

.ml-hdr__utility-links { display: flex; align-items: center; gap: 0.5rem; }

.ml-hdr__pill {
	display: inline-flex;
	align-items: center;
	gap: 0.375rem;
	height: 1.75rem;
	padding-inline: 0.75rem;
	border-radius: 9999px;
	border: 1px solid rgb(255 255 255 / 0.3);
	color: #fff;
	text-decoration: none;
	transition: color 0.2s, border-color 0.2s, background-color 0.2s;
}

.ml-hdr__pill:hover {
	border-color: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-lime);
}

/* The design's Lipunmyynti pill (SiteHeader.tsx:346) carries NO border at all —
 * only the three outline pills do. Keeping the base 1px border and merely making it
 * transparent still occupies 2px of width, and because the utility row is
 * right-aligned (justify-content:flex-end) that pushed the whole row — Ottelut,
 * Sarjataulukko, Pistepörssi included — 2px left of the design at every width. */
.ml-hdr__pill--lime {
	border: 0;
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
}

.ml-hdr__pill--lime:hover {
	background: #fff;
	color: var(--wp--preset--color--brand-teal);
}

/* ── Main bar ── */
.ml-hdr__bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	height: 4rem;
	gap: 0.75rem;
}

@media (min-width: 768px) {
	.ml-hdr__bar { height: 5rem; }
}

@media (min-width: 1024px) {
	.ml-hdr__bar { gap: 0.5rem; }
}

@media (min-width: 1280px) {
	.ml-hdr__bar { gap: clamp(0.6rem, 1.8vw, 2.5rem); }
}

.ml-hdr__brand {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	flex-shrink: 0;
	text-decoration: none;
}

/* The wordmark PNG (mestaruusliiga-logo-wide, 2206x404) carries 95px of TRANSPARENT gutter on
 * each side — 23.5% of its height — so an <img> whose box starts on the 1640 rail renders its
 * first visible glyph ~9px inside it, while every text/card below starts exactly on the rail.
 * Pull the image back out by that scaled gutter so the LOGO, not its empty margin, sits on the
 * rail. Kept as a margin (not a transform) because :hover already owns transform: scale(). */
.ml-hdr__logo {
	--ml-logo-h: 1.75rem;
	height: var(--ml-logo-h);
	margin-inline-start: calc(var(--ml-logo-h) * -0.2352);
	width: auto;
	filter: brightness(0) invert(1);
	transition: transform 0.3s;
}

@media (min-width: 1280px) {
	.ml-hdr__logo { --ml-logo-h: 2.25rem; }
}

.ml-hdr__brand:hover .ml-hdr__logo { transform: scale(1.03); }

.ml-hdr__logo-text { color: #fff; font-weight: 800; text-transform: uppercase; }

/* ── Desktop nav ── */
.ml-hdr__nav {
	display: none;
}

@media (min-width: 1024px) {
	.ml-hdr__nav {
		display: flex;
		align-items: center;
		flex: 1 1 0%;
		justify-content: flex-end;
		min-width: 0;
	}
}

.ml-hdr__list {
	display: flex;
	align-items: center;
	gap: 0;
	flex-wrap: nowrap;
	list-style: none;
	margin: 0;
	padding: 0;
}

.ml-hdr__item { position: relative; }

.ml-hdr__top {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
	padding: 0.5rem clamp(0.2rem, 0.9vw, 1.4rem);
	font-family: var(--wp--preset--font-family--body);
	font-size: clamp(10px, 0.9vw, 14px);
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	white-space: nowrap;
	color: #fff;
	background: none;
	border: 0;
	cursor: pointer;
	text-decoration: none;
	transition: color 0.2s;
}

.ml-hdr__top:hover,
.ml-hdr__top:focus-visible,
.ml-hdr__item.is-active .ml-hdr__top,
.ml-hdr__item.is-open .ml-hdr__top {
	color: var(--wp--preset--color--brand-lime);
	outline: none;
}

.ml-hdr__chev { transition: transform 0.2s; }
.ml-hdr__item.is-open .ml-hdr__chev { transform: rotate(180deg); }

.ml-hdr__underline {
	position: absolute;
	left: clamp(0.2rem, 0.9vw, 1.4rem);
	right: clamp(0.2rem, 0.9vw, 1.4rem);
	bottom: -0.125rem;
	height: 3px;
	border-radius: 9999px;
	background: var(--wp--preset--color--brand-lime);
	transform-origin: left;
	transform: scaleX(0);
	transition: transform 0.3s ease-out;
}

.ml-hdr__item.is-active .ml-hdr__underline,
.ml-hdr__item.is-open .ml-hdr__underline,
.ml-hdr__item:hover .ml-hdr__underline,
.ml-hdr__item:focus-within .ml-hdr__underline {
	transform: scaleX(1);
}

/* The design's search glyph: a decorative aria-hidden span (SiteHeader.tsx) — 40×40
   rounded box, hover tint, cursor-default, NO behaviour. The working search feature
   was removed on the customer's ask (tasklist 2026-07-30). */
.ml-hdr__search {
	align-items: center;
	justify-content: center;
	width: 2.5rem;
	height: 2.5rem;
	padding: 0;
	border: 0;
	border-radius: 9999px;
	background: none;
	color: #fff;
	font: inherit;
	line-height: inherit;
	cursor: default;
	transition: color 0.2s, background-color 0.2s;
}

.ml-hdr__search:hover {
	color: var(--wp--preset--color--brand-lime);
	background: rgb(255 255 255 / 0.05);
}

.ml-hdr__search--desktop { display: none; margin-left: 0.25rem; flex-shrink: 0; }

@media (min-width: 1280px) {
	.ml-hdr__search--desktop { display: inline-flex; }
}

/* design: hidden md:inline-flex — no glyph below 768, shown md–lg beside the burger. */
.ml-hdr__search--mobile { display: none; }
@media (min-width: 768px) {
	.ml-hdr__search--mobile { display: inline-flex; }
}

.ml-hdr__mobile-actions {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-right: -0.75rem;
}

@media (min-width: 1024px) {
	.ml-hdr__mobile-actions { display: none; }
}

/* Burger (three-bar morph, source burger spans) */
.ml-hdr__burger {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	color: #fff;
	background: none;
	border: 0;
	cursor: pointer;
	padding: 0;
}

.ml-hdr__burger-bg {
	position: absolute;
	inset: 0.375rem;
	border-radius: 0.375rem;
	background: rgb(255 255 255 / 0);
	transition: background-color 0.2s;
}

.ml-hdr__burger:hover .ml-hdr__burger-bg { background: rgb(255 255 255 / 0.1); }

.ml-hdr__burger-icon {
	position: relative;
	display: block;
	width: 1.25rem;
	height: 1rem;
}

.ml-hdr__burger-bar {
	position: absolute;
	left: 0;
	height: 2px;
	min-height: 2px;
	border-radius: 9999px;
	background: currentColor;
	transition: all 0.3s ease-out;
	display: block;
}

.ml-hdr__burger-bar--1 { top: 0; width: 1.25rem; }
/* The source authors the middle bar `left-0 … right-0 w-3` — over-constrained, so the LIVE
 * design resolves left+width and the `right-0` is dead: the short bar is LEFT-aligned
 * (measured left12/w12 in the rendered header). Porting the right-0 literally right-aligned
 * it — the customer's "hamburger icon is wrong in mobile". */
.ml-hdr__burger-bar--2 { top: 50%; transform: translateY(-50%); width: 0.75rem; }
.ml-hdr__burger-bar--3 { bottom: 0; width: 1rem; }
.ml-hdr__burger:hover .ml-hdr__burger-bar--2,
.ml-hdr__burger:hover .ml-hdr__burger-bar--3 { width: 1.25rem; }

.ml-hdr__burger.is-open .ml-hdr__burger-bar--1 { top: 50%; transform: translateY(-50%) rotate(45deg); }
.ml-hdr__burger.is-open .ml-hdr__burger-bar--2 { width: 1.25rem; transform: translateY(-50%) rotate(-45deg); }
.ml-hdr__burger.is-open .ml-hdr__burger-bar--3 { top: 50%; bottom: auto; transform: translateY(-50%); opacity: 0; }

.ml-hdr__accent {
	height: 2px;
	width: 100%;
	background: linear-gradient(to right, transparent, var(--wp--preset--color--brand-lime), transparent);
	opacity: 0.7;
}

/* ── Mega dropdown ── */
.ml-mm {
	display: none;
}

@media (min-width: 1024px) {
	.ml-mm {
		display: block;
		position: absolute;
		left: 0;
		right: 0;
		top: 100%;
		z-index: 40;
		opacity: 0;
		transform: translateY(-0.5rem);
		pointer-events: none;
		transition: opacity 0.2s, transform 0.2s;
	}
}

.ml-mm.is-open {
	opacity: 1;
	transform: translateY(0);
	pointer-events: auto;
}

.ml-mm__surface {
	position: relative;
	background: var(--wp--preset--color--brand-teal);
	-webkit-backdrop-filter: blur(12px);
	backdrop-filter: blur(12px);
	border-top: 1px solid rgb(255 255 255 / 0.1);
	box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.5);
	overflow: hidden;
}

.ml-mm__blob {
	pointer-events: none;
	position: absolute;
	top: -6rem;
	left: 25%;
	width: 36rem;
	height: 12rem;
	border-radius: 9999px;
	background: hsl(59 85% 47% / 0.07);
	filter: blur(64px);
}

.ml-mm__topline {
	pointer-events: none;
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 2px;
	background: linear-gradient(to right, transparent, hsl(59 85% 47% / 0.6), transparent);
}

.ml-mm__body { position: relative; padding-block: 2.5rem; }

.ml-mm__panel { display: none; }
.ml-mm__panel.is-active { display: block; }

@keyframes mlPanelIn {
	from { opacity: 0; transform: translateY(-0.5rem); }
	to { opacity: 1; transform: translateY(0); }
}

.ml-mm__panel.is-active .ml-mm__grid { animation: mlPanelIn 0.3s ease both; }

.ml-mm__grid {
	display: grid;
	column-gap: 3rem;
	row-gap: 2.5rem;
}

.ml-mm__grid--teams { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 1200px; }
.ml-mm__grid--wide { grid-template-columns: 1fr; max-width: 56rem; }
.ml-mm__grid--narrow { grid-template-columns: 1fr; max-width: 42rem; }

.ml-mm__group-title {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin: 0 0 1rem;
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 0.25em;
	text-transform: uppercase;
	color: var(--wp--preset--color--brand-lime);
}

.ml-mm__group-rule { width: 1.25rem; height: 1px; background: hsl(59 85% 47% / 0.6); }

.ml-mm__links {
	list-style: none;
	margin: 0;
	padding: 0;
}

.ml-mm__links > li + li { margin-top: 0.125rem; }

.ml-mm__links--two-col {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	column-gap: 1.5rem;
	row-gap: 0.125rem;
}

.ml-mm__links--two-col > li + li { margin-top: 0; }

.ml-mm__leaf {
	position: relative;
	display: block;
	padding: 0.625rem 0.75rem;
	border-radius: 0.375rem;
	text-decoration: none;
	transition: background-color 0.2s, transform 0.2s;
}

.ml-mm__leaf:hover,
.ml-mm__leaf:focus-visible {
	background: rgb(255 255 255 / 0.1);
	transform: translateX(2px);
	outline: none;
}

.ml-mm__leaf-bar {
	position: absolute;
	left: 0;
	top: 50%;
	height: 1.25rem;
	width: 2px;
	border-radius: 9999px;
	background: var(--wp--preset--color--brand-lime);
	transform: translateY(-50%) scaleY(0);
	transform-origin: center;
	transition: transform 0.2s;
}

.ml-mm__leaf:hover .ml-mm__leaf-bar { transform: translateY(-50%) scaleY(1); }

.ml-mm__leaf-label {
	display: flex;
	align-items: center;
	gap: 0.375rem;
	font-size: 0.875rem;
	/* design text-sm pairs 20px; inheriting 1.5 made every dropdown row 1px taller */
	line-height: 1.25rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.025em;
	color: #fff;
	transition: color 0.2s;
}

.ml-mm__leaf:hover .ml-mm__leaf-label { color: var(--wp--preset--color--brand-lime); }

.ml-mm__leaf-desc {
	display: block;
	margin-top: 0.25rem;
	font-size: 13px;
	color: rgb(255 255 255 / 0.75);
	text-transform: none;
	font-weight: 500;
	letter-spacing: normal;
	line-height: 1.375;
}

.ml-mm__teams {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 0.25rem;
	list-style: none;
	margin: 0;
	padding: 0;
}

@media (min-width: 1280px) {
	.ml-mm__teams { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

.ml-mm__team {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.5rem;
	padding: 0.75rem;
	border-radius: 0.5rem;
	text-decoration: none;
	transition: background-color 0.2s, transform 0.2s;
}

.ml-mm__team:hover {
	background: rgb(255 255 255 / 0.05);
	transform: translateY(-2px);
}

.ml-mm__team-box {
	width: 3.5rem;
	height: 3.5rem;
	border-radius: 0.375rem;
	background: rgb(255 255 255 / 0.95);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0.375rem;
	box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
	transition: box-shadow 0.2s;
}

.ml-mm__team:hover .ml-mm__team-box { box-shadow: 0 8px 20px -6px hsl(59 85% 47% / 0.5); }

.ml-mm__team-box img { max-width: 100%; max-height: 100%; object-fit: contain; }

.ml-mm__team-name {
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.025em;
	color: rgb(255 255 255 / 0.85);
	text-align: center;
	line-height: 1.25;
	transition: color 0.2s;
}

.ml-mm__team:hover .ml-mm__team-name { color: var(--wp--preset--color--brand-lime); }

/* ── Mobile drawer ── */
html.ml-drawer-lock { overflow: hidden; }

.ml-hdr__scrim {
	position: fixed;
	inset: 0;
	z-index: 59;
	background: rgb(0 0 0 / 0.8);
	opacity: 0;
	pointer-events: none;
	/* 150ms = the sheet overlay's tailwindcss-animate default fade in the design. */
	transition: opacity 0.15s;
}

.ml-hdr__scrim.is-open { opacity: 1; pointer-events: auto; }

.ml-drawer {
	position: fixed;
	top: 0;
	bottom: 0;
	right: 0;
	z-index: 60;
	width: 100%;
	background: var(--wp--preset--color--brand-teal);
	color: #fff;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	/* The design zeroes the sheet's slide (--tw-enter/exit-translate-x: 0px in SiteHeader.tsx):
	 * the panel appears IN PLACE — only the scrim fades and the rows stagger in. Hide the
	 * closed drawer with visibility, not a transform, so nothing ever moves horizontally. */
	visibility: hidden;
	/* design SheetContent has gap-4: the nav starts 16px below the header bar. Without it the
	   whole drawer body — every numbered row and the Pikalinkit block — sat 16px too high. */
	gap: 1rem;
}

@media (min-width: 640px) {
	.ml-drawer { width: 440px; max-width: 440px; }
}

.ml-drawer.is-open { visibility: visible; }

@media (min-width: 1024px) {
	.ml-drawer,
	.ml-hdr__scrim { display: none !important; }
}

.ml-drawer__glow {
	pointer-events: none;
	position: absolute;
	border-radius: 9999px;
	filter: blur(64px);
}

.ml-drawer__glow--top { top: -8rem; right: -6rem; width: 28rem; height: 28rem; background: hsl(59 85% 47% / 0.08); }
.ml-drawer__glow--bottom { bottom: 0; left: -8rem; width: 24rem; height: 24rem; background: hsl(59 85% 47% / 0.05); }

.ml-drawer__head {
	position: relative;
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding-inline: 1rem;
	height: 4rem;
	border-bottom: 1px solid rgb(255 255 255 / 0.1);
	margin-top: env(safe-area-inset-top);
}

@media (min-width: 640px) {
	.ml-drawer__head { padding-inline: 1.5rem; justify-content: flex-end; }
}

@media (min-width: 768px) {
	.ml-drawer__head { height: 5rem; }
}

.ml-drawer__brand { display: inline-flex; align-items: center; }

@media (min-width: 640px) {
	.ml-drawer__brand { display: none; }
}

/* Same pull-back as .ml-hdr__logo (the wordmark PNG's 23.5 % transparent gutter): the drawer's
 * head has the same 1 rem inset as the header bar, so without it the visible glyph sat 6.6 px
 * further in while the menu was open and "the logo moved" (customer 2026-08-27). The design's two
 * logos share one class set and therefore one x — this keeps the ported pair equal too. */
.ml-drawer__logo { height: 1.75rem; width: auto; margin-inline-start: calc(1.75rem * -0.2352); filter: brightness(0) invert(1); }

.ml-drawer__close {
	position: relative;
	margin-right: -0.75rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	color: #fff;
	background: none;
	border: 0;
	cursor: pointer;
	padding: 0;
}

.ml-drawer__close-bg {
	position: absolute;
	inset: 0.375rem;
	border-radius: 0.375rem;
	background: rgb(255 255 255 / 0);
	transition: background-color 0.2s;
}

.ml-drawer__close:hover .ml-drawer__close-bg { background: rgb(255 255 255 / 0.1); }

.ml-drawer__close-icon { position: relative; display: block; width: 1.25rem; height: 1rem; }

.ml-drawer__close-icon span {
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	height: 2px;
	min-height: 2px;
	border-radius: 9999px;
	background: currentColor;
	display: block;
}

.ml-drawer__close-icon span:first-child { transform: translateY(-50%) rotate(45deg); }
.ml-drawer__close-icon span:last-child { transform: translateY(-50%) rotate(-45deg); }

.ml-drawer__scroll {
	position: relative;
	flex: 1 1 0%;
	min-height: 0;
	overflow-y: auto;
	/* overflow-x must be EXPLICITLY hidden: with only overflow-y set it computes to auto,
	 * and the rows' 1rem enter animation transiently overflows — the menu becomes
	 * horizontally pannable (design's sheet panel is overflow-hidden; nothing pans). */
	overflow-x: hidden;
	-webkit-overflow-scrolling: touch;
}

.ml-drawer__list { padding: 0 0.5rem 0.5rem; }

@media (min-width: 640px) {
	.ml-drawer__list { padding: 0 0.75rem 0.5rem; }
}

@keyframes mlDrawerRowIn {
	from { opacity: 0; transform: translateX(1rem); }
	to { opacity: 1; transform: translateX(0); }
}

.ml-drawer__row { border-bottom: 1px solid rgb(255 255 255 / 0.06); }
.ml-drawer__row:last-child { border-bottom: 0; }

.ml-drawer.is-open .ml-drawer__row {
	animation: mlDrawerRowIn 350ms ease both;
	animation-delay: inherit;
}

.ml-drawer__toplink {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	width: 100%;
	padding-inline: 0.75rem;
	padding-block: 0; /* a <button> keeps the UA's 1px block padding otherwise */
	height: 52px;
	border-radius: 0.375rem;
	background: none;
	border: 0;
	cursor: pointer;
	text-decoration: none;
	text-align: left;
	overflow: hidden;
	transition: background-color 0.2s;
	font-family: var(--wp--preset--font-family--body);
}

.ml-drawer__toplink:hover { background: rgb(255 255 255 / 0.04); }

.ml-drawer__num {
	font-variant-numeric: tabular-nums;
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 0.18em;
	width: 2rem;
	flex-shrink: 0;
	text-align: left;
	color: rgb(255 255 255 / 0.35);
	transition: color 0.2s;
}

.ml-drawer__toplink.is-active .ml-drawer__num { color: var(--wp--preset--color--brand-lime); }

.ml-drawer__label {
	min-width: 0;
	flex: 1 1 0%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	text-align: left;
	font-size: 19px;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: -0.025em;
	line-height: 1;
	color: #fff;
	transition: color 0.2s;
}

@media (min-width: 640px) {
	.ml-drawer__label { font-size: 22px; }
}

/* Only the CURRENT page turns the label lime — not :hover. The design's drawer row carries
 * just `hover:bg-white/[0.04]` (a background tint, no colour change), and on a touch device
 * :hover STICKS after a tap, so a hover rule here left the row the user last tapped
 * permanently lime — reading as "selected" on a page it doesn't belong to. */
.ml-drawer__toplink.is-active .ml-drawer__label { color: var(--wp--preset--color--brand-lime); }

.ml-drawer__plus {
	color: hsl(59 85% 47% / 0.8);
	transition: transform 0.3s;
}

.ml-drawer__acc-trigger.is-expanded .ml-drawer__plus { transform: rotate(45deg); }

.ml-drawer__section {
	overflow: hidden;
	max-height: 0;
	transition: max-height 0.2s ease-out;
	/* The design's AccordionContent carries text-sm (accordion.tsx), so everything inside a
	 * drawer dropdown inherits 14px/20px. The group title sets only font-size (10px), so it
	 * takes its line box from here — inheriting the drawer's 1.5 made it 15px instead of 20px
	 * and pulled every link in the panel 5px up. */
	font-size: 0.875rem;
	line-height: 1.25rem;
}

.ml-drawer__section-inner {
	margin-left: 1.75rem;
	padding: 0 0 0.75rem 0.75rem;
	border-left: 1px solid hsl(59 85% 47% / 0.3);
	display: flex;
	flex-direction: column;
	gap: 1rem;
}

.ml-drawer__group-title {
	margin: 0 0 0.5rem;
	padding-inline: 0.25rem;
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: hsl(59 85% 47% / 0.8);
}

.ml-drawer__links { list-style: none; margin: 0; padding: 0; }
.ml-drawer__links > li + li { margin-top: 0.125rem; }

.ml-drawer__leaf {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.5rem;
	font-size: 0.875rem;
	line-height: 1.25rem; /* design text-sm pairs 20px (was inheriting 1.5 → 21px) */
	font-weight: 600;
	color: rgb(255 255 255 / 0.85);
	border-radius: 0.375rem;
	text-decoration: none;
	transition: background-color 0.2s, color 0.2s;
}

.ml-drawer__leaf:hover {
	background: rgb(255 255 255 / 0.06);
	color: var(--wp--preset--color--brand-lime);
}

.ml-drawer__leaf-dot {
	width: 0.25rem;
	height: 0.25rem;
	border-radius: 9999px;
	background: rgb(255 255 255 / 0.3);
	transition: background-color 0.2s;
	flex-shrink: 0;
}

.ml-drawer__leaf:hover .ml-drawer__leaf-dot { background: var(--wp--preset--color--brand-lime); }

/* Current page inside an open drawer section (SiteHeader.tsx isLeafActive): the row keeps the
 * white/6% tint permanently and both the label and its dot go lime — the drawer's second level
 * of "you are here", under the lime toplink. */
.ml-drawer__leaf.is-active {
	background: rgb(255 255 255 / 0.06);
	color: var(--wp--preset--color--brand-lime);
}

.ml-drawer__leaf.is-active .ml-drawer__leaf-dot { background: var(--wp--preset--color--brand-lime); }

.ml-drawer__leaf-label { flex: 1 1 0%; }

.ml-drawer__teams {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.25rem;
	list-style: none;
	margin: 0;
	padding: 0;
}

@media (min-width: 360px) {
	.ml-drawer__teams { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.ml-drawer__team {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.375rem;
	padding: 0.5rem;
	border-radius: 0.375rem;
	text-decoration: none;
	transition: background-color 0.2s;
}

.ml-drawer__team:hover { background: rgb(255 255 255 / 0.06); }

.ml-drawer__team-box {
	width: 3rem;
	height: 3rem;
	border-radius: 0.375rem;
	background: rgb(255 255 255 / 0.95);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0.25rem;
}

.ml-drawer__team-box img { max-width: 100%; max-height: 100%; object-fit: contain; }

.ml-drawer__team-name {
	font-size: 10px;
	font-weight: 700;
	text-transform: uppercase;
	color: rgb(255 255 255 / 0.85);
	text-align: center;
	line-height: 1.25;
}

.ml-drawer__quick { padding: 1.5rem 1.25rem 1rem; }

@media (min-width: 640px) {
	.ml-drawer__quick { padding-inline: 1.5rem; }
}

.ml-drawer__quick-title {
	margin: 0 0 0.75rem;
	font-size: 10px;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.28em;
	color: hsl(59 85% 47% / 0.8);
}

.ml-drawer__quick-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.5rem;
}

.ml-drawer__quick-pill {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.375rem;
	padding: 0.75rem;
	border-radius: 9999px;
	background: rgb(255 255 255 / 0.1);
	border: 1px solid rgb(255 255 255 / 0.2);
	color: #fff;
	font-size: 11px;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	white-space: nowrap;
	text-decoration: none;
	transition: background-color 0.2s, color 0.2s, border-color 0.2s;
}

.ml-drawer__quick-pill:hover {
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	border-color: var(--wp--preset--color--brand-lime);
}

.ml-drawer__quick-pill--lime {
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	border-color: transparent;
}

.ml-drawer__quick-pill--lime:hover { background: #fff; color: var(--wp--preset--color--brand-teal); }

.ml-drawer__fade {
	pointer-events: none;
	position: sticky;
	bottom: 0;
	left: 0;
	right: 0;
	height: 3rem;
	background: linear-gradient(to top, rgb(0 0 0 / 0.35), rgb(0 0 0 / 0.12), transparent);
	z-index: 10;
	transition: opacity 0.3s;
}

.ml-drawer__fade.is-hidden { opacity: 0; }

/* ══════════════════════════════════════════════════════════════════
 * Site footer — 1:1 port of source SiteFooter.tsx
 * (sl-ml/site-footer dynamic block; back-to-top in site-footer.js)
 * ══════════════════════════════════════════════════════════════════ */

.ml-ftr {
	position: relative;
	margin-top: 5rem;
	color: #fff;
	/* The teal lives on .ml-ftr__body, which is enough for the designed markup — but a block the
	 * customer adds at SHELL level renders after that body, i.e. on the page's white background,
	 * inheriting this white text: invisible (measured on the stress footer, "LISATTY ml-ftr__body"
	 * rendered white-on-white below the bottom bar). Painting the shell too costs nothing —
	 * __body paints the same colour over it — and means every drop target inside the footer is
	 * dark. */
	background: var(--wp--preset--color--brand-teal);
}

.ml-ftr__accent {
	height: 3px;
	width: 100%;
	background: linear-gradient(to right, var(--wp--preset--color--brand-lime), hsl(59 85% 47% / 0.4), var(--wp--preset--color--brand-lime));
}

/* Composable footer (parts/footer.html group shell). A core/group can't emit a bare decorative
 * div, so the shell carries the accent line as ::before — same box as .ml-ftr__accent above
 * (skill: "a sheen/scrim the monolith emitted as its first child must become a ::before"). */
.ml-ftr--shell::before {
	content: '';
	display: block;
	height: 3px;
	width: 100%;
	background: linear-gradient(to right, var(--wp--preset--color--brand-lime), hsl(59 85% 47% / 0.4), var(--wp--preset--color--brand-lime));
}

.ml-ftr__body {
	background: var(--wp--preset--color--brand-teal);
	position: relative;
	overflow: hidden;
}

/* The 1640px cap is the CONTENT rail, with the page gutter OUTSIDE it — exactly like
 * .ml-container above (SiteFooter.tsx nests `px-4 sm:px-6` around an inner
 * `mx-auto w-full max-w-[1640px]`). With `max-width: 1640px` and the padding inside a
 * border-box element the rail came out 2×gutter narrow, so above ~1688px — where the cap
 * finally engages — EVERY footer row was 1592px wide and 24px right of the design's 1640px
 * at x=140. That is the reported "footer has different width than lovable"; it is invisible
 * at 1440, which is why measuring the rail there showed a match. */
.ml-ftr__container {
	width: 100%;
	margin-inline: auto;
	max-width: calc(1640px + 2rem);
	padding-inline: 1rem;
}
/* No global box-sizing reset in this theme; .ml-ftr__container is authored for border-box
   (width:100% + side padding), so without this it renders 2×padding too wide and its right
   edge is clipped by .ml-ftr__body{overflow:hidden} at narrow widths (partner names cut off
   at mobile). Same scoped reset as .ml-nc / .ml-content / .ml-container. */
.ml-ftr__container, .ml-ftr__container * { box-sizing: border-box; }

@media (min-width: 640px) {
	.ml-ftr__container { padding-inline: 1.5rem; max-width: calc(1640px + 3rem); }
}

/* Back-to-top strip */
.ml-ftr__top-strip { position: relative; border-bottom: 1px solid rgb(255 255 255 / 0.1); }

.ml-ftr__top-row {
	padding-block: 1.25rem;
	display: flex;
	align-items: center;
	justify-content: flex-end;
}

.ml-ftr__to-top {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.5rem;
	height: 2.5rem;
	border-radius: 9999px;
	border: 1px solid rgb(255 255 255 / 0.15);
	background: none;
	color: rgb(255 255 255 / 0.8);
	cursor: pointer;
	transition: color 0.3s, background-color 0.3s, border-color 0.3s;
}

.ml-ftr__to-top:hover {
	color: var(--wp--preset--color--brand-teal);
	background: var(--wp--preset--color--brand-lime);
	border-color: var(--wp--preset--color--brand-lime);
}

/* Main grid */
.ml-ftr__main { padding-block: 3.5rem; }

@media (min-width: 768px) {
	.ml-ftr__main { padding-block: 5rem; }
}

.ml-ftr__grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: 2.5rem;
}

@media (min-width: 640px) {
	.ml-ftr__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
	.ml-ftr__grid { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 2rem; }
}

@media (min-width: 1280px) {
	.ml-ftr__grid { gap: 3rem; }
}

@media (min-width: 1024px) {
	.ml-ftr__brand-col { grid-column: span 4 / span 4; }
	.ml-ftr__team-col { grid-column: span 4 / span 4; }
	.ml-ftr__team-col--divided {
		border-left: 1px solid rgb(255 255 255 / 0.1);
		padding-left: 2rem;
	}
}

@media (min-width: 1280px) {
	.ml-ftr__team-col--divided { padding-left: 3rem; }
}

.ml-ftr__brand { display: inline-flex; align-items: center; }
/* Same transparent-gutter pull-back as .ml-hdr__logo above (see the note there). */
.ml-ftr__logo {
	--ml-logo-h: 2.5rem;
	height: var(--ml-logo-h);
	margin-inline-start: calc(var(--ml-logo-h) * -0.2352);
	width: auto;
	filter: brightness(0) invert(1);
	transition: transform 0.3s;
}
.ml-ftr__brand:hover .ml-ftr__logo { transform: scale(1.02); }

.ml-ftr .ml-ftr__blurb {
	/* two classes, not one: the editor canvas's flow-layout rule zeroes a (0,1,0) margin —
	 * measured 20px live vs 0 in the canvas (skill: "restate it at two classes"). Same for
	 * __contacts/__socials/__partners below. */
	margin: 1.25rem 0 0;
	font-size: 0.875rem;
	line-height: 1.625;
	color: rgb(255 255 255 / 0.7);
	max-width: 24rem;
}

.ml-ftr .ml-ftr__contacts {
	margin-top: 1.5rem;
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	/* SiteFooter.tsx: a plain `text-sm` — 14px paired with a FIXED 20px. Without it the
	 * global 1.5 ratio gave 21px, which stacked up over the footer's rows and left every
	 * row below the columns ~1.5px low (and the footer 2.6px taller than the design). */
	font-size: 0.875rem;
	line-height: 1.25rem;
	color: rgb(255 255 255 / 0.7);
}

.ml-ftr__contact-row { display: flex; align-items: center; gap: 0.5rem; margin: 0; }
/* ── "An ADDED item gets the design" ──────────────────────────────────────────────────────────
 * A block the customer adds to the footer carries no design class, so it inherited .ml-ftr's
 * plain white 16px instead of the column's 14px/70% — measured: an added paragraph came out
 * rgb(255,255,255) 16px/24px next to the blurb's rgba(255,255,255,.7) 14px/22.75px ("if i add
 * text it comes different colored").
 *
 * Everything below is wrapped in :where(), i.e. specificity ZERO, so it is a DEFAULT that every
 * designed class rule overrides by construction — that is what keeps the A/B diff at 0 deltas
 * while still dressing bare blocks. Never raise the specificity of this block.
 *
 * NB .ml-ftr__contacts is deliberately NOT in the paragraph list: it sets font-size/line-height
 * on ITSELF for its rows to inherit, and an inherited value loses to ANY direct declaration —
 * even a zero-specificity one. Adding it here re-set every contact row's line-height 20px →
 * 22.75px and grew the footer 5.5px (caught by the A/B diff). Its rows inherit correctly as they
 * are, and .ml-ftr__contacts > p below gives an added row the flex layout. */
/* The container list covers the OUTER wrappers as well (.ml-ftr, __body, __main-wrap, __main,
 * __grid), because a block can be added there too — before that, an added paragraph outside the
 * three columns came out plain white 16px while the identical block inside a column was
 * 14px/70 % (measured on the stress footer). Still :where(), still specificity zero. */
:where(.ml-ftr, .ml-ftr__body, .ml-ftr__main-wrap, .ml-ftr__main, .ml-ftr__grid, .ml-ftr__brand-col, .ml-ftr__socials, .ml-ftr__team-col, .ml-ftr__partners) > p {
	margin: 1.25rem 0 0;
	font-size: 0.875rem;
	line-height: 1.625;
	color: rgb(255 255 255 / 0.7);
}
:where(.ml-ftr, .ml-ftr__body, .ml-ftr__main-wrap, .ml-ftr__main, .ml-ftr__grid, .ml-ftr__brand-col, .ml-ftr__socials, .ml-ftr__team-col, .ml-ftr__partners) > :where(h1, h2, h3, h4, h5, h6) {
	margin: 0 0 1rem;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.2em;
	color: var(--wp--preset--color--brand-lime);
}
:where(.ml-ftr) :where(p, li) a {
	color: rgb(255 255 255 / 0.7);
	text-decoration: none;
	transition: color 0.2s;
}
:where(.ml-ftr) :where(p, li) a:hover {
	color: var(--wp--preset--color--brand-lime);
}
/* ── …and an ADDED item must not break the LAYOUT either (customer, 2026-08-11: "now if i add
 * more blocks the footer brokes") ───────────────────────────────────────────────────────────
 * Measured on a stress footer carrying one heading + paragraph inside EVERY container a
 * customer can insert into, at 1920/900/390:
 *   · a block dropped into .ml-ftr__grid became a GRID ITEM — 93px wide at 1920 (one of twelve
 *     columns) and it pushed Naiset onto its own row: the reported break;
 *   · a block dropped into .ml-ftr, .ml-ftr__body or .ml-ftr__main-wrap rendered x=0 w=1920,
 *     i.e. edge-to-edge with no gutter, because the 1640 rail lives on .ml-ftr__container.
 * Blocks added INSIDE the three designed columns or inside .ml-ftr__main were already correct
 * (x=140 w=515 / w=1640) — those paths are untouched here.
 *
 * The two rules below are placement-only, keyed on "not one of the designed children", so they
 * cannot reach the designed markup: the A/B against the untouched footer stays at 0 deltas. */
.ml-ftr__grid > :not(.ml-ftr__brand-col):not(.ml-ftr__team-col) {
	/* Full row, at every breakpoint (1 col mobile, 2 at 640, 12 at 1024) — an added block reads
	 * as a band under the columns instead of stealing a cell from them. */
	grid-column: 1 / -1;
}
:where(.ml-ftr, .ml-ftr__body, .ml-ftr__main-wrap) > :not(.ml-ftr__accent):not(.ml-ftr__body):not(.ml-ftr__top-strip):not(.ml-ftr__main-wrap):not(.ml-ftr__bottom):not(.ml-ftr__container) {
	/* The same rail .ml-ftr__container gives its own children, so added content lines up with
	 * the logo and the bottom bar instead of touching the viewport edge. */
	box-sizing: border-box;
	width: 100%;
	max-width: calc(1640px + 2rem);
	margin-inline: auto;
	padding-inline: 1rem;
}
@media (min-width: 640px) {
	:where(.ml-ftr, .ml-ftr__body, .ml-ftr__main-wrap) > :not(.ml-ftr__accent):not(.ml-ftr__body):not(.ml-ftr__top-strip):not(.ml-ftr__main-wrap):not(.ml-ftr__bottom):not(.ml-ftr__container) {
		max-width: calc(1640px + 3rem);
		padding-inline: 1.5rem;
	}
}
/* An EMPTY paragraph is ONE BLANK LINE — pressing Enter for space has to work (customer,
 * 2026-08-11: "the 'kirjoita valitaksesi lohkon' text is always there even if i press enter so
 * space wont work correctly"). Hiding empty blocks outright — the first answer to "the footer is
 * completely broken as i added empty blocks there" — took the customer's spacing gesture with it:
 * the canvas showed a stack of placeholder lines and the live footer showed nothing at all.
 *
 * An empty `<p></p>` has NO line box, so it collapses to 0 height on its own; `min-height` is
 * what turns each Enter into one predictable line of the footer's own text metrics (1.625 ×
 * 0.875rem ≈ 22.75px). `margin: 0` overrides the added-paragraph default's 1.25rem top so N
 * Enters cost N lines and nothing else. NB inside `.ml-ftr__grid` an empty line is still a
 * full-width grid ITEM (rule above), so it also collects the grid's 3rem gap — ~71px per Enter
 * there against ~23px inside a column, which is the only place the two disagree.
 *
 * An empty GROUP stays hidden: a container with nothing in it is not a spacing gesture, and
 * unhidden it eats a grid row for nothing. A Spacer/Separator is empty ON PURPOSE and neither
 * rule touches it, nor void elements, nor the design's own empty divs (.ml-ftr__accent is the
 * lime line, .ml-ftr__team-crest a crest chip) — that's why both rules list element types.
 *
 * These are the only added-block rules that are not :where()-zero; they cannot reach the design
 * by construction, since they need :empty and every designed p / heading / group in the footer
 * carries text or children. The specificity is deliberate — `.ml-ftr` + the `:is()` class +
 * `:empty` = 0,3,0 beats `.ml-ftr__contacts > p`, which would otherwise keep its flex row (and
 * `display: flex` on an empty box ignores min-height's blank line). */
.ml-ftr :is(p, h1, h2, h3, h4, h5, h6):empty {
	display: block;
	margin: 0;
	min-height: 1.625em;
}
.ml-ftr :is(.wp-block-group):empty {
	display: none;
}
/* …with ONE exception: a blank line cannot be a child of the column grid. Measured at 1600 — one
 * empty paragraph between .ml-ftr__brand-col and the team columns is a full-width grid item (the
 * placement rule above), and a full-width item between items necessarily ENDS the row: the grid
 * went 295.5px → 818px and Miehet/Naiset dropped under the brand column. That is the customer's
 * original "the footer brokes" symptom, so an empty line there stays out of layout; spacing
 * belongs inside a column or between the footer's bands, where it costs exactly its own line. */
.ml-ftr .ml-ftr__grid > :is(p, h1, h2, h3, h4, h5, h6):empty {
	display: none;
}

/* ── Every OTHER block type an editor can drop in the footer ─────────────────────────────────
 * The rules above dress paragraphs, headings, links and buttons. The rest arrived with core's
 * own light-background defaults, which read as damage on the teal (measured with one image,
 * columns, table, separator, spacer, quote and group added above "Seuraa meitä": the table drew
 * white 1px borders around white text, the quote came out indented + italic with no accent, the
 * separator was invisible, and an image had no size ceiling at all). All :where() = specificity
 * zero, so the designed footer markup keeps winning. */
:where(.ml-ftr) :where(figure, .wp-block-image) { margin-block: 1.25rem; }
:where(.ml-ftr) :where(figure, .wp-block-image) img { max-width: 100%; height: auto; border-radius: 0.5rem; }
:where(.ml-ftr) figcaption {
	margin-top: 0.5rem;
	font-size: 0.75rem;
	line-height: 1rem;
	color: rgb(255 255 255 / 0.55);
}
/* Lists: the design's own lime dot, the same one .ml-content uses, so an added list matches the
 * footer's team lists instead of core's browser bullets. */
:where(.ml-ftr) :where(ul, ol):not(.ml-ftr__team-list):not(.wp-block-social-links) {
	margin: 1rem 0;
	padding-left: 1.25rem;
	font-size: 0.875rem;
	line-height: 1.625;
	color: rgb(255 255 255 / 0.7);
}
:where(.ml-ftr) ul:not(.ml-ftr__team-list):not(.wp-block-social-links) { list-style: none; padding-left: 0; }
:where(.ml-ftr) ul:not(.ml-ftr__team-list):not(.wp-block-social-links) > li { position: relative; padding-left: 1.25rem; margin: 0.35rem 0; }
:where(.ml-ftr) ul:not(.ml-ftr__team-list):not(.wp-block-social-links) > li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.6em;
	width: 0.4rem;
	height: 0.4rem;
	border-radius: 50%;
	background: var(--wp--preset--color--brand-lime);
}
:where(.ml-ftr) ol:not(.ml-ftr__team-list) > li { margin: 0.35rem 0; }
:where(.ml-ftr) ol > li::marker { color: var(--wp--preset--color--brand-lime); font-weight: 800; }
/* Quote: the design's lime rule, in footer type sizes. */
:where(.ml-ftr) :where(blockquote, .wp-block-quote) {
	margin: 1.5rem 0;
	padding-left: 1rem;
	border-left: 3px solid var(--wp--preset--color--brand-lime);
	font-style: normal;
}
:where(.ml-ftr) :where(blockquote, .wp-block-quote) p { font-size: 1rem; line-height: 1.5; color: #fff; }
:where(.ml-ftr) :where(blockquote, .wp-block-quote) cite {
	display: block;
	margin-top: 0.5rem;
	font-style: normal;
	font-size: 0.8125rem;
	color: rgb(255 255 255 / 0.55);
}
/* Table: the footer has no light surface to draw on, so borders and header follow the same
 * 10 %-white language as the rest of the chrome (the bottom bar, the divider between columns). */
:where(.ml-ftr) .wp-block-table { margin: 1.25rem 0; overflow-x: auto; }
:where(.ml-ftr) .wp-block-table table {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.875rem;
	line-height: 1.25rem;
	color: rgb(255 255 255 / 0.8);
}
:where(.ml-ftr) .wp-block-table :where(th, td) {
	padding: 0.5rem 0.75rem;
	border: 0;
	border-bottom: 1px solid rgb(255 255 255 / 0.12);
	text-align: left;
}
:where(.ml-ftr) .wp-block-table th { color: var(--wp--preset--color--brand-lime); font-weight: 800; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.1em; }
/* Separator + horizontal rule: the footer's own hairline, not core's dark default. */
:where(.ml-ftr) :where(hr, .wp-block-separator) {
	margin: 1.5rem 0;
	border: 0;
	border-top: 1px solid rgb(255 255 255 / 0.12);
	background: none;
	opacity: 1;
}

/* An added BUTTON in the footer defaults to the design's lime pill rather than core's blue. */
:where(.ml-ftr) .wp-block-button:not([class*="is-style-"]) > .wp-block-button__link {
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	border-radius: 9999px;
}

/* Row-scoped, not only class-scoped: a bare paragraph the customer ADDS inside the contacts
 * group must lay out like its designed siblings (the modifier class only opts a row into a
 * glyph). Same declarations as .ml-ftr__contact-row above. */
.ml-ftr__contacts > p { display: flex; align-items: center; gap: 0.5rem; margin: 0; }

/* Editor-entered text must never clip: .ml-ftr__body{overflow:hidden} CUTS anything that can't
 * wrap (measured: a long e-mail address and an unbroken word both clipped at every viewport).
 * `anywhere` is a last resort — it changes nothing until a word can't fit its line — and it
 * also shrinks flex min-content, so the long e-mail wraps inside its icon row instead of
 * pushing past the card. Scoped to the brand column, the only free-text zone in the footer. */
.ml-ftr .ml-ftr__brand-col p,
.ml-ftr .ml-ftr__brand-col p a { overflow-wrap: anywhere; min-width: 0; }
.ml-ftr__contacts > p a { color: rgb(255 255 255 / 0.7); text-decoration: none; transition: color 0.2s; }
.ml-ftr__contacts > p a:hover { color: var(--wp--preset--color--brand-lime); }
.ml-ftr__contact-icon { color: var(--wp--preset--color--brand-lime); }

/* ── Paragraph icon styles (register_block_style, includes/block-styles.php) ──────────────────
 * "Ikoni: sähköposti/sijainti/…" in the block's Tyylit tab — how an editor puts an icon next to
 * text ANYWHERE, and what the footer's contact rows use (they were bespoke --mail/--pin
 * modifiers, i.e. a look only a developer could apply). The glyphs are the design's own lucide
 * outlines drawn as a CSS mask, so the icon needs no markup and inherits its surroundings:
 *   --ml-icon-color  (default currentColor)  --ml-icon-size (1em)  --ml-icon-gap (0.5em)
 * A host scope overrides those three to restore its own design metrics — see .ml-ftr__contacts. */
.is-style-ml-icon-mail::before,
.is-style-ml-icon-pin::before,
.is-style-ml-icon-phone::before,
.is-style-ml-icon-clock::before,
.is-style-ml-icon-link::before {
	content: '';
	display: inline-block;
	flex: none;
	width: var(--ml-icon-size, 1em);
	height: var(--ml-icon-size, 1em);
	/* Optical baseline alignment when the paragraph is ordinary inline text; a flex host
	   (the footer rows) ignores it and centres the item instead. */
	vertical-align: -0.15em;
	margin-right: var(--ml-icon-gap, 0.5em);
	background-color: var(--ml-icon-color, currentColor);
	-webkit-mask: var(--ml-icon) center / contain no-repeat;
	mask: var(--ml-icon) center / contain no-repeat;
}
.is-style-ml-icon-mail::before {
	--ml-icon: url("data:image/svg+xml,%3Csvg 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'%3E%3Crect width='20' height='16' x='2' y='4' rx='2'/%3E%3Cpath d='m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7'/%3E%3C/svg%3E");
}
.is-style-ml-icon-pin::before {
	--ml-icon: url("data:image/svg+xml,%3Csvg 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'%3E%3Cpath d='M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z'/%3E%3Ccircle cx='12' cy='10' r='3'/%3E%3C/svg%3E");
}
.is-style-ml-icon-phone::before {
	--ml-icon: url("data:image/svg+xml,%3Csvg 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'%3E%3Cpath d='M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z'/%3E%3C/svg%3E");
}
.is-style-ml-icon-clock::before {
	--ml-icon: url("data:image/svg+xml,%3Csvg 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'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E");
}
.is-style-ml-icon-link::before {
	--ml-icon: url("data:image/svg+xml,%3Csvg 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'%3E%3Cpath d='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'/%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'/%3E%3C/svg%3E");
}
/* The footer's contact rows are a flex row that supplies its own 0.5rem gap and draws a fixed
 * 16px lime glyph (design), so the three knobs are re-pinned here rather than inherited. */
.ml-ftr__contacts {
	--ml-icon-color: var(--wp--preset--color--brand-lime);
	--ml-icon-size: 1rem;
	--ml-icon-gap: 0;
}

.ml-ftr__contact-row a {
	color: rgb(255 255 255 / 0.7);
	text-decoration: none;
	transition: color 0.2s;
}

.ml-ftr__contact-row a:hover { color: var(--wp--preset--color--brand-lime); }

.ml-ftr .ml-ftr__socials { margin-top: 2rem; }

.ml-ftr__col-title {
	margin: 0;
	font-size: 0.75rem;
	/* text-xs pairs a fixed 16px; the inherited h2 ratio (1.25) gave 15px, and one px per
	 * column title was what still held the whole lower footer 1.4px above the design. */
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.2em;
	color: var(--wp--preset--color--brand-lime);
}

.ml-ftr__socials .ml-ftr__col-title { margin-bottom: 1rem; }

.ml-ftr__social-row { display: flex; align-items: center; gap: 0.75rem; }

.ml-ftr__social {
	width: 3rem;
	height: 3rem;
	border-radius: 9999px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	/* SiteFooter.tsx:140 `transition-all duration-300` — colour AND transform, not transform alone. */
	transition: background-color 0.3s, color 0.3s, transform 0.3s;
}

/* Hover-capable pointers only. On iOS Safari a tap leaves :hover latched on the element until the
 * next tap elsewhere, so the icon the visitor just tapped stayed white and lifted — the design's
 * effect is a pointer affordance, and a phone has no pointer (customer 2026-08-31: the footer
 * social hover "is different in safari, also check iphone"). :focus-visible below keeps the keyboard
 * path, which is what actually needs a visible state on touch devices.
 */
@media (hover: hover) and (pointer: fine) {
	.ml-ftr__social:hover {
		background: #fff;
		color: var(--wp--preset--color--brand-teal);
		transform: translateY(-2px);
	}
}

.ml-ftr__social:focus-visible {
	background: #fff;
	color: var(--wp--preset--color--brand-teal);
}

/* Same pinning as the core-block row below: the lucide glyphs are stroke-drawn, so the stroke is
 * what must not be able to inherit white. */
.ml-ftr__social svg {
	color: var(--wp--preset--color--brand-teal);
	stroke: var(--wp--preset--color--brand-teal);
}

/* The composable shell renders the socials as a native core/social-links block (user decision
 * 2026-08-11: the handles edit IN the footer like any Gutenberg content — the separate `some`
 * navigation was one indirection too many). These map core's markup onto the design's circles
 * above; specificity is deliberately three classes so core's per-service brand colors lose.
 * Recorded divergence: core's brand glyphs vs the design's lucide outlines. */
ul.wp-block-social-links.ml-ftr__social-row { margin: 0; padding: 0; gap: 0.75rem; }
/* `background: none` is load-bearing, not tidying: core paints the per-service BRAND colour on the
 * LI (`:where(.wp-block-social-links:not(.is-style-logos-only)) .wp-social-link-facebook{background:#0866ff}`),
 * and the lime circle is our ANCHOR — so the two are exactly the same size and the blue/magenta sat
 * hidden underneath until the anchor lifted 2px on hover and uncovered a coloured sliver along the
 * bottom edge. That sliver is what made the footer hover differ from the design (and read worse in
 * Safari, whose subpixel rounding showed more of it). */
.ml-ftr .ml-ftr__social-row .wp-block-social-link { margin: 0; border-radius: 9999px; background: none; }
.ml-ftr .ml-ftr__social-row .wp-block-social-link .wp-block-social-link-anchor {
	width: 3rem;
	height: 3rem;
	border-radius: 9999px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	/* SiteFooter.tsx:140 `transition-all duration-300` — colour AND transform, not transform alone. */
	transition: background-color 0.3s, color 0.3s, transform 0.3s;
	padding: 0;
}
/* Hover-capable pointers only — see the .ml-ftr__social note above (iOS Safari latches :hover). */
@media (hover: hover) and (pointer: fine) {
	.ml-ftr .ml-ftr__social-row .wp-block-social-link .wp-block-social-link-anchor:hover {
		background: #fff;
		color: var(--wp--preset--color--brand-teal);
		transform: translateY(-2px);
	}
}
.ml-ftr .ml-ftr__social-row .wp-block-social-link .wp-block-social-link-anchor:focus-visible {
	background: #fff;
	color: var(--wp--preset--color--brand-teal);
}
/* The glyph colour is pinned to the token on the SVG *and* its paths, not inherited through
 * `currentColor` (customer 2026-09-01: on iPhone/Safari the glyphs still came out white while the
 * circles stayed lime — i.e. the anchor's own `color` reached the box but not the glyph). Core's
 * own social-links CSS colours the glyph `#fff` for its per-service styling, so anything that
 * breaks the inheritance chain — a forced-dark pass, a Global Styles icon colour, a core version
 * whose rule beats ours on the svg rather than the anchor — lands back on white. An explicit fill
 * has no chain to break. `path` too: core's markup leaves fill unset on the path, so a stray
 * `fill` there would otherwise win over the svg's. */
.ml-ftr .ml-ftr__social-row .wp-block-social-link svg {
	width: 1.25rem;
	height: 1.25rem;
	color: var(--wp--preset--color--brand-teal);
	fill: var(--wp--preset--color--brand-teal);
}
.ml-ftr .ml-ftr__social-row .wp-block-social-link svg path {
	fill: var(--wp--preset--color--brand-teal);
}
/* Hover/focus flips the circle to white, so the glyph must follow — see the anchor rules above. */
@media (hover: hover) and (pointer: fine) {
	.ml-ftr .ml-ftr__social-row .wp-block-social-link .wp-block-social-link-anchor:hover svg,
	.ml-ftr .ml-ftr__social-row .wp-block-social-link .wp-block-social-link-anchor:hover svg path {
		fill: var(--wp--preset--color--brand-teal);
	}
}
/* Core's own li-hover is scale(1.1); the design's is translateY(-2px) on the anchor (above).
 * :focus-within too — core scales the LI on focus as well, which on a tap in iOS Safari stayed
 * scaled after the finger left. */
.ml-ftr .ml-ftr__social-row .wp-block-social-link:hover,
.ml-ftr .ml-ftr__social-row .wp-block-social-link:focus-within { transform: none; }

/* Team columns */
.ml-ftr__team-list {
	margin: 1rem 0 0;
	padding: 0;
	list-style: none;
	display: grid;
	grid-template-columns: 1fr;
	row-gap: 0.625rem;
	column-gap: 1.5rem;
}

@media (min-width: 640px) {
	.ml-ftr__team-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.ml-ftr__team {
	display: inline-flex;
	align-items: center;
	gap: 0.625rem;
	font-size: 0.875rem;
	line-height: 1.25rem; /* text-sm pairs 20px — see .ml-ftr__contacts */
	color: rgb(255 255 255 / 0.7);
	text-decoration: none;
	transition: color 0.2s;
}

.ml-ftr__team:hover { color: #fff; }

.ml-ftr__team-crest {
	width: 2.5rem;
	height: 2.5rem;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 9999px;
	background: rgb(255 255 255 / 0.95);
	flex-shrink: 0;
	overflow: hidden;
	transition: transform 0.2s;
}

.ml-ftr__team:hover .ml-ftr__team-crest { transform: scale(1.1); }

.ml-ftr__team-crest img { width: 2rem; height: 2rem; object-fit: contain; }

.ml-ftr__team-name {
	border-bottom: 1px solid transparent;
	transition: border-color 0.2s;
}

.ml-ftr__team:hover .ml-ftr__team-name { border-bottom-color: hsl(59 85% 47% / 0.6); }

/* Partners */
.ml-ftr .ml-ftr__partners {
	margin-top: 3.5rem;
	padding-top: 2.5rem;
	border-top: 1px solid rgb(255 255 255 / 0.1);
}

.ml-ftr__partners-title {
	margin: 0;
	font-size: 0.75rem;
	line-height: 1rem; /* text-xs pairs 16px; the inherited ratio gave 15.6px */
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.2em;
	color: rgb(255 255 255 / 0.6);
	text-align: center;
}

.ml-ftr__partners-row {
	margin-top: 1.5rem;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	column-gap: 2rem;
	row-gap: 0.75rem;
}

.ml-ftr__partner {
	font-size: 0.875rem;
	line-height: 1.25rem; /* text-sm pairs 20px — see .ml-ftr__contacts */
	font-weight: 800;
	letter-spacing: 0.025em;
	text-transform: uppercase;
	color: rgb(255 255 255 / 0.55);
	text-decoration: none;
	transition: color 0.2s;
}

.ml-ftr__partner:hover { color: var(--wp--preset--color--brand-lime); }

/* Bottom bar */
.ml-ftr__bottom {
	border-top: 1px solid rgb(255 255 255 / 0.1);
	background: hsl(182 100% 9% / 0.8);
}

.ml-ftr__bottom-row {
	padding-block: 1.5rem;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	font-size: 0.875rem;
	line-height: 1.25rem; /* text-sm pairs 20px — see .ml-ftr__contacts */
	color: rgb(255 255 255 / 0.7);
}

@media (min-width: 768px) {
	.ml-ftr__bottom-row { flex-direction: row; }
}

.ml-ftr__copy { margin: 0; }

.ml-ftr__legal {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	column-gap: 1.5rem;
	row-gap: 0.5rem;
}

.ml-ftr__legal a {
	color: rgb(255 255 255 / 0.7);
	text-decoration: none;
	transition: color 0.2s;
}

.ml-ftr__legal a:hover { color: var(--wp--preset--color--brand-lime); }

/* ══════════════════════════════════════════════════════════════════
 * Ad slots — TopBanner.tsx + ImageAdBanner.tsx ports
 * ══════════════════════════════════════════════════════════════════ */

/* Top banner strip (above the header) */
.ml-topbanner {
	position: relative;
	width: 100%;
	background: linear-gradient(to right, var(--wp--preset--color--brand-lime), #C5E860, var(--wp--preset--color--brand-teal));
}

.ml-topbanner__link {
	display: block;
	width: 100%;
	text-decoration: none;
}

.ml-topbanner__link:focus-visible {
	outline: none;
	box-shadow: inset 0 0 0 2px var(--wp--preset--color--brand-teal);
}

/* The creative sizes the strip: width:100% + height:auto, so it renders edge-to-edge at its
 * own aspect ratio (customer's call, 2026-07-29 — the letterboxed fixed slot "doesn't look
 * good"). Consequence, recorded on purpose: the strip is only the design's 108px at exactly
 * 1920, and at narrower widths it is shorter, so everything below the header sits that much
 * higher than the design (~27px at 1440). The design's fixed h-[128px]/md:h-[92px]/lg:h-[108px]
 * still governs the PLACEHOLDER (.ml-topbanner__ph), which is editor-only now that an empty
 * ad slot renders nothing on the live page. */
.ml-topbanner__img { display: block; width: 100%; height: auto; }

.ml-topbanner__ph {
	color: var(--wp--preset--color--brand-teal);
	font-weight: 800;
	text-transform: uppercase;
	align-items: center;
	justify-content: center;
}

.ml-topbanner__ph--mobile {
	display: flex;
	height: 128px;
	font-size: 11px;
	letter-spacing: 0.28em;
}

.ml-topbanner__ph--desktop { display: none; }

@media (min-width: 768px) {
	.ml-topbanner__ph--mobile { display: none; }
	.ml-topbanner__ph--desktop {
		display: flex;
		height: 92px;
		font-size: 0.875rem;
		letter-spacing: 0.32em;
	}
}

@media (min-width: 1024px) {
	.ml-topbanner__ph--desktop { height: 108px; font-size: 1rem; }
}

.ml-topbanner__close {
	position: absolute;
	/* ONE position at every width (customer 2026-07-30: "make them stay at one position … in
	   mobile they are good now") — both pills in the BOTTOM-RIGHT corner, side by side, 12px in,
	   no breakpoint switch, so resizing never moves them.
	   Why side by side and not stacked: the strip is sized by its creative, and at 768–1024 the
	   1920×108 desktop creative scales to only 43–58px tall. A stacked pair needs ≥54px, so it
	   overlapped there (close 3–31, pause 14–40 at 768). Side by side needs 56×26 and fits every
	   strip height. The design centres a SINGLE pill; ours must also seat the pause control
	   (WCAG 2.2.2), so the arrangement of two is ours either way. */
	bottom: 3px;
	right: 0.75rem;
	z-index: 10;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.375rem;
	border-radius: 9999px;
	background: rgb(255 255 255 / 0.25);
	-webkit-backdrop-filter: blur(4px);
	backdrop-filter: blur(4px);
	color: hsl(182 100% 9% / 0.9);
	border: 0;
	cursor: pointer;
	transition: background-color 0.2s, color 0.2s;
}

.ml-topbanner__close:hover {
	background: rgb(255 255 255 / 0.5);
	color: var(--wp--preset--color--brand-teal);
}

/* Pause/play toggle for the multi-slide rotator (WCAG 2.2.2) — mirrors the close pill,
   sits just left of it. Shows the pause glyph while playing, play glyph when paused. */
.ml-topbanner__toggle {
	position: absolute;
	/* Immediately LEFT of the close pill, same bottom edge — one row, one position, all widths. */
	bottom: 3px;
	right: 2.75rem;
	z-index: 10;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.375rem;
	border-radius: 9999px;
	background: rgb(255 255 255 / 0.25);
	-webkit-backdrop-filter: blur(4px);
	backdrop-filter: blur(4px);
	color: hsl(182 100% 9% / 0.9);
	border: 0;
	cursor: pointer;
	transition: background-color 0.2s, color 0.2s;
}
.ml-topbanner__toggle:hover {
	background: rgb(255 255 255 / 0.5);
	color: var(--wp--preset--color--brand-teal);
}
/* JS hides the control via the hidden attribute under prefers-reduced-motion
 * (no autoplay → nothing to pause); the author display rule above would beat
 * the UA [hidden] rule without this. */
.ml-topbanner__toggle[hidden] { display: none; }
.ml-topbanner__toggle-pause,
.ml-topbanner__toggle-play { display: inline-flex; align-items: center; }
.ml-topbanner__toggle .ml-topbanner__toggle-play { display: none; }
.ml-topbanner__toggle.is-paused .ml-topbanner__toggle-pause { display: none; }
.ml-topbanner__toggle.is-paused .ml-topbanner__toggle-play { display: inline-flex; }

/* Mobile: the ~65px strip is small and real creatives put their CTA at the right —
 * two vertically-centered pills side by side covered it. Stack them into the top/bottom
 * right CORNERS at the 24px WCAG 2.5.8 minimum target so the creative stays visible. */
/* Phones: shrink both pills to the WCAG 2.5.8 minimum target (24px) so they take less of a
   65px strip — position is identical at every width, only the SIZE steps down here. */
@media (max-width: 767px) {
	.ml-topbanner__close,
	.ml-topbanner__toggle {
		padding: 5px; /* 14px glyph + 2×5px = 24px */
	}
	.ml-topbanner__close svg,
	.ml-topbanner__toggle svg { width: 14px; height: 14px; }
}

/* Top-banner crossfade rotator (multi-slide, lentopallo.fi-style) */
.ml-topbanner__viewport { position: relative; width: 100%; }

.ml-topbanner--slider .ml-topbanner__slide {
	position: absolute;
	inset: 0;
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.6s ease;
}
/* The first slide stays in normal flow so the strip keeps a stable height
 * (all creatives share the strip aspect); its visibility is opacity-only. */
.ml-topbanner--slider .ml-topbanner__slide:first-child { position: relative; }
.ml-topbanner--slider .ml-topbanner__slide.is-active {
	opacity: 1;
	pointer-events: auto;
	z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
	.ml-topbanner--slider .ml-topbanner__slide { transition: none; }
}

/* In-page ad slot */
.ml-ad {
	position: relative;
	display: block;
	width: 100%;
	overflow: hidden;
	border-radius: 0.75rem;
	text-decoration: none;
}

.ml-ad:focus-visible {
	outline: none;
	box-shadow: 0 0 0 2px var(--wp--preset--color--brand-lime);
}

.ml-ad__img {
	display: block;
	width: 100%;
	height: auto;
	object-fit: contain;
	transition: transform 0.5s;
}

.ml-ad:hover .ml-ad__img { transform: scale(1.02); }

/* In-page ad slot as a multi-creative crossfade rotator (customer 2026-08-20 — same
 * behaviour the top banner already had). Mirrors .ml-topbanner--slider exactly: every
 * slide is absolutely stacked EXCEPT the first, which stays in flow so the slot keeps a
 * stable height and nothing below it shifts as the creatives rotate. Emitted only for
 * two or more creatives — a single one is still a plain .ml-ad with no wrapper. */
.ml-adslider { position: relative; width: 100%; }
.ml-adslider .ml-ad__slide {
	position: absolute;
	inset: 0;
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.6s ease;
}
.ml-adslider .ml-ad__slide:first-child { position: relative; }
.ml-adslider .ml-ad__slide.is-active {
	opacity: 1;
	pointer-events: auto;
	z-index: 1;
}
/* Stacked slides are sized by the in-flow first one; make their creative fill that box
 * so a slightly different intrinsic ratio cannot make one slide overflow the slot. */
.ml-adslider .ml-ad__slide:not(:first-child) .ml-ad__img { height: 100%; object-fit: contain; }

/* Pause/play (WCAG 2.2.2). Bottom-right INSIDE the slot — unlike the top banner there is
 * no close pill to sit beside, so it takes the corner itself. */
.ml-adslider__toggle {
	position: absolute;
	bottom: 0.5rem;
	right: 0.5rem;
	z-index: 10;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.375rem;
	border-radius: 9999px;
	background: rgb(255 255 255 / 0.55);
	-webkit-backdrop-filter: blur(4px);
	backdrop-filter: blur(4px);
	color: hsl(182 100% 9% / 0.9);
	border: 0;
	cursor: pointer;
	transition: background-color 0.2s, color 0.2s;
}
.ml-adslider__toggle:hover {
	background: rgb(255 255 255 / 0.85);
	color: var(--wp--preset--color--brand-teal);
}
/* JS hides the control via the hidden attribute under prefers-reduced-motion
 * (no autoplay → nothing to pause); the author display rule above would beat
 * the UA [hidden] rule without this. */
.ml-adslider__toggle[hidden] { display: none; }
.ml-adslider__toggle-pause,
.ml-adslider__toggle-play { display: inline-flex; align-items: center; }
.ml-adslider__toggle .ml-adslider__toggle-play { display: none; }
.ml-adslider__toggle.is-paused .ml-adslider__toggle-pause { display: none; }
.ml-adslider__toggle.is-paused .ml-adslider__toggle-play { display: inline-flex; }
.ml-adslider__toggle svg { width: 16px; height: 16px; }

@media (prefers-reduced-motion: reduce) {
	.ml-adslider .ml-ad__slide { transition: none; }
}

/* Optional mobile creative (<picture> swap). When a mobile creative is set its
 * aspect ratio usually differs from the wide desktop one; drop the desktop
 * width/height-derived ratio so the active source renders at its own ratio
 * (a taller, readable mobile banner instead of a 40px sliver). */
.ml-ad picture { display: block; }
.ml-ad--has-mobile .ml-ad__img { aspect-ratio: auto; }

.ml-ad__ph {
	position: relative;
	display: flex;
	width: 100%;
	align-items: center;
	justify-content: center;
	background: var(--wp--preset--color--brand-teal);
	border: 1px solid hsl(59 85% 47% / 0.3);
	border-radius: 0.75rem;
}

.ml-ad--leaderboard .ml-ad__ph { height: 90px; }
.ml-ad--billboard .ml-ad__ph { height: 180px; }

@media (min-width: 768px) {
	.ml-ad--leaderboard .ml-ad__ph { height: 120px; }
	.ml-ad--billboard .ml-ad__ph { height: 240px; }
}

@media (min-width: 1024px) {
	.ml-ad--leaderboard .ml-ad__ph { height: 140px; }
	.ml-ad--billboard .ml-ad__ph { height: 320px; }
}

.ml-ad__label {
	color: var(--wp--preset--color--brand-lime);
	font-size: 0.875rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.28em;
}

@media (min-width: 768px) {
	.ml-ad__label { font-size: 1rem; }
}

.ml-ad__tick {
	position: absolute;
	background: hsl(59 85% 47% / 0.7);
}

.ml-ad__tick--tl-h { top: 0.5rem; left: 0.5rem; width: 1rem; height: 2px; }
.ml-ad__tick--tl-v { top: 0.5rem; left: 0.5rem; width: 2px; height: 1rem; }
.ml-ad__tick--tr-h { top: 0.5rem; right: 0.5rem; width: 1rem; height: 2px; }
.ml-ad__tick--tr-v { top: 0.5rem; right: 0.5rem; width: 2px; height: 1rem; }
.ml-ad__tick--bl-h { bottom: 0.5rem; left: 0.5rem; width: 1rem; height: 2px; }
.ml-ad__tick--bl-v { bottom: 0.5rem; left: 0.5rem; width: 2px; height: 1rem; }
.ml-ad__tick--br-h { bottom: 0.5rem; right: 0.5rem; width: 1rem; height: 2px; }
.ml-ad__tick--br-v { bottom: 0.5rem; right: 0.5rem; width: 2px; height: 1rem; }

/* ══════════════════════════════════════════════════════════════════
 * Etusivu sections — Home.tsx ports
 * ══════════════════════════════════════════════════════════════════ */

/* Section header (SectionHeader.tsx + Home SectionHead wrapper) */
.ml-secthead {
	display: flex;
	align-items: flex-end;
	gap: 1rem;
	margin-bottom: 1.25rem;
}

@media (min-width: 768px) {
	.ml-secthead { margin-bottom: 1.5rem; }
}

/* SectionHeader.tsx eyebrow — text-xs md:text-sm (paired 16px / 20px line-heights). */
.ml-secthead__eyebrow {
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.2em;
	color: hsl(182 100% 9% / 0.7);
}

@media (min-width: 768px) {
	.ml-secthead__eyebrow { font-size: 0.875rem; line-height: 1.25rem; }
}

/* SectionHeader.tsx h2 — text-xl md:text-2xl (Tailwind leading 1.75rem / 2rem). */
.ml-secthead__title {
	margin: 0.25rem 0 0;
	font-size: 1.25rem;
	line-height: 1.75rem;
	font-weight: 800;
	letter-spacing: -0.025em;
	text-transform: uppercase;
	color: var(--wp--preset--color--foreground);
}

@media (min-width: 768px) {
	.ml-secthead__title { font-size: 1.5rem; line-height: 2rem; }
}

.ml-secthead__rule {
	flex: 1;
	height: 1px;
	background: var(--wp--preset--color--border);
	margin-bottom: 0.35rem;
}

/* The divider line as a pseudo-element for section headers that have NO explicit
 * .ml-secthead__rule child. Block-authored headers (patterns / editable content)
 * drop the empty rule <div> — as an empty core/group it showed in the editor as a
 * selectable but useless box — and get the line from here instead. SSR headers
 * (club-strip etc.) keep their real rule <div>, so :not(:has()) excludes them and
 * the line is never drawn twice. */
.ml-secthead:not(:has(.ml-secthead__rule))::after {
	content: "";
	flex: 1;
	height: 1px;
	background: var(--wp--preset--color--border);
	margin-bottom: 0.35rem;
}

/* Pill CTA buttons (BrandBand / NewsGrid) — modifier on the OUTER
 * wp-block-button div (canonical-patterns.md § Buttons), arrow via
 * Lucide arrow-right mask, never a Unicode arrow. */
/* The three pill CTAs are also REGISTERED block styles (includes/block-styles.php) so the
 * customer picks them from the block's Tyylit tab; `ml-cta*` stays as the class the patterns
 * already carry. Same rules for both spellings — is-style-* is only another name for the look. */
.wp-block-button.is-style-ml-cta-lime,
.wp-block-button.is-style-ml-cta-outline-lime,
.wp-block-button.is-style-ml-cta-teal { /* marker: inherits the .ml-cta geometry below */ }

.wp-block-button.ml-cta > .wp-block-button__link,
.wp-block-button.is-style-ml-cta-lime > .wp-block-button__link,
.wp-block-button.is-style-ml-cta-outline-lime > .wp-block-button__link,
.wp-block-button.is-style-ml-cta-teal > .wp-block-button__link {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.625rem 1.25rem;
	border-radius: 9999px;
	border: 2px solid transparent;
	font-size: 0.75rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	/* text-xs pairs a FIXED 1rem line-height (12px/16px) — a 1.25 ratio gives 15px
	 * and every pill's label sits 0.5px high. Tailwind's text-* sizes each ship
	 * their own line-height; never re-derive one as a ratio. (.ml-cta-outline
	 * below already had the correct 1rem — this rule was the outlier.) */
	line-height: 1rem;
	/* core sets word-break:break-word on __link → flex min-content = 1 char
	 * and the pill collapses to a vertical letter column. word-break:normal +
	 * overflow-wrap:anywhere means a normal (multi-word) label wraps at spaces
	 * and only an over-long token breaks — a long custom label wraps to a taller
	 * pill instead of overflowing the section. (Was white-space:nowrap = never
	 * wrapped.) max-width:100% lets it shrink to the CTA row so wrapping engages. */
	word-break: normal;
	white-space: normal;
	overflow-wrap: anywhere;
	text-align: center;
	max-width: 100%;
	transition: color 0.15s, background-color 0.15s, border-color 0.15s;
}

.wp-block-button.ml-cta > .wp-block-button__link::after,
.wp-block-button.is-style-ml-cta-lime > .wp-block-button__link::after,
.wp-block-button.is-style-ml-cta-outline-lime > .wp-block-button__link::after,
.wp-block-button.is-style-ml-cta-teal > .wp-block-button__link::after {
	content: "";
	width: 1rem;
	height: 1rem;
	flex-shrink: 0;
	background: currentColor;
	-webkit-mask: 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="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>') no-repeat center / contain;
	mask: 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="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>') no-repeat center / contain;
	transition: transform 0.15s;
}

.wp-block-button.ml-cta > .wp-block-button__link:hover::after,
.wp-block-button.is-style-ml-cta-lime > .wp-block-button__link:hover::after,
.wp-block-button.is-style-ml-cta-outline-lime > .wp-block-button__link:hover::after,
.wp-block-button.is-style-ml-cta-teal > .wp-block-button__link:hover::after {
	transform: translateX(0.125rem);
}

.wp-block-button.ml-cta--lime > .wp-block-button__link,
.wp-block-button.is-style-ml-cta-lime > .wp-block-button__link {
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	border-color: var(--wp--preset--color--brand-lime);
}

/* !important: the global .wp-block-button__link:hover (lime/teal) rule
 * above also carries !important — this pill inverts to teal/lime. */
.wp-block-button.ml-cta--lime > .wp-block-button__link:hover,
.wp-block-button.is-style-ml-cta-lime > .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--brand-teal) !important;
	color: var(--wp--preset--color--brand-lime) !important;
}

.wp-block-button.ml-cta--outline-lime > .wp-block-button__link,
.wp-block-button.is-style-ml-cta-outline-lime > .wp-block-button__link {
	background: transparent;
	color: var(--wp--preset--color--brand-lime);
	border-color: var(--wp--preset--color--brand-lime);
}

.wp-block-button.ml-cta--outline-lime > .wp-block-button__link:hover,
.wp-block-button.is-style-ml-cta-outline-lime > .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--brand-lime) !important;
	color: var(--wp--preset--color--brand-teal) !important;
}

/* Teal pill — the inverse of the lime one, for lime/light bands (Button.tsx `default`:
   bg-primary/text-primary-foreground). Registered-style only; no pattern uses a class form. */
.wp-block-button.is-style-ml-cta-teal > .wp-block-button__link {
	background: var(--wp--preset--color--brand-teal);
	color: #fff;
	border-color: var(--wp--preset--color--brand-teal);
}
.wp-block-button.is-style-ml-cta-teal > .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--brand-lime) !important;
	color: var(--wp--preset--color--brand-teal) !important;
	border-color: var(--wp--preset--color--brand-lime);
}

/* Brand band */
.ml-brandband {
	position: relative;
	overflow: hidden;
	margin-top: 3rem;
	border-radius: 1rem;
	color: #fff;
	background: linear-gradient(to bottom right, var(--wp--preset--color--brand-teal), #0a3b3a, var(--wp--preset--color--brand-teal));
}

.ml-brandband::before,
.ml-brandband::after {
	content: "";
	position: absolute;
	border-radius: 50%;
	filter: blur(64px);
	pointer-events: none;
}

.ml-brandband::after {
	right: -6rem;
	bottom: -6rem;
	width: 460px;
	height: 460px;
	background: hsl(182 100% 9% / 0.3);
}

.ml-brandband::before {
	left: -5rem;
	top: -6rem;
	width: 360px;
	height: 360px;
	background: hsl(182 100% 9% / 0.2);
}

@media (min-width: 768px) {
	.ml-brandband {
		margin-top: 4rem;
		background: linear-gradient(to bottom right, var(--wp--preset--color--brand-teal), var(--wp--preset--color--brand-teal), #0a3b3a);
	}
	.ml-brandband::after { background: hsl(59 85% 47% / 0.2); }
	.ml-brandband::before { background: hsl(59 85% 47% / 0.1); }
}

.ml-brandband__grid {
	position: relative;
	/* Content must paint ABOVE the decorative ::before/::after blur circles.
	 * In source/Home.tsx the blur <div>s sit before this relative grid in the
	 * DOM, so the grid paints over them; here they are ::before/::after and
	 * ::after (last child, z-index auto) would otherwise paint OVER the buttons,
	 * tinting the lime CTAs with the mobile teal blur (olive on mobile; on
	 * desktop the blur is lime so the bug is invisible). z-index restores order. */
	z-index: 1;
	display: grid;
	grid-template-columns: 1fr;
	gap: 1.5rem;
	align-items: center;
	padding: 1.5rem;
}

@media (min-width: 768px) {
	.ml-brandband__grid {
		grid-template-columns: 1.4fr 1fr;
		gap: 2.5rem;
		padding: 2.5rem;
	}
}

/* Long unbroken text (a mashed key, a pasted URL) typed into an etusivu section
 * title/eyebrow must WRAP, not overflow the section and break the layout — the
 * .ml-content overflow-wrap net does not reach these home-section headings.
 * `anywhere` also shrinks min-content so the brand-band grid's fr text column
 * can narrow; min-width:0 is the belt-and-suspenders (an fr grid item defaults
 * to min-width:auto = its content width, which a long token would force wide). */
.ml-brandband__eyebrow,
.ml-brandband__title,
.ml-brandband__text,
.ml-secthead__eyebrow,
.ml-secthead__title {
	overflow-wrap: anywhere;
}
.ml-brandband__grid > * { min-width: 0; }

/* ── Customer-added blocks can't break the band ──────────────────────────────
 * (tasklist 2026-08-13: "adding more blocks to the sisältölaatikko breaks it".
 * The Enter-split half of that report is closed editor-side — editor-chrome-
 * enter-guard.js now loads in the page editor too. These rules are the layout
 * net for the blocks that DO get added.)
 * 1) A block added at the GRID level goes FULL-ROW (below the content) instead
 *    of dropping into the design's side column. The rule keys on ROLE, not on
 *    child index: the two designed children are the first one (the text column)
 *    and the logo column, and everything else is by definition an addition.
 *    An index rule can't tell them apart — the front-page band has two designed
 *    children but the Sisältölaatikko pattern has ONE, so `nth-child(n+3)` left
 *    the pattern's first addition sitting in a 530px right-hand column
 *    (measured) instead of under the text.
 * 2) The logo column keeps the LAST column explicitly, so it stays put even when
 *    an addition lands before it and shifts its index.
 * 3) A block added at the BAND level (sibling of the grid) inherits the band's
 *    padding so it doesn't sit flush against the rounded, overflow-clipped edge.
 * Both designed structures render exactly as before: nothing here matches until
 * the customer adds something (verified by A/B measurement of the front-page
 * band with these rules neutralised — identical geometry). */
.ml-brandband__grid > :not(:first-child):not(.ml-brandband__logos) { grid-column: 1 / -1; }
.ml-brandband__grid > .ml-brandband__logos { grid-column: -2 / -1; }
/* max-width mirrors the band's own lead text (p.ml-brandband__text, max-w-xl 36rem) — without
 * it a customer paragraph at band level ran the full 1392px band (measured 2026-08-14 on the
 * gap sweep). Content-box here (no box-sizing reset outside .ml-content), so the cap bounds
 * the TEXT and the padding rides outside it — the text still aligns with the band's own copy. */
.ml-brandband > :not(.ml-brandband__grid) { margin: 0 0 1rem; padding: 0 1.5rem; max-width: 36rem; }
.ml-brandband > :first-child:not(.ml-brandband__grid) { padding-top: 1.5rem; }
.ml-brandband > :last-child:not(.ml-brandband__grid) { padding-bottom: 1.5rem; margin-bottom: 0; }

@media (min-width: 768px) {
	.ml-brandband > :not(.ml-brandband__grid) { padding-left: 2.5rem; padding-right: 2.5rem; }
	.ml-brandband > :first-child:not(.ml-brandband__grid) { padding-top: 2.5rem; }
	.ml-brandband > :last-child:not(.ml-brandband__grid) { padding-bottom: 2.5rem; }
}

/* The eyebrow / headline / lead-text typography lives in § BRAND BAND TEXT IS
 * CONTEXT-PROOF near the END of this file, not here. It has to be authored after
 * the `.ml-content` and `.ml-article__body` text nets: those match on
 * `.ml-article__body p` (0,2,1) / `.ml-content p` (0,1,1) and would otherwise
 * repaint the band's white-on-teal copy in body-text teal — invisible on the
 * band's own gradient (measured on the customer's article, 2026-08-13). */

.ml-brandband__ctas {
	margin-top: 1.25rem;
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
}

/* Let a CTA pill shrink to the row width so a long label wraps inside it
 * instead of forcing the button (and the brand band) wider than the column. */
.ml-brandband__ctas > .wp-block-button { max-width: 100%; min-width: 0; }

.ml-brandband__logos {
	display: flex;
	justify-content: flex-start;
	align-items: center;
	gap: 1rem;
}

.ml-brandband__logos .wp-block-image { margin: 0; }
.ml-brandband__logos .wp-block-image img { height: 1.75rem; width: auto; }

/* The Sisältölaatikko pattern ships this slot with one UNCONFIGURED core/image (no stock
 * file — the customer picks their own; the pattern comment has the story). Published
 * never-configured it must render as if absent: the src-less <img> would otherwise paint
 * a 1.75rem ghost box (the logo-height rule above), and on mobile's one-column grid even
 * an empty slot cost one 1.5rem grid gap of dead space under the buttons (measured 24px
 * at 390). Hide the src-less image, and the whole slot while nothing in it would render —
 * an image WITH a file, or any real block the customer parked there, keeps it alive.
 * The slot hider is FRONT-ONLY (`body:not(.editor-styles-wrapper)` — the canvas body
 * carries that class): in the canvas the unconfigured image renders core's "choose image"
 * tile, which is none of the listed elements, and hiding the slot there would hide the
 * very control the customer fills it with. */
.ml-brandband__logos img:not([src]) { display: none; }
body:not(.editor-styles-wrapper) .ml-brandband__logos:not(:has(img[src], video, iframe, p:not(:empty), h1, h2, h3, h4, h5, h6, li, .wp-block-button)) { display: none; }

@media (min-width: 768px) {
	.ml-brandband__logos {
		flex-direction: column;
		align-items: flex-end;
		justify-content: center;
		gap: 1.5rem;
	}
	/* The design nests TWO flex wrappers here (Home.tsx:377-380): the outer column is
	 * `md:items-end`, the inner `md:items-start`. Net effect — the wordmarks share ONE
	 * left edge inside a 160px box (lippu.fi's own md:max-w) that is itself right-aligned
	 * in the column. Flattened to a single `align-items:flex-end` column, WP right-aligned
	 * each logo instead, so the narrower Ruutu mark sat 20px right of lippu.fi's edge.
	 * Giving both figures the inner box's width reproduces the two-level alignment. */
	.ml-brandband__logos .wp-block-image { width: 160px; }
	.ml-brandband__logos .wp-block-image img { height: auto; width: 100%; }
	.ml-brandband__logos .ml-logo-lippufi img { max-width: 160px; }
	.ml-brandband__logos .ml-logo-ruutu img { max-width: 140px; }
}

/* Both partner wordmarks use the design's purpose-made WHITE assets
 * (lippufi-logo-white.svg — white text + a yellow "+" — and ruutu-logo-white.png),
 * self-hosted in the theme. No brightness(0) invert(1) filter: it flattens
 * lippu.fi's yellow "+" to white and jaggs the anti-aliasing. */

/* Club logo strip */
.ml-clubstrip { margin-top: 2rem; }

@media (min-width: 768px) {
	.ml-clubstrip { margin-top: 2.5rem; }
}

.ml-clubstrip__cols {
	display: grid;
	grid-template-columns: 1fr;
	gap: 1.25rem;
}
/* Each column is a grid item; its default min-width:auto lets it grow to the intrinsic
   width of the inner club row (~884px), overflowing the 1fr track so the row's own
   overflow-x:auto has nothing to scroll (the "oma joukkueesi scroll won't work" bug on
   mobile). min-width:0 pins the item to the track width → the row overflows and swipes. */
.ml-clubstrip__cols > div { min-width: 0; }

@media (min-width: 768px) {
	.ml-clubstrip__cols { grid-template-columns: 1fr 1fr; gap: 2rem; }
}

.ml-clubstrip__label {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	margin-bottom: 0.625rem;
}

.ml-clubstrip__label h3 {
	margin: 0;
	font-size: 0.875rem;
	/* Home.tsx ClubRow `text-sm md:text-base`: both sizes pair a FIXED line-height
	 * (20px / 24px). Without them the theme.json h3 ratio (1.3) applied — 18.2px /
	 * 20.8px — so both league labels sat short of the design at every width. */
	line-height: 1.25rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	color: var(--wp--preset--color--brand-teal);
}

.ml-clubstrip__bar {
	width: 0.25rem;
	height: 1.5rem;
	background: var(--wp--preset--color--brand-lime);
}

@media (min-width: 768px) {
	.ml-clubstrip__label { margin-bottom: 0.75rem; }
	.ml-clubstrip__label h3 { font-size: 1rem; line-height: 1.5rem; }
}

.ml-clubstrip__scroll { position: relative; margin-right: -1rem; }

.ml-clubstrip__row {
	display: flex;
	gap: 0.5rem;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	padding: 0 2rem 0.25rem 0;
	scrollbar-width: none;
}

.ml-clubstrip__row::-webkit-scrollbar { display: none; }

.ml-clubstrip__tile {
	flex-shrink: 0;
	scroll-snap-align: start;
	width: 4rem;
	height: 4rem;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0.375rem;
	border-radius: 0.75rem;
	background: #fff;
	border: 1px solid hsl(182 100% 9% / 0.1);
	transition: border-color 0.3s, box-shadow 0.3s, transform 0.3s;
}

.ml-clubstrip__tile img {
	max-width: 100%;
	max-height: 100%;
	object-fit: contain;
	transition: transform 0.3s;
}

.ml-clubstrip__fade {
	pointer-events: none;
	position: absolute;
	top: 0;
	right: 0;
	height: 100%;
	width: 3rem;
	background: linear-gradient(to left, var(--wp--preset--color--background), transparent);
}

@media (min-width: 640px) {
	.ml-clubstrip__scroll { margin-right: -1.5rem; }
}

@media (min-width: 768px) {
	.ml-clubstrip__scroll { margin-right: 0; }
	.ml-clubstrip__fade { display: none; }
	.ml-clubstrip__row {
		display: grid;
		grid-template-columns: repeat(8, 1fr);
		gap: 0.375rem;
		overflow: visible;
		padding: 0;
	}
	.ml-clubstrip__tile { width: auto; height: auto; aspect-ratio: 1; }
	.ml-clubstrip__tile:hover {
		border-color: var(--wp--preset--color--brand-lime);
		box-shadow: 0 10px 15px -3px hsl(59 85% 47% / 0.1), 0 4px 6px -4px hsl(59 85% 47% / 0.1);
		transform: translateY(-2px);
	}
	.ml-clubstrip__tile:hover img { transform: scale(1.1); }
}

@media (min-width: 1024px) {
	.ml-clubstrip__row { grid-template-columns: repeat(10, 1fr); }
}

/* Partners logo grid */
.ml-partners { margin-top: 3rem; }

@media (min-width: 768px) {
	.ml-partners { margin-top: 4rem; }
}

/* minmax(0, 1fr), never bare 1fr (= minmax(auto, 1fr)), and min-width: 0 on the tiles — the
 * design's grid-cols-N compiles to exactly this. With the auto minimum, a track's floor is the
 * tile's min-content, and for a flex tile whose only child is an <img> with a PERCENTAGE
 * max-width WebKit resolves that contribution from the image's INTRINSIC width (300 px medium /
 * 390 px srcset pick), not the tile: measured at 390 px two 246 px tracks in a 358 px grid, the
 * second column past the viewport and the grid's rows taller than the container, so the
 * separator + heading below painted over the last card (customer 2026-08-27; Blink and Gecko
 * compute the same markup correctly, which is why it looked fine in Chrome). */
.ml-partners__grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.75rem;
}

.ml-partners__grid > .wp-block-image {
	margin: 0;
	min-width: 0;
	/* border-box is LOAD-BEARING, not tidiness: the theme has no global box-sizing
	 * reset and this grid lives outside the scoped ones (.ml-content etc.), so as
	 * content-box `aspect-ratio: 3/2` sizes the CONTENT box and the padding+border
	 * are added on top — 124/1.5 + 32 + 2 = 116.75px tall where the design (Tailwind
	 * Preflight = border-box) is 158/1.5 = 105.4. Measured design 158×105 vs WP
	 * 158×117 before this line. Same trap as .ml-container in CLAUDE.md. */
	box-sizing: border-box;
	aspect-ratio: 3 / 2;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 1rem;
	background: #fff;
	border-radius: 0.375rem;
	border: 1px solid hsl(182 100% 9% / 0.1);
	transition: border-color 0.15s;
}

.ml-partners__grid > .wp-block-image:hover {
	border-color: var(--wp--preset--color--brand-lime);
}

/* The logo is wrapped in an <a> to the partner's site (customer 2026-08-20). The figure is the
 * flex centerer, so the link has to become the full-size flex item or the logo would sit at the
 * link's intrinsic size and stop being clickable across the whole tile. Same rule the
 * /yhteistyokumppanit/ .ml-logogrid variant already carries. */
.ml-partners__grid > .wp-block-image a {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100%;
}

.ml-partners__grid > .wp-block-image img {
	max-width: 100%;
	max-height: 100%;
	width: auto;
	height: auto;
	object-fit: contain;
}

@media (min-width: 640px) {
	.ml-partners__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 768px) {
	.ml-partners__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; }
}

@media (min-width: 1024px) {
	.ml-partners__grid { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}

/* Homepage section spacing wrapper (design mt-12 md:mt-16 ad slots) */
.ml-sect-gap { margin-top: 3rem; }

@media (min-width: 768px) {
	.ml-sect-gap { margin-top: 4rem; }
}

/* An ad-slot wrapper whose ad-banner block renders nothing (no creative set →
 * hidden on the front end) collapses entirely, so no empty gap is left behind. */
.ml-sect-gap:empty,
.ml-sect-gap-sm:empty { display: none; margin: 0; }

/* ══════════════════════════════════════════════════════════════════
 * Hero news carousel — Home.tsx NewsCarousel port (sl-ml/news-carousel;
 * behaviours in the plugin's news-carousel.js)
 * ══════════════════════════════════════════════════════════════════ */

/* The design (Tailwind Preflight) applies box-sizing:border-box to every
 * element; the theme has no global reset, so these elements default to
 * content-box. `.ml-container` is authored FOR border-box (its
 * max-width:calc(1640px + padding) adds the padding onto the max-width), so
 * under content-box `.ml-nc-sidecont` becomes width:100% + 48px padding and
 * overflows the stage by 48px — pushing the right-anchored side list past the
 * viewport and clipping its titles. Scope a border-box reset to the carousel
 * so its box model matches the design exactly. */
.ml-nc,
.ml-nc *,
.ml-nc *::before,
.ml-nc *::after { box-sizing: border-box; }

.ml-nc { margin-top: 2rem; }

@media (min-width: 768px) {
	.ml-nc { margin-top: 2.5rem; }
}

.ml-nc-frame {
	position: relative;
	background: var(--wp--preset--color--brand-teal);
	overflow: hidden;
}

/* Image stage — taller on mobile so text never crops (design aspect ladder) */
.ml-nc-stage { position: relative; aspect-ratio: 4 / 5; }

@media (min-width: 640px) { .ml-nc-stage { aspect-ratio: 16 / 10; } }
@media (min-width: 768px) { .ml-nc-stage { aspect-ratio: 16 / 9; } }
@media (min-width: 1024px) { .ml-nc-stage { aspect-ratio: 21 / 9; } }
@media (min-width: 1280px) { .ml-nc-stage { aspect-ratio: 2.5 / 1; } }

.ml-nc-slide {
	position: absolute;
	inset: 0;
	display: block;
	text-decoration: none;
	opacity: 0;
	transform: scale(1.04);
	transition: opacity 0.9s ease-out, transform 0.9s ease-out;
	pointer-events: none;
}

.ml-nc-slide.is-active { opacity: 1; transform: scale(1); pointer-events: auto; }

.ml-nc-slide:focus-visible {
	outline: 2px solid hsl(59 85% 47% / 0.7);
	outline-offset: 2px;
	border-radius: 4px; /* rounded-sm = 4px in this design's --radius map */
}

/* object-position is a DEVIATION from the Lovable source, which centres this image (Home.tsx
   :172) and uses object-top only for player photos. Asked for and approved by the customer
   2026-08-26: the stage is 2.5/1 on desktop while the median featured image is 3:2 and 39 of
   200 posts are portrait (down to 9:16), so centring cropped the subject's face away and left
   the torso. Swept 0 / 8 / 20 / 30 / 40% against the five pinned slides; 20% is the only value
   that shows every face. The binding case is a TALL portrait whose subject sits low (1585x1981,
   ratio 0.80 — only ~32% of its height fits the stage): at 8% the window landed on the forehead
   and cut the face off entirely. Going further to 30% fixes that one but starts clipping the top
   of the head on a 0.88 portrait, so 20% is the balance point, not a rounding.
   Headline graphics lose their top strip at this depth — accepted: a face beats a title.
   0% remains the ceiling; a value past it (50% -40%) slides the image off the edge and renders a
   blank band, not more picture. NB the stage is only ~634px tall at 2.5/1, so this picks WHICH
   part of an image shows, never how much — for more, the stage's aspect-ratio steps are the lever
   and a per-post focal point is the real fix if one global number keeps failing some photo. */
.ml-nc-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 20%; }

/* Cinematic gradients (readability + right-edge fade under the side list) */
.ml-nc-shade { position: absolute; inset: 0; pointer-events: none; }
.ml-nc-shade--v { background: linear-gradient(to top, hsl(182 100% 9%), hsl(182 100% 9% / 0.55), transparent); }
.ml-nc-shade--h { display: none; background: linear-gradient(to right, hsl(182 100% 9% / 0.75), hsl(182 100% 9% / 0.1), transparent); }
.ml-nc-shade--vig { background: radial-gradient(120% 80% at 50% 100%, hsl(182 100% 9% / 0.55), transparent 70%); }
.ml-nc-shade--edge { display: none; inset: 0 0 0 auto; width: 20rem; background: linear-gradient(to right, transparent, hsl(182 100% 9% / 0.95)); }

@media (min-width: 1024px) {
	.ml-nc-shade--h, .ml-nc-shade--edge { display: block; }
}
@media (min-width: 1280px) { .ml-nc-shade--edge { width: 22rem; } }

/* Hero copy pinned to the bottom, aligned to the 1640 container */
.ml-nc-content { position: absolute; inset: auto 0 0 0; display: block; }
.ml-nc-container { display: block; }
.ml-nc-content-in { display: block; padding: 1.5rem 0; }

@media (min-width: 640px) { .ml-nc-content-in { padding: 2rem 0; } }
@media (min-width: 768px) { .ml-nc-content-in { padding: 2.5rem 0; } }
@media (min-width: 1024px) { .ml-nc-content-in { padding: 3.5rem 22rem 3.5rem 0; } }
@media (min-width: 1280px) { .ml-nc-content-in { padding-right: 24rem; } }

.ml-nc-slide.is-active .ml-nc-content-in { animation: ml-nc-in 0.7s ease both; }

@keyframes ml-nc-in {
	from { opacity: 0; transform: translateY(0.75rem); }
	to { opacity: 1; transform: translateY(0); }
}

.ml-nc-kicker { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; }

@media (min-width: 768px) { .ml-nc-kicker { margin-bottom: 1rem; } }

/* Hero kicker chip + date (Home.tsx:196/200): text-xs md:text-sm tracking-[0.2em]. */
.ml-nc-chip {
	display: inline-flex;
	align-items: center;
	padding: 0.25rem 0.75rem;
	border-radius: 4px; /* rounded-sm = 4px in this design's --radius map */
	background: rgb(255 255 255 / 0.15);
	color: #fff;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.2em;
}

.ml-nc-kline { height: 1px; width: 2rem; background: rgb(255 255 255 / 0.3); }

.ml-nc-kdate {
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.2em;
	color: rgb(255 255 255 / 0.7);
}

@media (min-width: 768px) {
	.ml-nc-chip, .ml-nc-kdate { font-size: 0.875rem; line-height: 1.25rem; }
}

.ml-nc-title {
	display: block;
	color: #fff;
	font-size: 26px;
	line-height: 1.1;
	font-weight: 800;
	letter-spacing: -0.025em;
}

/* Line-heights track the design's Tailwind size utilities per breakpoint:
 * base text-[26px] keeps leading-[1.1]; sm:text-3xl → 2.25rem; md:text-4xl →
 * 2.5rem; lg:text-[44px] (arbitrary) keeps 1.1; xl:text-5xl → line-height 1. */
@media (min-width: 640px) { .ml-nc-title { font-size: 1.875rem; line-height: 2.25rem; } }
@media (min-width: 768px) { .ml-nc-title { font-size: 2.25rem; line-height: 2.5rem; } }
@media (min-width: 1024px) { .ml-nc-title { font-size: 44px; line-height: 1.1; } }
@media (min-width: 1280px) { .ml-nc-title { font-size: 3rem; line-height: 1; } }

/* Lime hover underline sweep (bg-size trick, verbatim from the source) */
.ml-nc-title-u {
	display: inline;
	background-image: linear-gradient(hsl(59, 85%, 47%), hsl(59, 85%, 47%));
	background-size: 0% 2px;
	background-repeat: no-repeat;
	background-position: left bottom;
	transition: background-size 0.5s;
}

.ml-nc-slide:hover .ml-nc-title-u { background-size: 100% 2px; }

.ml-nc-excerpt {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
	margin-top: 0.75rem;
	max-width: 48rem;
	color: rgb(255 255 255 / 0.85);
	font-size: 0.875rem;
	line-height: 1.625; /* design mobile keeps leading-relaxed (22.75px); md:text-base resets to 24px */
}

@media (min-width: 768px) { .ml-nc-excerpt { margin-top: 1rem; font-size: 1rem; line-height: 1.5rem; } }

/* Desktop side list — anchored to the container's right edge */
.ml-nc-sidewrap { display: none; position: absolute; inset: 0; pointer-events: none; }

@media (min-width: 1024px) { .ml-nc-sidewrap { display: block; } }

.ml-nc-sidecont { position: relative; height: 100%; }

.ml-nc-side {
	pointer-events: auto;
	position: absolute;
	right: 1rem;
	top: 0;
	bottom: 0;
	width: 18rem;
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	background: hsl(182 100% 9% / 0.4);
	-webkit-backdrop-filter: blur(12px);
	backdrop-filter: blur(12px);
	border-left: 1px solid rgb(255 255 255 / 0.08);
}

@media (min-width: 640px) { .ml-nc-side { right: 1.5rem; } }
@media (min-width: 1280px) { .ml-nc-side { width: 20rem; } }

.ml-nc-item { flex: 1; border-bottom: 1px solid rgb(255 255 255 / 0.06); }
.ml-nc-item:last-child { border-bottom: 0; }

/* .ml-nc-side qualifier: outranks the editorial `.wp-block-post-content li a`
 * content-link rule (underline + teal), which otherwise leaks into the list. */
.ml-nc-side .ml-nc-item > a {
	position: relative;
	display: flex;
	align-items: flex-start;
	gap: 0.75rem;
	width: 100%;
	height: 100%;
	padding: 1rem 1.25rem;
	text-decoration: none;
	font-weight: inherit;
	transition: background-color 0.3s;
}

.ml-nc-item.is-active > a { background: rgb(255 255 255 / 0.05); }
.ml-nc-item:not(.is-active) > a:hover { background: rgb(255 255 255 / 0.03); }

.ml-nc-rail {
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
	width: 3px;
	background: rgb(255 255 255 / 0.07);
	overflow: hidden;
}

.ml-nc-rail-fill {
	display: block;
	width: 100%;
	height: 100%;
	background: var(--wp--preset--color--brand-lime);
	transform: scaleY(0);
	transform-origin: top;
}

.ml-nc-item.is-active .ml-nc-rail-fill.is-running { animation: ml-slideProgress 7000ms linear forwards; }
.ml-nc.is-paused .ml-nc-item.is-active .ml-nc-rail-fill { animation: none; transform: scaleY(1); }

@keyframes ml-slideProgress {
	from { transform: scaleY(0); }
	to { transform: scaleY(1); }
}

/* Reduced motion: news carousel doesn't auto-advance (news-carousel.js suppresses
 * the timer), so freeze the slide fade/scale + the progress-fill + content-in
 * animations too. Slides stay reachable via dots/arrows/hover. (WCAG 2.2.2 / 2.3.3) */
@media (prefers-reduced-motion: reduce) {
	.ml-nc-slide { transition: none; }
	.ml-nc-slide.is-active .ml-nc-content-in { animation: none; }
	.ml-nc-item.is-active .ml-nc-rail-fill.is-running,
	.ml-nc-dotbtn.is-active .ml-nc-dot-fill.is-running { animation: none; }
}

/* Home.tsx rail number — text-sm font-extrabold leading-none pt-[3px] (drop 2026-08-21). */
.ml-nc-num {
	flex-shrink: 0;
	padding-top: 3px;
	font-size: 14px;
	line-height: 1;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
	color: rgb(255 255 255 / 0.3);
	transition: color 0.3s;
}

.ml-nc-item.is-active .ml-nc-num { color: var(--wp--preset--color--brand-lime); }

.ml-nc-item-body { min-width: 0; flex: 1; }

/* Home.tsx rail kicker — text-xs md:text-sm tracking-[0.22em] (drop 2026-08-21). */
.ml-nc-item-kicker {
	display: flex;
	align-items: center;
	gap: 0.375rem;
	font-size: 12px;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.22em;
	color: rgb(255 255 255 / 0.55);
	transition: color 0.3s;
}
@media (min-width: 768px) { .ml-nc-item-kicker { font-size: 14px; line-height: 1.25rem; } }

.ml-nc-item.is-active .ml-nc-item-kicker { color: var(--wp--preset--color--brand-lime); }

.ml-nc-item-title {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
	margin-top: 0.375rem;
	font-size: 14px;
	font-weight: 800;
	line-height: 1.375;
	color: rgb(255 255 255 / 0.8);
	transition: color 0.3s;
}

@media (min-width: 1280px) { .ml-nc-item-title { font-size: 15px; } }

.ml-nc-item.is-active .ml-nc-item-title,
.ml-nc-item > a:hover .ml-nc-item-title { color: #fff; }

/* Mobile indicator bar — "X / N" counter + tappable segment dots */
.ml-nc-mob {
	position: relative;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.75rem 1rem;
	background: var(--wp--preset--color--brand-teal);
	border-top: 1px solid rgb(255 255 255 / 0.08);
}

@media (min-width: 1024px) { .ml-nc-mob { display: none; } }

/* Home.tsx:293 — text-xs md:text-sm tracking-[0.22em]. */
.ml-nc-count {
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	letter-spacing: 0.22em;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--brand-lime);
}
@media (min-width: 768px) { .ml-nc-count { font-size: 0.875rem; line-height: 1.25rem; } }

/* Recorded a11y divergence: source is text-white/40 (3.55:1 on brand-teal,
 * fails WCAG AA) — lifted to /0.55 (~5:1) to pass the axe gate. */
.ml-nc-count-total { color: rgb(255 255 255 / 0.55); }

.ml-nc-dots { flex: 1; display: flex; align-items: center; gap: 0.375rem; }

.ml-nc-dotbtn {
	flex: 1;
	height: 0.75rem;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
}

.ml-nc-dot {
	display: block;
	width: 100%;
	height: 3px;
	border-radius: 9999px;
	overflow: hidden;
	background: rgb(255 255 255 / 0.1);
	transition: height 0.3s, background-color 0.3s, box-shadow 0.3s;
}

.ml-nc-dotbtn.is-past .ml-nc-dot { background: hsl(59 85% 47% / 0.6); }

.ml-nc-dotbtn.is-active .ml-nc-dot {
	height: 5px;
	background: hsl(59 85% 47% / 0.3);
	box-shadow: 0 0 6px hsl(59 85% 47% / 0.25);
}

.ml-nc-dot-fill {
	display: none;
	height: 100%;
	background: var(--wp--preset--color--brand-lime);
	box-shadow: 0 0 8px hsl(59 85% 47% / 0.6);
	transform: scaleX(0);
	transform-origin: left;
}

.ml-nc-dotbtn.is-active .ml-nc-dot-fill { display: block; }
.ml-nc-dotbtn.is-active .ml-nc-dot-fill.is-running { animation: ml-slideProgressX 7000ms linear forwards; }
.ml-nc.is-paused .ml-nc-dotbtn.is-active .ml-nc-dot-fill { animation: none; transform: scaleX(0); }

@keyframes ml-slideProgressX {
	from { transform: scaleX(0); }
	to { transform: scaleX(1); }
}

/* ══════════════════════════════════════════════════════════════════
 * News grid — Home.tsx NewsGrid port (sl-ml/news-grid)
 * ══════════════════════════════════════════════════════════════════ */

/* Section-head trailing link ("Kaikki uutiset →", SectionHead children) —
 * design text-xs md:text-sm (12px mobile, 14px ≥768px). */
.ml-secthead__link {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
	margin-bottom: 0.35rem;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--wp--preset--color--brand-teal);
	text-decoration: none;
}
@media (min-width: 768px) {
	.ml-secthead__link { font-size: 0.875rem; line-height: 1.25rem; }
}

.ml-secthead__link > span {
	border-bottom: 2px solid transparent;
	transition: border-color 0.15s;
}

.ml-secthead__link:hover > span { border-color: var(--wp--preset--color--brand-lime); }

.ml-secthead__link svg { margin-top: -1px; transition: transform 0.15s; }

.ml-secthead__link:hover svg { transform: translateX(0.125rem); }

/* Mobile: horizontal snap carousel, full-bleed to the root padding */
.ml-ng-scroll { position: relative; margin: 0 -1rem; }

/* Below-track scroll nav (design mobile-scroll-nav.tsx, drop 2026-08-21): two round buttons
 * that dim at their end + a progress bar, shown UNDER the carousel so cards are never covered.
 * Mobile-only like the carousel itself; ships hidden, hscroll-nav.js reveals it when the row
 * actually scrolls. */
.ml-msnav { margin-top: 0.75rem; display: flex; align-items: center; gap: 0.75rem; }
.ml-msnav[hidden] { display: none; }
.ml-msnav__btns { display: flex; align-items: center; gap: 0.5rem; }
.ml-msnav__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.5rem;
	height: 2.5rem;
	padding: 0;
	border-radius: 9999px;
	border: 2px solid hsl(182 100% 9% / 0.15);
	background: #fff;
	color: var(--wp--preset--color--brand-teal);
	cursor: pointer;
	transition: all 0.3s ease-out;
}
.ml-msnav__btn:disabled { opacity: 0.25; pointer-events: none; }
.ml-msnav__btn:hover { background: var(--wp--preset--color--brand-lime); color: var(--wp--preset--color--brand-teal); border-color: var(--wp--preset--color--brand-lime); }
.ml-msnav__btn:active { transform: scale(0.95); }
.ml-msnav__btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp--preset--color--brand-lime); }
.ml-msnav__btn svg { width: 1rem; height: 1rem; }
.ml-msnav__track { flex: 1 1 auto; height: 0.25rem; border-radius: 9999px; background: hsl(182 100% 9% / 0.1); overflow: hidden; }
.ml-msnav__fill {
	height: 100%;
	width: 38%;
	border-radius: 9999px;
	background: var(--wp--preset--color--brand-teal);
	transition: transform 0.3s ease-out;
}

.ml-ng-track {
	display: flex;
	gap: 1rem;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-padding-left: 1rem;
	padding: 0 1rem 0.5rem;
	scrollbar-width: none;
}

.ml-ng-track::-webkit-scrollbar { display: none; }

.ml-ng-track .ml-ng-card { flex: 0 0 78%; scroll-snap-align: start; }

.ml-ng-fade {
	pointer-events: none;
	position: absolute;
	top: 0;
	right: 0;
	height: 100%;
	width: 2.5rem;
	background: linear-gradient(to left, var(--wp--preset--color--background), transparent);
}

.ml-ng-grid { display: none; }

@media (min-width: 640px) {
	.ml-ng-scroll,
	.ml-ng-mobile { display: none; }
	.ml-ng-grid {
		display: grid;
		grid-template-columns: repeat(2, 1fr);
		column-gap: 1.5rem;
		row-gap: 2rem;
	}
	/* Drop 2026-08-21: 12 cards, but 10–12 only exist on the 3-col grid (`hidden lg:block`
	 * on i ≥ 9) — the 2-col band shows 9. */
	.ml-ng-grid .ml-ng-card:nth-child(n + 10) { display: none; }
}

@media (min-width: 1024px) {
	.ml-ng-grid { grid-template-columns: repeat(3, 1fr); }
	.ml-ng-grid .ml-ng-card:nth-child(n + 10) { display: block; }
}

/* Card */
.ml-ng-card { display: block; text-decoration: none; }

.ml-ng-media {
	position: relative;
	display: block;
	aspect-ratio: 16 / 10;
	background: var(--wp--preset--color--muted);
	border-radius: 0.5rem;
	overflow: hidden;
}

.ml-ng-media img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 0.5s;
}

.ml-ng-grid .ml-ng-card:hover .ml-ng-media img { transform: scale(1.05); }

.ml-ng-veil { position: absolute; inset: 0; background: hsl(182 100% 9% / 0); transition: background-color 0.3s; }

.ml-ng-card:hover .ml-ng-veil { background: hsl(182 100% 9% / 0.2); }

/* Card meta line (Home.tsx news cards): text-xs md:text-sm font-extrabold tracking-wider teal/70. */
.ml-ng-meta {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-top: 0.75rem;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: hsl(182 100% 9% / 0.7);
}

@media (min-width: 768px) { .ml-ng-meta { font-size: 0.875rem; line-height: 1.25rem; } }

/* Category badge (lib/newsCategory.ts newsCategoryBadgeClass): the design drop restyled the
 * pills from solid color + white text to a 10% tint + full-color text + 20% inset ring, and
 * grew them to text-xs md:text-[13px]. One shared style on every surface (etusivu grid,
 * uutiset index, artikkeli header/sidebar/related). Colors verbatim from index.css
 * --news-women/-men/-general. */
.ml-ng-pill {
	display: inline-flex;
	align-items: center;
	padding: 0.125rem 0.375rem;
	border-radius: 9999px;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
}

@media (min-width: 768px) { .ml-ng-pill { font-size: 13px; } }

.ml-ng-pill.is-naiset { background: hsl(0 72% 45% / 0.1); color: hsl(0 72% 45%); box-shadow: inset 0 0 0 1px hsl(0 72% 45% / 0.2); }
.ml-ng-pill.is-miehet { background: hsl(215 75% 40% / 0.1); color: hsl(215 75% 40%); box-shadow: inset 0 0 0 1px hsl(215 75% 40% / 0.2); }
.ml-ng-pill.is-liiga { background: hsl(220 15% 12% / 0.1); color: hsl(220 15% 12%); box-shadow: inset 0 0 0 1px hsl(220 15% 12% / 0.2); }

.ml-ng-title {
	display: block;
	margin-top: 0.25rem;
	font-size: 0.875rem;
	font-weight: 800;
	/* Below sm this styles the MOBILE CAROUSEL card, whose design title is
	 * `text-sm … leading-snug` — an unprefixed leading-* beats text-sm's paired 20px,
	 * so 19.25px, not 20px. From sm up the RESPONSIVE text-* variants (sm:text-base,
	 * md:text-lg) sort after the unprefixed leading-snug and win, hence their own
	 * paired 24px / 28px below. Verified against both stacks at 390 and 1440. */
	line-height: 1.375;
	color: var(--wp--preset--color--foreground);
}

@media (min-width: 640px) { .ml-ng-title { font-size: 1rem; line-height: 1.5rem; } }
@media (min-width: 768px) { .ml-ng-title { font-size: 1.125rem; line-height: 1.75rem; } }
/* Uutiset INDEX cards carry a larger title than the front-page grid (design NewsIndex:
   text-lg md:text-xl = 18px mobile → 20px desktop). Scoped to .ml-uutiset so the front page
   (.ml-ng small cards) is untouched. */
.ml-uutiset .ml-ng-title { font-size: 1.125rem; line-height: 1.375; } /* design mobile leading-snug (24.75px) */
@media (min-width: 768px) { .ml-uutiset .ml-ng-title { font-size: 1.25rem; line-height: 1.75rem; } }

.ml-ng-title-u { border-bottom: 2px solid transparent; transition: border-color 0.15s; }

.ml-ng-card:hover .ml-ng-title-u { border-color: var(--wp--preset--color--brand-lime); }

/* The clamp's "…" comes from the FONT: Kobra Sans maps U+2026 to a glyph with the advance of a
 * period, so every clamped lead (here, .ml-nc-excerpt, .ml-tn-excerpt, the nav's text-overflow)
 * ended in what read as a full stop — "the ingress just stops" (customer 2026-08-27). Fixed at
 * the source: theme.json's fontFace declares unicodeRange without U+2026, so the ellipsis falls
 * through to system-ui's real three-dot glyph in every clamp on the site. No per-listing rule. */
.ml-ng-excerpt {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	/* 3, not 2: the design carries a line-clamp-2 class but it never takes effect there
	   (its excerpt stays display:block), so the source wraps naturally and its tallest card
	   runs 3 lines / 348px. Clamping at 2 made every WP card 23px shorter than the design.
	   The uutiset grid already used 3 — this makes the front-page carousel agree. */
	-webkit-line-clamp: 3;
	overflow: hidden;
	margin-top: 0.5rem;
	font-size: 0.875rem;
	line-height: 1.625;
	color: var(--wp--preset--color--muted-foreground);
}

/* Naisten/Miesten uutiset pill links */
.ml-ng-ctas { margin-top: 2rem; display: flex; gap: 0.5rem; }

@media (min-width: 640px) { .ml-ng-ctas { gap: 0.75rem; } }

.ml-ng-cta {
	flex: 1;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.375rem;
	padding: 0.5rem 0.75rem;
	border-radius: 9999px;
	background: var(--wp--preset--color--brand-teal);
	color: var(--wp--preset--color--brand-lime);
	border: 2px solid var(--wp--preset--color--brand-teal);
	font-size: 10px;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	text-decoration: none;
	white-space: nowrap;
	transition: color 0.15s, background-color 0.15s, border-color 0.15s;
}

@media (min-width: 640px) {
	.ml-ng-cta { gap: 0.5rem; padding: 0.625rem 1.25rem; font-size: 0.75rem; line-height: 1rem; }
}

.ml-ng-cta:hover {
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	border-color: var(--wp--preset--color--brand-lime);
}

.ml-ng-cta svg { transition: transform 0.15s; }

.ml-ng-cta:hover svg { transform: translateX(0.125rem); }

/* ══════════════════════════════════════════════════════════════════
 * PageHero (sl-ml/page-hero) + Uutiset index (sl-ml/news-index) —
 * PageHero.tsx + NewsIndex.tsx ports. border-box reset (theme has no
 * global one; `.ml-container` is authored for border-box — see the .ml-nc
 * note) so the full-bleed banner + padded container don't overflow.
 * ══════════════════════════════════════════════════════════════════ */
.ml-pagehero, .ml-pagehero *, .ml-pagehero *::before, .ml-pagehero *::after,
.ml-uutiset, .ml-uutiset *, .ml-uutiset *::before, .ml-uutiset *::after { box-sizing: border-box; }

.ml-pagehero__banner {
	position: relative;
	width: 100%;
	height: 180px;
	overflow: hidden;
	background: var(--wp--preset--color--brand-teal);
}
@media (min-width: 640px)  { .ml-pagehero__banner { height: 240px; } }
@media (min-width: 768px)  { .ml-pagehero__banner { height: 300px; } }
@media (min-width: 1024px) { .ml-pagehero__banner { height: 360px; } }

.ml-pagehero__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.ml-pagehero__shade { position: absolute; inset: 0; pointer-events: none; }
.ml-pagehero__shade--v { background: linear-gradient(to top, hsl(182 100% 9% / 0.6), hsl(182 100% 9% / 0.2) 50%, transparent); }
.ml-pagehero__shade--h { background: linear-gradient(to right, hsl(182 100% 9% / 0.4), transparent 50%); }

.ml-pagehero__body { padding-top: 1.5rem; padding-bottom: 1.5rem; }
@media (min-width: 768px) { .ml-pagehero__body { padding-top: 2rem; padding-bottom: 2rem; } }

.ml-pagehero__crumb {
	margin: 0 0 0.5rem;
	font-size: 0.75rem;
	line-height: 1rem; /* text-xs pair (12/16) — inherited 1.5 rendered 18px */
	color: var(--wp--preset--color--muted-foreground);
}
@media (min-width: 768px) { .ml-pagehero__crumb { font-size: 0.875rem; line-height: 1.25rem; } }
/* PageHero.tsx crumb list: flex-wrap, gap-x-2 gap-y-1; links hover to primary; the
 * current page crumb is primary-colored, non-linked (aria-current="page").
 * `li a` selectors out-tie the editorial `.wp-block-post-content li a` underline net
 * (0,1,2 — later in the cascade), which otherwise paints the crumbs lime-underlined teal. */
.ml-pagehero__crumb-list { display: flex; flex-wrap: wrap; align-items: center; column-gap: 0.5rem; row-gap: 0.25rem; margin: 0; padding: 0; list-style: none; }
.ml-pagehero__crumb-list li { display: flex; align-items: center; column-gap: 0.5rem; }
.ml-pagehero__crumb-list li a { font-weight: inherit; color: inherit; text-decoration: none; transition: color .15s; }
.ml-pagehero__crumb-list li a:hover { color: var(--wp--preset--color--brand-teal); text-decoration: none; }
.ml-pagehero__crumb-here { color: var(--wp--preset--color--brand-teal); }

.ml-pagehero__title {
	margin: 0;
	font-weight: 800;
	color: var(--wp--preset--color--brand-teal);
	text-transform: uppercase;
	letter-spacing: -0.025em; /* Tailwind tracking-tight (PageHero.tsx h1) — not -0.02em; drift accumulates over long titles */
	/* Tailwind size/lh pairs step together: text-3xl 30/36 → sm:text-4xl 36/40 →
	 * md:text-5xl 48/1 → lg:[61px] with the design's explicit lg:leading-[1.1]. */
	line-height: 2.25rem;
	font-size: 1.875rem;
	/* Opt OUT of the .ml-content customer-content wrap net, which is inherited and would
	   otherwise split a long Finnish compound mid-word across two lines ("SAAVUTETTAVUUSSELO
	   / STE"). The design keeps such a title on ONE line; at 390px its widest real title
	   overhangs its box by ~7px, which still sits inside the 16px page gutter, so nothing
	   clips. Body copy below keeps the net — only the hero title opts out. */
	overflow-wrap: normal;
	word-break: normal;
}
@media (min-width: 640px)  { .ml-pagehero__title { font-size: 2.25rem; line-height: 2.5rem; } }
@media (min-width: 768px)  { .ml-pagehero__title { font-size: 3rem; line-height: 1; } }
@media (min-width: 1024px) { .ml-pagehero__title { font-size: 61px; line-height: 1.1; } }

/* Uutiset index */
.ml-uutiset { padding-top: 1.5rem; padding-bottom: 1.5rem; }
@media (min-width: 768px) { .ml-uutiset { padding-top: 2rem; padding-bottom: 2rem; } }

.ml-uutiset__filters {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
}
.ml-uutiset__pills { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.ml-uutiset__pill {
	padding: 0.5rem 1.25rem;
	border-radius: 9999px;
	border: 2px solid hsl(182 100% 9% / 0.3);
	background: transparent;
	color: var(--wp--preset--color--brand-teal);
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	text-decoration: none;
	transition: color 0.2s, background-color 0.2s, border-color 0.2s;
}
.ml-uutiset__pill:hover {
	background: var(--wp--preset--color--brand-lime);
	border-color: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
}
.ml-uutiset__pill.is-active {
	background: var(--wp--preset--color--brand-teal);
	border-color: var(--wp--preset--color--brand-teal);
	color: var(--wp--preset--color--brand-lime);
}

/* Custom team dropdown — 1:1 with the design's shadcn Select (teal-outline trigger + rounded
   popover, lime-hover options). Measured against the OPEN Radix control, not just the closed
   trigger: geometry below is the design's computed values (probe 2026-07-29). */
.ml-uutiset__select { position: relative; }
.ml-uutiset__team {
	display: inline-flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	min-width: 220px;
	height: 2.5rem;
	border-radius: 9999px;
	border: 2px solid var(--wp--preset--color--brand-teal);
	background-color: transparent;
	padding: 0.5rem 1.25rem; /* design py-2 px-5 */
	color: var(--wp--preset--color--brand-teal);
	font-size: 0.75rem;
	line-height: 1rem; /* text-xs PAIRS 16px — without it the row inherited 18px */
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	cursor: pointer;
	transition: background-color 0.2s, border-color 0.2s;
}
.ml-uutiset__team:hover { background-color: var(--wp--preset--color--brand-lime); border-color: var(--wp--preset--color--brand-lime); }
/* design SelectTrigger: [&>span]:line-clamp-1 */
.ml-uutiset__team-label {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 1;
	overflow: hidden;
}
.ml-uutiset__chev { flex: none; transition: transform 0.2s; }
.ml-uutiset__team[aria-expanded="true"] .ml-uutiset__chev { transform: rotate(180deg); }
.ml-uutiset__menu {
	position: absolute;
	top: calc(100% + 4px); /* design popper translate-y-1 */
	right: 0;
	z-index: 40;
	min-width: 100%;
	max-height: 360px;
	overflow-y: auto;
	scrollbar-width: none; /* hidden scrollbar, still scrolls — matches the design's Radix viewport */
	margin: 0;
	padding: 0.25rem; /* design viewport p-1 */
	list-style: none;
	background: #fff;
	border: 2px solid var(--wp--preset--color--brand-teal);
	border-radius: 0.75rem;
	box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); /* design shadow-md */
}
/* Radix flips the popover above the trigger when the viewport has no room below it;
   news-index-filter.js sets this class from the same measurement. */
.ml-uutiset__menu.is-above { top: auto; bottom: calc(100% + 4px); }
.ml-uutiset__menu::-webkit-scrollbar { display: none; }
.ml-uutiset__menu[hidden] { display: none; }
.ml-uutiset__opt {
	position: relative;
	padding: 0.375rem 0.5rem 0.375rem 2rem; /* design py-1.5 pr-2 pl-8 — the 32px check gutter */
	border-radius: 0.25rem; /* design rounded-sm */
	cursor: pointer;
	font-size: 0.75rem;
	line-height: 1rem; /* text-xs pair; was inheriting 18px → 34px rows vs the design's 28px */
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--wp--preset--color--brand-teal);
	white-space: nowrap;
}
/* The design marks the SELECTED option with this check, not with a filled row; lime is its
   hover/highlight state only. */
.ml-uutiset__optcheck {
	position: absolute;
	left: 0.5rem;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 0.875rem;
	height: 0.875rem;
}
.ml-uutiset__opt:hover,
.ml-uutiset__opt:focus-visible {
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	outline: none;
}

.ml-uutiset__grid {
	margin-top: 2rem;
	display: grid;
	grid-template-columns: 1fr;
	column-gap: 1.5rem;
	row-gap: 2.5rem;
}
@media (min-width: 640px)  { .ml-uutiset__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 768px)  { .ml-uutiset__grid { margin-top: 2.5rem; } }
@media (min-width: 1024px) { .ml-uutiset__grid { grid-template-columns: repeat(3, 1fr); } }

/* Reuse .ml-ng-card, but this grid always shows cards (not the mobile-carousel
 * hide) and clamps the lead to 3 lines like NewsIndex.tsx. */
.ml-uutiset__grid .ml-ng-card:hover .ml-ng-media img { transform: scale(1.05); }
.ml-uutiset__grid .ml-ng-excerpt { -webkit-line-clamp: 3; }

/* 1fr grid tracks are minmax(auto,1fr): ONE unbreakable word in a customer
 * article's title/excerpt inflates that card's whole COLUMN via track
 * min-content (column 1 rendered ~230px wider than the others). min-width:0
 * restores the design's equal tracks; anywhere-wrapping keeps the word inside
 * the card. Applies to both news grids (uutiset index + front page). */
.ml-uutiset__grid > .ml-ng-card,
.ml-ng-grid > .ml-ng-card { min-width: 0; }
.ml-ng-card .ml-ng-title,
.ml-ng-card .ml-ng-excerpt { overflow-wrap: anywhere; }

.ml-uutiset__empty {
	text-align: center;
	color: var(--wp--preset--color--muted-foreground);
	padding: 6rem 0;
}

.ml-uutiset__pager {
	margin-top: 3rem;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	flex-wrap: wrap;
}
.ml-uutiset__pgbtn {
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
	padding: 0.5rem 0.75rem;
	border-radius: 0.375rem;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--wp--preset--color--brand-teal);
	text-decoration: none;
	transition: background-color 0.15s;
}
.ml-uutiset__pgbtn:hover { background: var(--wp--preset--color--brand-lime); }
.ml-uutiset__pgbtn.is-disabled { opacity: 0.4; pointer-events: none; }

.ml-uutiset__pgnum {
	width: 2.5rem;
	height: 2.5rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 0.375rem;
	font-size: 0.875rem;
	font-weight: 800;
	color: var(--wp--preset--color--brand-teal);
	text-decoration: none;
	transition: background-color 0.15s, color 0.15s;
}
.ml-uutiset__pgnum:hover { background: var(--wp--preset--color--brand-lime); }
.ml-uutiset__pgnum.is-active { background: var(--wp--preset--color--brand-teal); color: var(--wp--preset--color--brand-lime); }
/* Elided run of page numbers ("…") — same box as a number so the row keeps its rhythm. */
.ml-uutiset__pgdots {
	width: 2.5rem;
	height: 2.5rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 0; /* the "…" text node stays for copy/paste; the visible dots are drawn below */
	color: var(--wp--preset--color--brand-teal);
	opacity: 0.6;
	user-select: none;
}
/* Three real dots: the display font renders U+2026 as one tiny dot, unreadable as "more pages". */
.ml-uutiset__pgdots::before {
	content: "";
	width: 1rem;
	height: 0.25rem;
	background: radial-gradient(circle, currentColor 1.5px, transparent 1.6px) 0 0 / 0.375rem 100% repeat-x;
	background-position: -0.0625rem 0;
}

/* Home split: news 2/3 + aside 1/3 (Home.tsx section 5). The grid tracks are
 * the design's lg:grid-cols-3 with the news block spanning two. NB `2fr 1fr`
 * is NOT equivalent: a span-2 column absorbs one internal gap, so cols-3 math
 * gives main 1080 / aside 520 (@1640) where 2fr/1fr gives 1067/533. */
@media (min-width: 1024px) {
	.ml-home-split {
		display: grid;
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: 2.5rem;
	}
	.ml-home-split > :first-child {
		grid-column: span 2;
	}
	.ml-home-split > .ml-home-aside {
		grid-column: span 1;
	}
}

/* Aside stack rhythm (Home.tsx: <aside class="space-y-10">) */
.ml-home-aside > * + * {
	margin-block-start: 2.5rem;
}

/* Below the lg grid the aside flows under the news grid — keep the design's
 * grid-cols-1 gap-8 separation. */
@media (max-width: 1023px) {
	.ml-home-split > .ml-home-aside {
		margin-block-start: 2rem;
	}
}

/* Etusivu section rhythm: the global alignwide margin guard (top of this
 * file) zeroes margin-block-start on alignwide children of constrained
 * layouts — these homepage section wrappers need their design gaps back.
 * Same selector shape as the guard; later in source, so it wins the tie. */
:root :where(.is-layout-constrained) > .alignwide.ml-brandband,
:root :where(.is-layout-constrained) > .alignwide.ml-partners,
:root :where(.is-layout-constrained) > .alignwide.ml-nlsign,
:root :where(.is-layout-constrained) > .alignwide.ml-sect-gap {
	margin-block-start: 3rem !important;
}

@media (min-width: 768px) {
	:root :where(.is-layout-constrained) > .alignwide.ml-brandband,
	:root :where(.is-layout-constrained) > .alignwide.ml-partners,
	:root :where(.is-layout-constrained) > .alignwide.ml-nlsign,
	:root :where(.is-layout-constrained) > .alignwide.ml-sect-gap {
		margin-block-start: 4rem !important;
	}
}

/* The video variant follows the brand band at the design's TIGHTER gap
 * (VideoPromo.tsx `mt-6 md:mt-8` — 24 → 32 px, not the 48/64 section gap). */
:root :where(.is-layout-constrained) > .alignwide.ml-vpromo {
	margin-block-start: 1.5rem !important;
}

@media (min-width: 768px) {
	:root :where(.is-layout-constrained) > .alignwide.ml-vpromo {
		margin-block-start: 2rem !important;
	}
}

/* Small section gap = the design's mt-8 md:mt-10 (32 → 40 px): news carousel
 * (alignfull) + club strip (alignwide). The margin guard above also zeroes
 * alignfull children, so these need the same !important re-add — the plain
 * .ml-nc { margin-top } / .ml-clubstrip rules lose to the guard otherwise. */
:root :where(.is-layout-constrained) > .alignwide.ml-sect-gap-sm,
:root :where(.is-layout-constrained) > .alignfull.ml-sect-gap-sm {
	margin-block-start: 2rem !important;
}

@media (min-width: 768px) {
	:root :where(.is-layout-constrained) > .alignwide.ml-sect-gap-sm,
	:root :where(.is-layout-constrained) > .alignfull.ml-sect-gap-sm {
		margin-block-start: 2.5rem !important;
	}
}

/* First-section top padding below the sticky header — the design's MainContainer
 * pt-5 md:pt-6 (20 → 24 px). Padding, so the margin guard doesn't touch it. */
.ml-sect-top { padding-top: 1.25rem; }
@media (min-width: 768px) { .ml-sect-top { padding-top: 1.5rem; } }

/* ══ Article (single uutinen) — Article.tsx + BlockRenderer inline variant ══════════ */
.ml-article-page { background: hsl(210 20% 96% / 0.4); padding: 2rem 1rem; }
@media (min-width: 640px) { .ml-article-page { padding: 2rem 1.5rem; } }
@media (min-width: 768px) { .ml-article-page { padding: 3rem 1.5rem; } }
.ml-article,
.ml-article-related { max-width: 1640px; margin-inline: auto; }

/* Panel 1 — lime top border */
.ml-article {
	background: var(--wp--preset--color--card);
	border-radius: 1rem;
	box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
	border-top: 4px solid var(--wp--preset--color--brand-lime);
	/* clip, not hidden — `hidden` makes .ml-article a scroll container, which traps
	   the descendant .ml-article__aside `position: sticky` inside the panel so it
	   scrolls away instead of following the body (design keeps the panel un-clipped,
	   Article.tsx:47). `clip` still rounds the corners but preserves sticky. */
	overflow: clip;
}
.ml-article__head { padding: 2rem 1.25rem 0.5rem; }
.ml-article__figure { padding: 1.5rem 0 0; margin: 0; }
.ml-article__cols { padding: 1.5rem 1.25rem; }
@media (min-width: 640px) {
	.ml-article__head, .ml-article__cols { padding-inline: 2rem; }
	.ml-article__figure { padding-inline: 2rem; }
}
@media (min-width: 768px) {
	.ml-article__head { padding-top: 3rem; }
	.ml-article__head, .ml-article__cols { padding-inline: 3rem; }
	.ml-article__figure { padding-inline: 3rem; }
	.ml-article__cols { padding-block: 2.5rem; }
}
@media (min-width: 1024px) {
	.ml-article__head, .ml-article__cols { padding-inline: 4rem; }
	.ml-article__figure { padding-inline: 4rem; }
}

/* Breadcrumb + pills */
.ml-article__topbar { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 0.75rem; margin-bottom: 1.5rem; }
/* flex-wrap + min-width:0 match the design's `flex flex-wrap` nav (Article.tsx): a long article
 * title in the current crumb wraps to the next line instead of pushing the breadcrumb past the
 * viewport; overflow-wrap breaks a pathological unbroken token; the chevron never shrinks. */
.ml-article__crumbs { display: flex; flex-wrap: wrap; min-width: 0; align-items: center; gap: 0.35rem; font-size: 0.75rem; line-height: 1rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--wp--preset--color--muted-foreground); }
.ml-article__crumbs a { color: var(--wp--preset--color--muted-foreground); text-decoration: none; }
.ml-article__crumbs a:hover { color: var(--wp--preset--color--brand-teal); }
.ml-article__crumbs svg { flex-shrink: 0; }
.ml-article__crumbs .is-current { color: var(--wp--preset--color--brand-teal); min-width: 0; overflow-wrap: anywhere; }
.ml-article__pills { display: inline-flex; align-items: center; gap: 0.5rem; }
.ml-article-pill { display: inline-flex; align-items: center; padding: 0.125rem 0.625rem; border-radius: 9999px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; }
.ml-article-pill--cat { background: var(--wp--preset--color--brand-teal); color: var(--wp--preset--color--brand-lime); }
.ml-article-pill--team { background: var(--wp--preset--color--brand-lime); color: var(--wp--preset--color--brand-teal); }

/* Title / lead / date — override theme.json uppercase h1 */
/* The design's ladder pairs each step with a FIXED line-height (Tailwind text-3xl = 30/36,
   text-5xl = 48/48), not one ratio across the ladder — a uniform 1.1 ran mobile 3px tight
   per line and tablet 4.8px loose. Only the ≥1024 step is genuinely ratio-based (1.05). */
.ml-article__title { margin: 0; font-size: 1.875rem; font-weight: 800; line-height: 2.25rem; letter-spacing: -0.025em; text-transform: none; color: var(--wp--preset--color--brand-teal); }
@media (min-width: 768px) { .ml-article__title { font-size: 3rem; line-height: 1; } }
@media (min-width: 1024px) { .ml-article__title { font-size: 56px; line-height: 1.05; } }
/* line-height is the design's FIXED 1.75rem (Tailwind text-lg/text-xl both pair 28px with
   18px and 20px), not a ratio — a 1.625 ratio ran the lead 4.5px looser per line at desktop. */
.ml-article__lead { margin: 1.25rem 0 0; font-size: 1.125rem; line-height: 1.75rem; color: var(--wp--preset--color--muted-foreground); }
@media (min-width: 768px) { .ml-article__lead { font-size: 1.25rem; } }
.ml-article__date { margin: 2rem 0 0; padding-bottom: 1.5rem; border-bottom: 1px solid var(--wp--preset--color--border); font-size: 0.875rem; color: var(--wp--preset--color--muted-foreground); }

/* Lead image + credit */
.ml-article__lead-image { margin: 0; }
.ml-article__lead-image img { width: 100%; height: auto; display: block; }
@media (min-width: 640px) { .ml-article__lead-image img { border-radius: 0.5rem; } }
.ml-article__credit { margin: 0.5rem 1.25rem 0; text-align: right; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--wp--preset--color--muted-foreground); }
@media (min-width: 640px) { .ml-article__credit { margin-inline: 0; } }

/* Body + sidebar grid */
.ml-article__cols { display: grid; gap: 2rem; }
@media (min-width: 1024px) { .ml-article__cols { grid-template-columns: minmax(0, 1fr) 320px; } }
@media (min-width: 1280px) { .ml-article__cols { gap: 3rem; } }
.ml-article__body { min-width: 0; max-width: none; }
/* Long unbroken words / pasted URLs must wrap, not overflow-clip: an ancestor's
 * overflow:clip hides the over-wide text instead of scrolling (false pass), so
 * customer-pasted URLs silently get cut off on mobile. overflow-wrap is inherited
 * → one rule covers every descendant (p, li, headings, table cells). */
.ml-article__body,
.ml-content { overflow-wrap: break-word; word-break: break-word; }

/* Site-wide safety net (skill L8 content-robustness): a long unbreakable token
 * — a bare URL, SKU, or 60+ char Finnish compound — in any heading/body element
 * must wrap, not overflow-clip. Covers surfaces OUTSIDE .ml-content that the
 * customer can still set (page-hero H1 .ml-pagehero__title, post excerpts).
 * :where() = 0 specificity, so intentional white-space:nowrap rules (e.g. the
 * .ml-cta pills) still win. overflow-wrap inherits → one rule cascades. */
:where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, blockquote, figcaption, cite, td, th) {
	overflow-wrap: break-word;
}

/* Korttiruudukko-kuvio (patterns/card-grid.php): natiivi core/group grid-asettelu hoitaa
 * responsiivisen kääriytymisen; .ml-card antaa korttien reunuksen/täytteen/pyöristyksen. */
.ml-card {
	height: 100%;
	/* !important: the block editor's root-padding-aware layout zeroes a constrained-group child's
	 * inline padding IN THE CANVAS (cards render edge-to-edge while typing), so the editing view
	 * didn't match live. The front end already renders 1.5rem all sides (no such override there),
	 * so !important is a no-op on the front end and only fixes editor parity. */
	padding: 1.5rem !important;
	border: 1px solid var(--wp--preset--color--border);
	border-radius: 1rem;
	background: #fff;
	transition: border-color 0.2s, box-shadow 0.2s;
}
.ml-card:hover { border-color: var(--wp--preset--color--brand-lime); box-shadow: 0 8px 24px hsl(182 100% 9% / 0.08); }
.ml-card > :first-child { margin-top: 0; }
.ml-card > :last-child { margin-bottom: 0; }
.ml-card h3 { margin: 0 0 0.5rem; font-size: 1.25rem; font-weight: 800; color: var(--wp--preset--color--brand-teal); }
.ml-card p { margin: 0 0 1rem; }

/* Body block spacing (theme.json blockGap:0 → restore from BlockRenderer inline variant) */
.ml-article__body :where(h2, h3, h4) { margin: 2.5rem 0 0.75rem; font-weight: 800; color: var(--wp--preset--color--brand-teal); letter-spacing: -0.025em; line-height: 1.2; }
.ml-article__body h2 { font-size: 1.5rem; line-height: 2rem; }
.ml-article__body h3 { font-size: 1.25rem; line-height: 1.75rem; }
.ml-article__body h4 { font-size: 1.125rem; }
@media (min-width: 768px) { .ml-article__body h2 { font-size: 1.875rem; line-height: 2.25rem; } .ml-article__body h3 { font-size: 1.5rem; line-height: 2rem; } .ml-article__body h4 { font-size: 1.25rem; line-height: 1.75rem; } }
.ml-article__body p { margin: 1rem 0; font-size: 15px; line-height: 1.5; color: var(--wp--preset--color--foreground); }
@media (min-width: 768px) { .ml-article__body p { font-size: 1rem; } }
/* Ingressi — the registered "Ingressi" paragraph style is offered in every editor, so it must
 * mean the same thing in an article body as on a page (mirror of `.ml-content p.is-style-lead`).
 * NB this is a body paragraph the writer marks up, not the article's own ingressi field
 * (that one is the manual excerpt — sl-ml-elements-plugin/includes/class-articles.php). */
.ml-article__body p.is-style-lead { font-size: 1.125rem; color: var(--wp--preset--color--muted-foreground); line-height: 1.75rem; }
@media (min-width: 768px) { .ml-article__body p.is-style-lead { font-size: 1.25rem; line-height: 1.75rem; } }
.ml-article__body ul, .ml-article__body ol { margin: 1rem 0; padding-left: 1.25rem; }
.ml-article__body ul { list-style: none; padding-left: 0; }
.ml-article__body ul li { position: relative; padding-left: 1.25rem; margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
.ml-article__body ul li::before { content: ""; position: absolute; left: 0; top: 0.6em; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--wp--preset--color--brand-lime); } /* round dot — matches the design bullet */
.ml-article__body ol { list-style: decimal; padding-left: 1.5rem; }
.ml-article__body ol li { margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
.ml-article__body ol li::marker { color: var(--wp--preset--color--brand-teal); font-weight: 800; }
@media (min-width: 768px) { .ml-article__body ul li, .ml-article__body ol li { font-size: 1rem; } }
.ml-article__body blockquote { margin: 2rem 0; padding-left: 1.5rem; border-left: 4px solid var(--wp--preset--color--brand-lime); }
.ml-article__body blockquote p { font-size: 1.25rem; font-weight: 800; line-height: 1.375; color: var(--wp--preset--color--brand-teal); }
/* md:text-2xl is a RESPONSIVE variant, so it beats the unprefixed leading-snug and
 * brings its own 32px — keeping the 1.375 ratio here made the quote 33px and every
 * sitaatti ~6px taller than the design. */
@media (min-width: 768px) { .ml-article__body blockquote p { font-size: 1.5rem; line-height: 2rem; } }
/* The design wraps the quote text in literal double quotes — BlockRenderer.tsx:165
 * renders `"{block.text}"`. They are presentation, not content: adding them here keeps
 * the stored quote clean (the customer types the text, never the marks, and editing it
 * can never lose or double them). first/last-of-type so a multi-paragraph core/quote
 * still gets exactly one opening and one closing mark. */
.ml-article__body blockquote p:first-of-type::before { content: '"'; }
.ml-article__body blockquote p:last-of-type::after { content: '"'; }
/* BlockRenderer.tsx:166 renders `— {block.cite}` — the em dash is part of the design's citation
 * line, same presentation-not-content call as the quote marks above (the customer types the name,
 * never the dash). */
.ml-article__body blockquote cite::before { content: '\2014\a0'; }
.ml-article__body blockquote cite { display: block; margin-top: 0.75rem; font-style: normal; font-size: 0.875rem; line-height: 1.25rem; color: var(--wp--preset--color--muted-foreground); }
.ml-article__body figure { margin: 2rem 0; }
.ml-article__body figure img { width: 100%; height: auto; border-radius: 0.5rem; }
.ml-article__body figcaption { margin-top: 0.5rem; font-size: 0.875rem; font-style: italic; color: var(--wp--preset--color--muted-foreground); }
/* A data integration dropped into an article (any ml/* block from "Integraatiot (Torneopal)")
 * joins the article rhythm like a figure. Without this the mount div has NO margins (blockGap
 * is 0 theme-wide and only the text elements above re-add rhythm), so the widget sat flush
 * against the next block — e.g. a matchstrip pressed straight onto an ad banner
 * (tasklist 2026-08-13: "make sure it has bit space under it"). */
.ml-article__body [class*="wp-block-ml-"] { margin: 2rem 0; }
.ml-article__body hr { margin: 2.5rem 0; border: 0; border-top: 1px solid var(--wp--preset--color--border); }

/* Sidebar */
.ml-article__aside { align-self: start; }
.ml-article__aside > * + * { margin-top: 1.5rem; }
@media (min-width: 1024px) { .ml-article__aside { position: sticky; top: 6rem; } }
/* NOTE: article-sidebar classes are prefixed .ml-artaside-* / .ml-artcta* on purpose —
 * the Etusivu mini blocks (AsideHead) already own .ml-aside-* and the brand band owns
 * .ml-cta, so reusing those names here leaks (shrank the Etusivu mini heads 18→14px). */
.ml-artaside-head { margin: 0 0 1rem; display: inline-flex; align-items: center; gap: 0.75rem; font-size: 1rem; line-height: 1.5rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; color: var(--wp--preset--color--brand-teal); }
.ml-artaside-head::before { content: ""; width: 1.5rem; height: 0.25rem; border-radius: 9999px; background: var(--wp--preset--color--brand-lime); flex: none; }
.ml-artaside-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--wp--preset--color--border); border-bottom: 1px solid var(--wp--preset--color--border); }
.ml-artaside-list li + li { border-top: 1px solid var(--wp--preset--color--border); }
.ml-artaside-item { display: flex; gap: 0.75rem; padding: 0.75rem 0; align-items: flex-start; text-decoration: none; }
.ml-artaside-thumb { position: relative; flex: 0 0 auto; width: 5rem; height: 4rem; border-radius: 0.25rem; overflow: hidden; background: var(--wp--preset--color--muted); }
.ml-artaside-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform .5s; }
.ml-artaside-item:hover .ml-artaside-thumb img { transform: scale(1.05); }
.ml-artaside-body { min-width: 0; }
.ml-artaside-title { display: block; font-size: 0.875rem; font-weight: 800; line-height: 1.375; color: var(--wp--preset--color--brand-teal); }
.ml-artaside-title span { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; border-bottom: 2px solid transparent; transition: border-color .15s; }
.ml-artaside-item:hover .ml-artaside-title span { border-color: var(--wp--preset--color--brand-lime); }
/* Article.tsx sidebar meta — text-xs md:text-sm font-semibold; the pill keeps the shared
 * .ml-ng-pill size (the drop dropped the old 9px sidebar cap). */
.ml-artaside-meta { margin-top: 0.25rem; display: flex; align-items: center; gap: 0.375rem; font-size: 0.75rem; line-height: 1rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--wp--preset--color--muted-foreground); }
@media (min-width: 768px) { .ml-artaside-meta { font-size: 0.875rem; line-height: 1.25rem; } }

/* CTA cards (Ruutu teal / lippu.fi lime) */
.ml-artaside-ctas { display: flex; flex-direction: column; gap: 0.75rem; }
.ml-artcta { display: flex; align-items: center; justify-content: space-between; gap: 1rem; border-radius: 0.5rem; padding: 1rem 1.25rem; text-decoration: none; }
.ml-artcta--ruutu { background: var(--wp--preset--color--brand-teal); }
.ml-artcta--lippu { background: var(--wp--preset--color--brand-lime); }
.ml-artcta__eyebrow { display: block; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; }
.ml-artcta--ruutu .ml-artcta__eyebrow { color: var(--wp--preset--color--brand-lime); }
.ml-artcta--lippu .ml-artcta__eyebrow { color: var(--wp--preset--color--brand-teal); }
.ml-artcta__title { margin-top: 0.25rem; font-size: 1rem; font-weight: 800; display: inline-flex; align-items: center; gap: 0.25rem; }
.ml-artcta--ruutu .ml-artcta__title { color: #fff; }
.ml-artcta--lippu .ml-artcta__title { color: var(--wp--preset--color--brand-teal); }
/* Hover = underline the title AND slide its arrow 2px right. The design pairs
   `group-hover:underline` on the title with `group-hover:translate-x-0.5
   transition-transform` on the ArrowUpRight (translate-x-0.5 = 0.125rem = 2px, Tailwind's
   default transition 150ms ease) — the arrow half was missing here, so the card underlined
   but the arrow sat still. */
.ml-artcta:hover .ml-artcta__title { text-decoration: underline; }
.ml-artcta__title svg { transition: transform 0.15s ease; }
.ml-artcta:hover .ml-artcta__title svg { transform: translateX(2px); }
.ml-artcta__logo { height: 1.5rem; width: auto; flex: 0 0 auto; }
.ml-artcta--ruutu .ml-artcta__logo { filter: brightness(0) invert(1); }
.ml-artcta--lippu .ml-artcta__logo { filter: brightness(0); }

/* Jaa artikkeli */
.ml-share { display: flex; align-items: center; gap: 0.5rem; }
.ml-share a, .ml-share button { display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; border-radius: 9999px; border: 1px solid var(--wp--preset--color--border); color: var(--wp--preset--color--brand-teal); background: transparent; cursor: pointer; transition: background .15s, border-color .15s; }
.ml-share a:hover, .ml-share button:hover { background: var(--wp--preset--color--brand-lime); border-color: var(--wp--preset--color--brand-lime); }
.ml-share .ml-share-copy.is-copied { background: var(--wp--preset--color--brand-lime); border-color: var(--wp--preset--color--brand-lime); }

/* Panel 2 — Lue myös (teal top border) */
.ml-article-related { margin-top: 2.5rem; background: var(--wp--preset--color--card); border-radius: 1rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); padding: 2rem 1.25rem; border-top: 4px solid var(--wp--preset--color--brand-teal); }
@media (min-width: 640px) { .ml-article-related { padding-inline: 2rem; } }
@media (min-width: 768px) { .ml-article-related { margin-top: 3.5rem; padding: 2.5rem; } }
/* Reuses .ml-ng-card styling but NOT .ml-ng-grid's hide-on-mobile — the design's Lue myös
 * grid shows 1/2/3 cols at all widths (grid-cols-1 sm:2 lg:3). Force display + columns. */
.ml-article-related__head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
/* Article.tsx:232 `text-xl md:text-2xl font-extrabold` — no tracking-tight. `normal` has to be
 * stated, not merely omitted: theme.json sets letterSpacing -0.025em on EVERY h2, which was
 * shrinking "Lue myös" by ~5px against the design. */
.ml-article-related__title { display: inline-flex; align-items: center; gap: 0.75rem; margin: 0; font-size: 1.25rem; font-weight: 800; line-height: 1.75rem; letter-spacing: normal; color: var(--wp--preset--color--brand-teal); }
.ml-article-related__title::before { content: ""; width: 2rem; height: 0.25rem; border-radius: 9999px; background: var(--wp--preset--color--brand-lime); flex: none; }
@media (min-width: 768px) { .ml-article-related__title { font-size: 1.5rem; line-height: 2rem; } }
.ml-article-related__grid { display: grid; grid-template-columns: 1fr; gap: 2rem 1.5rem; }
@media (min-width: 640px) { .ml-article-related__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1024px) { .ml-article-related__grid { grid-template-columns: repeat(3, 1fr); } }

/* "Lue myös" card — Article.tsx:248-274. Its OWN card, not the front-page grid card:
 * the order is media → title → meta (the grid card is media → meta → title + excerpt),
 * there is no excerpt, and the meta row ends in an arrow that fades + slides in on hover. */
.ml-rc { display: block; text-decoration: none; }

.ml-rc-media {
	position: relative;
	display: block;
	aspect-ratio: 16 / 10;
	overflow: hidden;
	border-radius: 0.5rem;
	background: var(--wp--preset--color--muted);
}

.ml-rc-media img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 0.5s;
}

.ml-rc:hover .ml-rc-media img { transform: scale(1.05); }

/* group-hover:bg-brand-teal/20 over a transparent base */
.ml-rc-veil {
	position: absolute;
	inset: 0;
	background: hsl(182 100% 9% / 0);
	transition: background-color 0.3s;
}

.ml-rc:hover .ml-rc-veil { background: hsl(182 100% 9% / 0.2); }

/* mt-4 text-base md:text-lg font-extrabold leading-snug — an unprefixed leading-snug beats
 * text-base's paired 24px (so 22px), while md:text-lg is a responsive variant and wins with
 * its own 28px. */
.ml-rc-title {
	display: block;
	margin-top: 1rem;
	font-size: 1rem;
	line-height: 1.375;
	font-weight: 800;
	color: var(--wp--preset--color--foreground);
	overflow-wrap: anywhere;
}

@media (min-width: 768px) { .ml-rc-title { font-size: 1.125rem; line-height: 1.75rem; } }

.ml-rc-title-u { border-bottom: 2px solid transparent; transition: border-color 0.15s; }
.ml-rc:hover .ml-rc-title-u { border-color: var(--wp--preset--color--brand-lime); }

/* mt-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground */
.ml-rc-meta {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-top: 0.75rem;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--wp--preset--color--muted-foreground);
}

/* The related card pill follows the shared .ml-ng-pill size since the drop
 * (Article.tsx:266 — text-xs md:text-[13px], tinted). */
.ml-rc-pill { letter-spacing: 0.05em; }

/* ArrowRight: opacity-0 -translate-x-1 → opacity-100 translate-x-0, transition-all 300ms.
 * This is the hover affordance the grid-card reuse dropped entirely. */
.ml-rc-arrow {
	display: inline-flex;
	align-items: center;
	color: var(--wp--preset--color--brand-teal);
	opacity: 0;
	transform: translateX(-0.25rem);
	transition: opacity 0.3s, transform 0.3s;
}

.ml-rc-arrow .ml-icon { width: 1rem; height: 1rem; }

.ml-rc:hover .ml-rc-arrow { opacity: 1; transform: translateX(0); }

@media (prefers-reduced-motion: reduce) {
	.ml-rc-media img,
	.ml-rc-veil,
	.ml-rc-arrow { transition: none; }
}
/* "Kaikki artikkelit" outline pill (mirrors the design's outline button) */
.ml-cta-outline { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; border-radius: 9999px; border: 2px solid var(--wp--preset--color--brand-teal); font-size: 0.75rem; line-height: 1rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; color: var(--wp--preset--color--brand-teal); text-decoration: none; white-space: nowrap; transition: background .15s, border-color .15s; }
.ml-cta-outline:hover { background: var(--wp--preset--color--brand-lime); border-color: var(--wp--preset--color--brand-lime); }

/* ══════════════════════════════════════════════════════════════════════════
 * Content pages (Sisältösivut) — BlockRenderer "page" variant, ported 1:1 from
 * source/src/components/content/BlockRenderer.tsx + ContentPage.tsx.
 *
 * Design layout: a full-bleed band with px-4/6 outer padding; inside it a
 * centered 1640 track; every block is LEFT-FLUSH within that track, capped at
 * 760 (text — Narrow), 1100 (Wide: table/gallery/columns) or 1640 (Full: image
 * full / logoGrid). NOT WP's centered constrained layout — so the body is
 * emitted as a core/group.ml-content (alignfull) that re-caps here. The theme
 * has no global box-sizing reset, so scope border-box (same trick as .ml-nc).
 * ════════════════════════════════════════════════════════════════════════ */
.ml-content, .ml-content * { box-sizing: border-box; }
/* .ml-content is alignfull (to escape page.html's 760px constrained post-content) and caps
 * itself at 1640+padding, centered — so its 760/1100 blocks sit left-flush UNDER the page-hero
 * H1 (design: content column left-flush within a centered 1640 container). BUT core's
 * `.has-global-padding > .alignfull` breakout rule (0,2,0) overrides margin-inline:auto with a
 * negative margin, pulling the whole block to the bleed edge → content landed ~116px too far left
 * (x24 vs the design's x140). Force the centering back (!important beats the un-important core rule
 * regardless of source order). max-width ALSO needs !important: on the front-end my cap wins by
 * source order, but the BLOCK EDITOR injects `.alignfull { max-width: none }` AFTER the theme's
 * editor styles, so at equal specificity it won and .ml-content filled the whole canvas — the
 * content never centered in the editor (always flush-left) no matter how wide the canvas. With
 * !important the 1688 cap holds in both, so the editor centers exactly like live at the same width. */
.ml-content { width: 100%; max-width: calc(1640px + 3rem) !important; margin-inline: auto !important; padding-inline: 1rem; padding-block: 1.5rem 2.5rem; }
@media (min-width: 640px) { .ml-content { padding-inline: 1.5rem; } }
/* Intermediate-width rail alignment (user bug 2026-07-17: "title is not aligned correct when
 * window is different sized"). The margin-inline:auto above centres .ml-content INSIDE the
 * root-padded post-content box (theme.json root padding = 1.5rem/side at ≥640), while the
 * page-hero's .ml-container computes from the full bleed — so between 640 and ~1720px the
 * content sat 24px right of the hero title (Δ=0 only at 390/1920, the gated widths, which is
 * how it shipped). Fix: the centring margin bottoms out at −1.5rem (exactly the root padding),
 * making .ml-content's left edge match .ml-container at EVERY width: 24px from the viewport
 * edge below the 1688px cap, centred above it (140px @1920 — unchanged). Same !important +
 * later source order beats the base rule. Mobile (<640) already aligns (16px) — untouched. */
@media (min-width: 640px) {
	/* width:auto (not the base 100%) so BOTH margins resolve — under width:100% the box is
	 * overconstrained and the right margin is recomputed, leaving the right edge 2×padding short. */
	.ml-content { width: auto; margin-inline: max(calc((100% - 1640px - 3rem) / 2), -1.5rem) !important; }
	/* Nested pieces (Kuva/Taulukko-osio inside a Sisältöalue) keep the existing zero-margin reset. */
	.ml-content .ml-content { width: 100%; margin-inline: 0 !important; }
}
@media (min-width: 768px) { .ml-content { padding-block: 2.5rem 4rem; } }
/* Mobile (<640): .ml-content sits INSIDE page.html's constrained post-content, which already
 * carries the ~1rem root padding at mobile — so .ml-content's own 1rem padding double-inset the
 * content to 32px vs the design's single px-4 (16px). At ≥640 the max-width cap (1688) absorbs the
 * arithmetic (content lands at the design's x140 @1920), but below the cap the double padding shows
 * (content 326px wide vs the design's 358 → dense tables wrap an extra line → +12% mobile height).
 * Drop .ml-content's own inline padding at mobile so the single parent padding matches px-4. */
@media (max-width: 639.98px) { .ml-content { padding-inline: 0; } }
/* Intro lead paragraph (ContentPage:26 `text-lg md:text-xl text-muted-foreground
 * leading-relaxed`): 18px/lh1.625 below md, 20px/lh28px (md:text-xl's own 1.75rem
 * line-height wins over leading-relaxed) at md, muted-teal. MUST out-specify
 * `.ml-content p` (0,1,1) — a bare `.ml-content__intro` (0,1,0) loses, so the
 * intro was rendering as 16px foreground body text. Scope it under .ml-content
 * AND the p element → (0,2,1). */
.ml-content p.ml-content__intro { max-width: 760px; margin: 0.5rem 0 0; font-size: 1.125rem; line-height: 1.625; color: var(--wp--preset--color--muted-foreground); }
@media (min-width: 768px) { .ml-content p.ml-content__intro { font-size: 1.25rem; line-height: 1.75rem; } }
/* In the design the intro is its OWN band directly under the hero (ContentPage.tsx: a
 * px-4 max-w-1640 wrapper with pt-2), NOT part of the content area — the WP port nests it as
 * the content area's first child, which displaced it DOWN by .ml-content's top padding
 * (h1→intro 80px vs the design's 40 at md; 56 vs 32 at mobile). Pull a leading intro up by
 * exactly that padding, and give back the same amount below so the rest of the page stays put
 * (following siblings are normal flow). The bottom margin is authored as the FULL design
 * intro→next gap (40px mobile / 56px md) because it COLLAPSES with the next block's own
 * margin-top (1rem) — max() wins, so 2.5rem/3.5rem here yields exactly 40/56. Measured
 * 2026-07-17 (kuukauden-tahdistot, 390/768/1440): h1→intro 32/40/40 and intro→next 40/56/56 —
 * all Δ0 vs design. Scoped to :first-child so a mid-page intro is untouched. */
.ml-content > p.ml-content__intro:first-child { margin-top: -1rem; margin-bottom: 2.5rem; }
@media (min-width: 768px) { .ml-content > p.ml-content__intro:first-child { margin-top: -2rem; margin-bottom: 3.5rem; } }

/* Width caps — left-flush within the 1640 track (margin-right auto, not centered).
 * `.ml-content` is a FLOW group so children stay left-flush (a constrained group force-
 * centres them). Front-end: the saved markup carries .alignwide/.alignfull, so those rules
 * set 1100/full. Editor: a flow group hides wide/full alignment, so the block would render
 * WITHOUT the class and shrink to the 760 cap — visibly narrower than live (e.g. the
 * erotuomarit table). Fix: give the design's always-wide/full block TYPES their widths by
 * element, so editor and live match regardless of the alignment class. */
.ml-content > * { max-width: 760px; margin-inline: 0 auto; }
.ml-content > .alignwide { max-width: 1100px; }
.ml-content > .alignfull { max-width: none; }
/* A content piece (Taulukko-osio / Kuva) wraps ITSELF in .ml-content so it renders
 * correctly when dropped straight onto the page (full container + content styles).
 * When it's instead nested inside a Sisältöalue (.ml-content within .ml-content),
 * neutralise the inner container box so padding/width/margin don't double up — the
 * descendant styling (.ml-content h2, table, figure, a…) still applies either way. */
.ml-content .ml-content { max-width: none !important; margin-inline: 0 !important; padding: 0 !important; }
/* Editor-parity: BlockRenderer renders tables + galleries "wide" (1100) and the logo grid
 * "full" — mirror that by element so the flow editor (no alignment class) matches live. */
.ml-content .wp-block-table,
.ml-content .wp-block-gallery { max-width: 1100px; }
.ml-content .ml-logogrid { max-width: none; }
/* Korttiruudukko sits on the wide (1100) rail tier like tables/galleries — left-flush within the
 * .ml-content band, so three 18rem cards fit in one row on desktop and the grid still wraps to
 * 2/1 columns via its native minimumColumnWidth. Out-specifies `.ml-content > *`'s 760 cap (0,2,0
 * vs 0,1,0); the `margin-inline: 0 auto` from that rule keeps it left-flush. */
.ml-content .ml-cardgrid { max-width: 1100px; }

/* Block typography (mirrors .ml-article__body + BlockRenderer).
 * Per-level font-size + line-height mirror Tailwind's text-N and md:text-N pairs
 * in BlockRenderer (h2 = text-2xl then md:text-3xl, h3 = text-xl then md:text-2xl,
 * h4 = text-lg then md:text-xl) — a flat line-height 1.2 mismatched h3/h4 and h2 below md.
 * Explicit type selectors give (0,1,1) — NOT :where() at (0,1,0), which ties (and loses
 * by order to) core's flow-layout margin-block-start reset when blockGap is "0", zeroing
 * the design's mt-10 heading gap. */
.ml-content h2, .ml-content h3, .ml-content h4 { margin: 2.5rem 0 0.75rem; font-weight: 800; color: var(--wp--preset--color--brand-teal); letter-spacing: -0.025em; }
.ml-content h2 { font-size: 1.5rem; line-height: 2rem; }
.ml-content h3 { font-size: 1.25rem; line-height: 1.75rem; }
.ml-content h4 { font-size: 1.125rem; line-height: 1.75rem; }
@media (min-width: 768px) { .ml-content h2 { font-size: 1.875rem; line-height: 2.25rem; } .ml-content h3 { font-size: 1.5rem; line-height: 2rem; } .ml-content h4 { font-size: 1.25rem; line-height: 1.75rem; } }
.ml-content p { margin: 1rem 0; font-size: 15px; line-height: 1.5; color: var(--wp--preset--color--foreground); } /* design body p inherits leading 1.5 (22.5px @15px) */
@media (min-width: 768px) { .ml-content p { font-size: 1rem; line-height: 1.5rem; } } /* md:text-base resets lh to 1.5rem (24px) — matches BlockRenderer body p */
/* Lead body paragraph = identical typography to the intro (BlockRenderer renders
 * a lead paragraph with the same text-lg/md:text-xl/muted/leading-relaxed classes as
 * ContentPage's intro): base lh 1.625, md lh 1.75rem (28px) — NOT a flat 1.7. */
.ml-content p.is-style-lead { font-size: 1.125rem; color: var(--wp--preset--color--muted-foreground); line-height: 1.75rem; } /* text-lg pair 18/28 (measured; text-lg's lh wins over leading-relaxed) */
@media (min-width: 768px) { .ml-content p.is-style-lead { font-size: 1.25rem; line-height: 1.75rem; } }
.ml-content a:where(:not(.wp-element-button)) { font-weight: 600; color: var(--wp--preset--color--brand-teal); text-decoration: underline; text-decoration-color: var(--wp--preset--color--brand-lime); text-decoration-thickness: 2px; text-underline-offset: 4px; transition: text-decoration-color .15s; }
.ml-content a:where(:not(.wp-element-button)):hover { text-decoration-color: var(--wp--preset--color--brand-teal); }

/* Lists — lime round bullets / teal decimal markers. */
.ml-content ul, .ml-content ol { margin: 1rem 0; padding-left: 1.25rem; }
.ml-content ul { list-style: none; padding-left: 0; }
.ml-content ul li { position: relative; padding-left: 1.25rem; margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
.ml-content ul li::before { content: ""; position: absolute; left: 0; top: 0.6em; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--wp--preset--color--brand-lime); } /* round dot — matches the design bullet */
.ml-content ol { list-style: decimal; padding-left: 1.5rem; }
.ml-content ol li { margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
.ml-content ol li::marker { color: var(--wp--preset--color--brand-teal); font-weight: 800; }
@media (min-width: 768px) { .ml-content ul li, .ml-content ol li { font-size: 1rem; line-height: 1.5rem; } } /* md:text-base resets lh to 1.5rem (24px) — matches BlockRenderer list items */

/* Quote — left lime rule, big extrabold teal. */
.ml-content blockquote { margin: 2rem 0; padding-left: 1.5rem; border-left: 4px solid var(--wp--preset--color--brand-lime); }
.ml-content blockquote p { font-size: 1.25rem; font-weight: 800; line-height: 1.375; color: var(--wp--preset--color--brand-teal); }
/* md:text-2xl is a RESPONSIVE variant, so it beats the unprefixed leading-snug and
 * brings its own 32px — keeping the 1.375 ratio here made the quote 33px and every
 * sitaatti ~6px taller than the design. */
@media (min-width: 768px) { .ml-content blockquote p { font-size: 1.5rem; line-height: 2rem; } }
/* The design wraps the quote text in literal double quotes — BlockRenderer.tsx:165
 * renders `"{block.text}"`. They are presentation, not content: adding them here keeps
 * the stored quote clean (the customer types the text, never the marks, and editing it
 * can never lose or double them). first/last-of-type so a multi-paragraph core/quote
 * still gets exactly one opening and one closing mark. */
.ml-content blockquote p:first-of-type::before { content: '"'; }
.ml-content blockquote p:last-of-type::after { content: '"'; }
.ml-content blockquote cite::before { content: '\2014\a0'; }
.ml-content blockquote cite { display: block; margin-top: 0.75rem; font-style: normal; font-size: 0.875rem; line-height: 1.25rem; color: var(--wp--preset--color--muted-foreground); }

/* ── Nosto (core/pullquote) ─────────────────────────────────────────────────
 * Reported broken (customer 2026-08-04: "nosto block is not working correctly"),
 * and measured so: the block came out as a Lainaus with the wrong alignment and
 * two big holes around it. Two causes, both the same shape as the core/video gap:
 *
 *  1. wp-block-library gives core/pullquote `padding: 3em 0` + `text-align:center`
 *     but its RULES (border-top/bottom) live in wp-block-library-THEME, which this
 *     theme does not load. Measured: padding 96px top and bottom, border-width 0 —
 *     96 px of empty space above and below with nothing drawn in it.
 *  2. The quote rules above are descendant selectors (`.ml-content blockquote`), and
 *     core/pullquote nests a <blockquote> inside its <figure> — so the Nosto also
 *     inherited the Lainaus's 4px LEFT bar while its own text stayed centred: a left
 *     accent on centred text, and nothing else to tell it from a Lainaus.
 *
 * So the Nosto gets its own rules here, in the design's existing vocabulary (lime
 * accent, teal extrabold quote text, muted cite, the same typographic quote marks) —
 * as the pull-out it is: rules above and below, centred, one type step up from the
 * Lainaus. Everything is set explicitly rather than inherited from the quote rules,
 * because at page top level (`> :is(blockquote, .wp-block-quote)`, a direct-child
 * selector) the nested blockquote is not matched at all and would otherwise be bare.
 * Mirrored in editor-page.css / editor-article.css — the canvas must show this too. */
:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote {
	margin: 2.5rem 0;
	padding: 2.5rem 0;
	border-top: 2px solid var(--wp--preset--color--brand-lime);
	border-bottom: 2px solid var(--wp--preset--color--brand-lime);
	text-align: center;
}
/* Undo the Lainaus bar/inset the descendant quote rules leak onto the inner blockquote. */
:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote blockquote {
	margin: 0;
	padding-left: 0;
	border-left: 0;
}
:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote p {
	margin: 0;
	font-size: 1.5rem;
	font-weight: 800;
	line-height: 1.3;
	color: var(--wp--preset--color--brand-teal);
}
@media (min-width: 768px) {
	:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote p { font-size: 1.875rem; line-height: 2.25rem; }
}
/* Same presentation-only quote marks as the Lainaus (the stored text carries none). */
:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote p:first-of-type::before { content: '"'; }
:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote p:last-of-type::after { content: '"'; }
:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote cite {
	display: block;
	margin-top: 1rem;
	font-style: normal;
	font-size: 0.875rem;
	line-height: 1.25rem;
	color: var(--wp--preset--color--muted-foreground);
}
:is(.ml-content, .ml-article__body, :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content) .wp-block-pullquote cite::before { content: '\2014\a0'; }

/* Image / figure. */
.ml-content figure { margin: 2rem 0; }
/* `max-width`, not `width` (customer 2026-08-03: "image size cant be changed").
 * The design's BlockRenderer image is `className="w-full h-auto"`, so the port set
 * width:100% — faithful for the design's own images, which are all wider than the
 * column, but it made WP's "Kuvan koko" control inert: picking Pikkukuva rendered a
 * 150-px thumbnail UPSCALED to the full 760 column (measured 760×760, blurry).
 * max-width keeps every image that is wider than the column at exactly the column
 * width — i.e. identical output for all existing content (swept: the only image
 * narrower than its box on the whole site was a partner logo, off by 1 px) — while a
 * deliberately smaller resolution now renders at its own size. The resize handle
 * already worked, because that writes an inline width style. */
.ml-content figure img { max-width: 100%; height: auto; }
/* An aligned image is an explicit "make this big": keep the stretch there, so an
 * alignwide/alignfull image still fills its slot regardless of the file's own width. */
.ml-content .wp-block-image:is(.alignwide, .alignfull) img { width: 100%; }
.ml-content figcaption { margin-top: 0.5rem; font-size: 0.875rem; font-style: italic; color: var(--wp--preset--color--muted-foreground); }
/* Gallery — BlockRenderer renders `grid grid-cols-2 md:grid-cols-3 gap-3` with `w-full h-auto`
 * (uncropped) images. WP core's gallery flex-layout CSS (wp-block-library) is NOT enqueued in this
 * theme, so the .is-layout-flex columns never applied and the nested figures stacked at full width
 * (kk-pelaajat ballooned to ~10000px). Lay it out as the design grid; the extra `.is-layout-flex`
 * selector out-specifies core's (0,3,0 > 0,2,0) in case that CSS ever loads. */
.ml-content .wp-block-gallery.is-layout-flex,
.ml-content .wp-block-gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; margin: 2rem 0; align-items: start; }
/* Editor-parity: in the CANVAS the layout-support CSS emitted for the theme.json global
 * blockGap:0 lands after the editor copy of this sheet and zeroes the gallery gap (measured:
 * grid held, gap 0). Pin the design's gap-3 (12px) inside the editor; live is unaffected. */
.editor-styles-wrapper .ml-content .wp-block-gallery.is-layout-flex,
.editor-styles-wrapper .ml-content .wp-block-gallery { gap: 0.75rem !important; }
@media (min-width: 768px) {
	.ml-content .wp-block-gallery.is-layout-flex,
	.ml-content .wp-block-gallery { grid-template-columns: repeat(3, 1fr); }
}
.ml-content .wp-block-gallery .wp-block-image { margin: 0; width: auto; }
/* max-width + centring, not `width: 100%` (customer 2026-08-03: "the 3 image gallery
 * doesn't work if the images are smaller"). Every image the design's own galleries hold
 * is wider than a cell, so for them the two are identical — but a 150-px logo dropped
 * into a gallery was stretched to the full cell and rendered soft. Now a small image
 * keeps its own size and sits centred in its cell, so a mixed-size gallery still reads
 * as an even grid. */
.ml-content .wp-block-gallery .wp-block-image img { max-width: 100%; height: auto; display: block; margin-inline: auto; }
/* Editor-parity: wp-block-library (editor-only here) crops is-cropped gallery images to equal
 * squares — the design (BlockRenderer "gallery") is UNCROPPED h-auto, which is what the front
 * end renders since that CSS never loads there. Neutralise the crop in the editor so the canvas
 * matches live whatever the gallery's imageCrop attr says. */
/* `!important` is REQUIRED, not defensive: core's rule is
 * `.wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) img`
 * — the `:not(#id)` is an ID-specificity hack, so no class selector can out-rank it. Measured on the
 * customer's test page: a 150-px logo in a cropped gallery was forced to `width:100%; height:100%`
 * and rendered as a soft 359-px square. (The older note in this file — "wp-block-library is not
 * enqueued here" — no longer holds for the gallery: core loads per-block styles on demand, so the
 * block's own CSS IS present on any page that contains one.)
 * `width: auto` + `max-width: 100%` is what core's UNCROPPED rule does, i.e. fill the cell when the
 * file is bigger than it, keep its own size when smaller. */
.ml-content .wp-block-gallery.is-cropped .wp-block-image img { height: auto !important; width: auto !important; max-width: 100%; object-fit: unset; margin-inline: auto; }
.ml-content .wp-block-gallery.is-cropped .wp-block-image { height: auto !important; }
/* Article gallery — NOT the design grid (customer 2026-08-14: "the gallery needs to work
 * like it was, just add a bit of gap between the images"). Core's flex layout stays: in an
 * article every photo claims a full row (`.ml-article__body figure img { width: 100% }`),
 * so a gallery reads as stacked full-width images, rounded like the article's single images.
 * Only the GAP is ours — the theme.json global blockGap:0 emits a per-instance
 * `gap: 0` at (0,1,0), and core's own `.wp-block-gallery.has-nested-images` gap ties at
 * (0,2,0), so the `.is-layout-flex` variant (0,3,0) settles both. Same 12px as the design
 * galleries (gap-3). Canvas mirror: editor-article.css (blockGap:0 needs !important there). */
.ml-article__body .wp-block-gallery.is-layout-flex,
.ml-article__body .wp-block-gallery { gap: 0.75rem; }
.ml-content hr { margin: 2.5rem 0; border: 0; border-top: 1px solid var(--wp--preset--color--border); }

/* Video / embed — BlockRenderer "embed": lazy iframe in a 16:9 box (aspect-video, Wide=1100px)
 * + optional italic caption (mt-2 text-sm text-muted-foreground italic → the .ml-content figcaption
 * rule above). WP's wp-block-library responsive-embed CSS is NOT enqueued in this theme (see the
 * gallery note above), so replicate its aspect-ratio frame here — scoped to .ml-content and keyed on
 * the wp-embed-aspect-* / wp-has-aspect-ratio classes the embed block writes into its saved markup
 * (so this never touches non-video rich embeds). YouTube/Vimeo resolve to 16:9. */
.ml-content .wp-block-embed { max-width: 1100px; }
/* The ASPECT FRAME is unscoped on purpose, unlike the 1100 width tier above.
 * Since the video patterns collapsed into one (customer 2026-08-04: "cant the video upotus and
 * oma video be one pattern that handles those"), the pattern carries core/video and an EMBED
 * arrives the way WordPress natively makes one — paste a YouTube/Vimeo/Ruutu address and the
 * block appears wherever the caret was, which is often NOT inside a .ml-content band. Scoped to
 * .ml-content, these rules missed that block entirely and the iframe collapsed to nothing (the
 * frame is the only thing giving it height — wp-block-library's responsive-embed CSS is not
 * enqueued here). Width still belongs to each context's own tiers, so only the frame is shared. */
.wp-block-embed__wrapper { position: relative; }
.wp-has-aspect-ratio .wp-block-embed__wrapper::before { content: ""; display: block; padding-top: 50%; }
.wp-embed-aspect-21-9 .wp-block-embed__wrapper::before { padding-top: 42.85%; }
.wp-embed-aspect-18-9 .wp-block-embed__wrapper::before { padding-top: 50%; }
.wp-embed-aspect-16-9 .wp-block-embed__wrapper::before { padding-top: 56.25%; }
.wp-embed-aspect-4-3 .wp-block-embed__wrapper::before { padding-top: 75%; }
.wp-embed-aspect-1-1 .wp-block-embed__wrapper::before { padding-top: 100%; }
.wp-embed-aspect-9-16 .wp-block-embed__wrapper::before { padding-top: 177.78%; }
.wp-embed-aspect-1-2 .wp-block-embed__wrapper::before { padding-top: 200%; }
.wp-has-aspect-ratio .wp-block-embed__wrapper iframe,
.wp-has-aspect-ratio .wp-block-embed__wrapper object,
.wp-has-aspect-ratio .wp-block-embed__wrapper embed { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Self-hosted video (core/video — an uploaded mp4, the "Video (oma tiedosto)"
 * pattern). Same wide tier + rounded frame as an embed, but there is no
 * wp-embed-aspect-* class to key on: a <video> carries its own intrinsic ratio,
 * so width:100% + height:auto is the whole job. Without this the element renders
 * at its default 300×150 (wp-block-library's video CSS is not enqueued in this
 * theme — see the gallery note above) and the caption loses the figcaption look. */
.ml-content .wp-block-video { max-width: 1100px; margin: 2rem 0; }
/* Unscoped for the same reason as the embed frame above: a core/video dropped outside a
 * .ml-content band otherwise renders at the 300×150 default. Width stays per-context. */
.wp-block-video video { display: block; width: 100%; height: auto; border-radius: 0.75rem; background: #000; }

/* Buttons — pill (teal fill / teal outline), uppercase. */
.ml-content .wp-block-buttons { margin: 1.5rem 0; gap: 0.75rem; }
.ml-content .wp-block-button__link { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.625rem 1.25rem; border-radius: 9999px; font-size: 0.875rem; line-height: 1.25rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.025em; background: var(--wp--preset--color--brand-teal); color: #fff; transition: background .2s, color .2s, transform .2s; }
.ml-content .wp-block-button__link:hover { background: var(--wp--preset--color--brand-lime); color: var(--wp--preset--color--brand-teal); transform: translateY(-2px); }
.ml-content .is-style-outline .wp-block-button__link, .ml-content .wp-block-button.is-style-outline .wp-block-button__link { background: transparent; color: var(--wp--preset--color--brand-teal); border: 2px solid var(--wp--preset--color--brand-teal); }
/* The styleguide outline rule (.wp-block-button.is-style-outline > …, 12px) out-specifies the
 * content button rule above — content outline buttons are the design's text-sm (14/20). */
.ml-content .wp-block-button.is-style-outline > .wp-block-button__link { font-size: 0.875rem; line-height: 1.25rem; letter-spacing: 0.025em; }
.ml-content .is-style-outline .wp-block-button__link:hover { background: var(--wp--preset--color--brand-teal); color: #fff; }
/* The design's ArrowUpRight (lucide), as a mask so every copy of it inherits the
 * surrounding colour. Named once because four rules draw it — the content button,
 * the inline text link, the bare-page mirror of that link, and the off-site rule
 * functions.php injects (it is the only place that knows this site's own host). */
:root { --ml-arrow-mask: 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="M7 7h10v10"/><path d="M7 17 17 7"/></svg>'); }

/* External content buttons carry the design's ↗ (BlockRenderer.tsx: external →
 * <ArrowUpRight w-4 h-4>). Keyed on target="_blank" so internal buttons stay
 * plain, matching the source. currentColor mask → follows the label colour. */
.ml-content .wp-block-button__link[target="_blank"]::after {
	content: "";
	width: 1rem;
	height: 1rem;
	flex-shrink: 0;
	background: currentColor;
	-webkit-mask: var(--ml-arrow-mask) no-repeat center / contain;
	mask: var(--ml-arrow-mask) no-repeat center / contain;
}

/* ── The ↗ on an INLINE text link that leaves the page ─────────────────────
 * BlockRenderer.tsx renderInline gives every external href an <ArrowUpRight
 * w-3.5 h-3.5 text-brand-teal>. "External" in the design is just `^https?://`,
 * because its own internal links are React <Link to="/…"> routes and never carry
 * a scheme. In WordPress that test does not port: the link search inserts an
 * ABSOLUTE url for an internal page too, so `^http` alone would arrow half the
 * sivukartta. So the port reads "leaves the page" from what the editor can
 * actually express about a link (customer 2026-08-13, Materiaali: "if i want to
 * add link it doesnt have that arrow icon as others in that page do" — every
 * link already on that page is a new-tab PDF, and a fresh one matched neither):
 *   · target="_blank" — the link popover's "Avaa uuteen välilehteen" toggle
 *   · a file href — a PDF/Office/zip download, which is what Materiaali IS,
 *     and the one case that stays same-host once the file lives in the media
 *     library (…/wp-content/uploads/…), so no host test could catch it
 *   · another host — added by functions.php; the stylesheet cannot know the
 *     site's own host and this theme may not hard-code one (§ self-contained)
 * Scoped to p/li/td text so image links (logogrid/gallery <figure>) and buttons
 * stay plain — matches the source, where only inline text runs get the arrow.
 * The bare-page mirror of this pair lives in § BARE PAGE CONTENT; keep the three
 * selector lists in step. */
/* The design's anchor is `inline-flex items-baseline gap-0.5` (BlockRenderer.tsx renderInline)
 * — one unbreakable box, so a long link never wraps mid-label. Reproduce the display too, not
 * just the arrow: as a plain inline the anchor measured 18px tall vs the design's 22.5. */
.ml-content :is(p, li, td) a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button)) {
	display: inline-flex;
	align-items: baseline;
	gap: 0.125rem;
}
.ml-content :is(p, li, td) a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button))::after {
	content: "";
	width: 0.875rem;
	height: 0.875rem;
	align-self: center; /* the design's `self-center` on the ArrowUpRight */
	background: currentColor;
	-webkit-mask: var(--ml-arrow-mask) no-repeat center / contain;
	mask: var(--ml-arrow-mask) no-repeat center / contain;
}

/* Callout — tone-coded left-border card (info teal / warning amber / success emerald). */
.ml-content .ml-callout { display: grid; grid-template-columns: 1.25rem 1fr; column-gap: 0.75rem; align-items: start; margin: 1.5rem 0; padding: 1.25rem; border-left: 4px solid; border-radius: 0 0.375rem 0.375rem 0; --ml-callout-icon: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23fff'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'/%3E%3Cpath%20d='M12%2016v-4'/%3E%3Cpath%20d='M12%208h.01'/%3E%3C/svg%3E"); }
.ml-content .ml-callout p { margin: 0; font-size: 15px; line-height: 1.625; }
.ml-content .ml-callout p.ml-callout__title { font-size: 1rem; line-height: 1.5rem; } /* design: bare font-extrabold p inherits 16/24 */
.ml-content .ml-callout__title { font-weight: 800; margin-bottom: 0.25rem !important; }
/* Tone icon — lucide (v0.462) injected as a currentColor CSS mask, 1:1 with
 * BlockRenderer.tsx's <Icon className="w-5 h-5 shrink-0 mt-0.5" />. Info is the
 * base default; warning/success swap the mask. Decorative → CSS, not markup. */
.ml-content .ml-callout::before { content: ""; grid-column: 1; grid-row: 1; width: 1.25rem; height: 1.25rem; margin-top: 0.125rem; background-color: currentColor; -webkit-mask: var(--ml-callout-icon) center / contain no-repeat; mask: var(--ml-callout-icon) center / contain no-repeat; }
.ml-content .ml-callout > * { grid-column: 2; }
.ml-content .ml-callout--info { background: color-mix(in srgb, var(--wp--preset--color--brand-teal) 5%, transparent); border-color: color-mix(in srgb, var(--wp--preset--color--brand-teal) 20%, transparent); color: var(--wp--preset--color--brand-teal); }
.ml-content .ml-callout--warning { background: #fffbeb; border-color: #fcd34d; color: #78350f; --ml-callout-icon: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23fff'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m21.73%2018-8-14a2%202%200%200%200-3.48%200l-8%2014A2%202%200%200%200%204%2021h16a2%202%200%200%200%201.73-3'/%3E%3Cpath%20d='M12%209v4'/%3E%3Cpath%20d='M12%2017h.01'/%3E%3C/svg%3E"); }
.ml-content .ml-callout--success { background: #ecfdf5; border-color: #6ee7b7; color: #064e3b; --ml-callout-icon: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23fff'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'/%3E%3Cpath%20d='m9%2012%202%202%204-4'/%3E%3C/svg%3E"); }

/* Table — teal head, zebra rows, scrollable. overflow-x:auto matches the design's
 * wrapper (<div class="overflow-x-auto"> in BlockRenderer): a table too wide for the
 * column scrolls horizontally instead of the page breaking. */
.ml-content .wp-block-table { margin: 2rem 0; overflow-x: auto; }
/* Right-edge fade while the table has more to the right (scroll-fade.js toggles is-fade-end live;
 * the same rule as the React elements' tables, tokens.css .ml-hscroll-fade). A mask, not an
 * overlay: the figure IS the scroller, so an ::after would scroll away with the cells, and the
 * mask fades to whatever background the table sits on without a per-surface colour. */
.wp-block-table.is-fade-end {
	-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
	mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
}
.ml-content .wp-block-table table { width: 100%; border-collapse: collapse; font-size: 0.875rem; line-height: 1.25rem; border: 1px solid var(--wp--preset--color--border); border-radius: 0.375rem; overflow: hidden; }
/* Cells keep natural wrapping (wrap at spaces) but DON'T force-break long tokens —
 * the .ml-content net's word-break:break-word would otherwise wrap an over-long
 * string to fit the container, collapsing the other columns to unreadable slivers.
 * Letting it grow instead makes the whole table scroll (design behaviour). */
.ml-content .wp-block-table th, .ml-content .wp-block-table td { overflow-wrap: normal; word-break: normal; }
.ml-content .wp-block-table thead { background: var(--wp--preset--color--brand-teal); color: #fff; }
/* Line-heights are the design's Tailwind pairs (text-xs = 12/16, text-sm = 14/20) —
 * the inherited 1.5 rendered every row 1px taller (th 3px), drifting long tables. */
.ml-content .wp-block-table th { text-align: left; padding: 0.75rem 1rem; font-weight: 800; text-transform: uppercase; font-size: 0.75rem; line-height: 1rem; letter-spacing: 0.05em; }
.ml-content .wp-block-table td { padding: 0.625rem 1rem; line-height: 1.25rem; border-top: 1px solid var(--wp--preset--color--border); }
/* Design tables separate ROWS only — kill core's all-sides cell borders (the vertical column
 * lines come from core's .wp-block-table td/th border, not from the theme). */
.ml-content .wp-block-table td { border-left: 0; border-right: 0; border-bottom: 0; }
.ml-content .wp-block-table th { border: 0; }
/* The design's table cells carry NO column weight (BlockRenderer.tsx:265-267 — `px-4 py-2.5
 * border-t` only): the bold comes from the CONTENT's own `**…**`, which the seeded core/table
 * preserves as <strong>. A structural first-column rule therefore bolded the rows the design
 * leaves at 400 (e.g. the tail rows of the Pro Libero tables), and 700 resolves to the 800 face
 * here — visibly heavier than the design. Emphasis follows the content; the customer bolds a new
 * row the same way they'd bold anything else. */
.ml-content .wp-block-table tbody tr:nth-child(odd) { background: color-mix(in srgb, var(--wp--preset--color--muted) 40%, transparent); }

/* Gallery + logoGrid. */
.ml-content .wp-block-gallery { margin: 2rem 0; }

/* minmax(0, 1fr) + min-width: 0 for the same reason as .ml-partners__grid above: with bare 1fr
 * WebKit sizes each track from the FULL-size logo's intrinsic width, so at 390 px the grid ran
 * past the viewport and the blocks below it (separator, heading, button) overlapped the last card. */
.ml-content .ml-logogrid { margin: 2.5rem 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
@media (min-width: 640px) { .ml-content .ml-logogrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 768px) { .ml-content .ml-logogrid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .ml-content .ml-logogrid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
/* Cards are now native core/image blocks (editable) — style the figures directly. */
.ml-content .ml-logogrid .wp-block-image { margin: 0; min-width: 0; aspect-ratio: 3 / 2; background: #fff; border: 1px solid var(--wp--preset--color--border); display: flex; align-items: center; justify-content: center; padding: 1.5rem; transition: border-color .2s, box-shadow .2s; }
.ml-content .ml-logogrid .wp-block-image:hover { border-color: var(--wp--preset--color--brand-lime); box-shadow: 0 4px 12px -4px rgb(0 0 0 / 0.15); }
.ml-content .ml-logogrid .wp-block-image a { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
.ml-content .ml-logogrid .wp-block-image img { max-width: 100%; max-height: 100%; width: auto; object-fit: contain; }

/* ═══ Joukkue single (TeamDetail.tsx) — page padding + section rhythm ═══
 * MainContainer py-6 md:py-8; TeamOverview sections space-y-10 md:space-y-14;
 * TabsContent mt-8 md:mt-10 between the tab bar and the first overview section.
 * The gaps live HERE on the block wrappers (the elements ship margin:0), so the
 * spacing matches the design without compounding element + page margins.
 * Specificity (0,4,0)+ beats core's :where() blockGap resets. */
body.single-joukkue main > .is-layout-constrained { padding-top: 1.5rem; padding-bottom: 1.5rem; }
@media (min-width: 768px) { body.single-joukkue main > .is-layout-constrained { padding-top: 2rem; padding-bottom: 2rem; } }
/* !important required: core forces `:root :where(.is-layout-constrained) > .alignwide
 * { margin-block-start: 0 !important }` and every section wrapper here is alignwide. */
body.single-joukkue main .is-layout-constrained > * + * { margin-block-start: 2.5rem !important; }
@media (min-width: 768px) { body.single-joukkue main .is-layout-constrained > * + * { margin-block-start: 3.5rem !important; } }
/* Hero photo → tab bar (design ~2rem), tab bar → first section (TabsContent mt-8 md:mt-10). */
/* Data-view pages: the design wraps each view in `MainContainer py-6 md:py-8`
 * (Standings/Matches/PlayerStats.tsx; Teams.tsx py-8 flat) — the bare ml/* block after
 * the page hero lacks that page-level padding, so the toolbar/table sat ~32px too high
 * ("gap under the title" mismatch). Scoped to main so embedded/mini usages are untouched. */
main .wp-block-ml-standings,
main .wp-block-ml-pisteporssi,
main .wp-block-ml-livescores,
main .wp-block-ml-livescore {
	padding-block: 24px;
}
main .wp-block-ml-teams { padding-block: 32px; }
@media (min-width: 768px) {
	main .wp-block-ml-standings,
	main .wp-block-ml-pisteporssi,
	main .wp-block-ml-livescores,
	main .wp-block-ml-livescore {
		padding-block: 32px;
	}
}

body.single-joukkue main .is-layout-constrained > .wp-block-ml-teamtabs { margin-block-start: 2rem !important; }
body.single-joukkue main .is-layout-constrained > .wp-block-ml-teamtabs + * { margin-block-start: 2rem !important; }
@media (min-width: 768px) { body.single-joukkue main .is-layout-constrained > .wp-block-ml-teamtabs + * { margin-block-start: 2.5rem !important; } }

/* ═══ 404 (NotFound.tsx) — templates/404.html ═══
 * Design: <div class="flex min-h-screen items-center justify-center bg-muted"> with a
 * text-center box: h1 "404" mb-4 text-4xl font-extrabold · p mb-4 text-xl
 * text-muted-foreground · a text-primary underline hover:text-primary/90.
 * The bg-muted is inline on the group (hsl(210 20% 96%) = --muted). blockGap is "0"
 * theme-wide, so the design's mb-4 rhythm is re-added explicitly. */
main.ml-notfound { display: flex; min-height: 100vh; align-items: center; justify-content: center; }
.ml-notfound__inner { text-align: center; }
.ml-notfound__code {
	margin-top: 0; margin-bottom: 1rem;
	font-size: 2.25rem; line-height: 2.5rem; font-weight: 800;
	text-transform: none; letter-spacing: normal;
}
.ml-notfound__lead { margin-top: 0; margin-bottom: 1rem; font-size: 1.25rem; line-height: 1.75rem; }
.ml-notfound__home { margin: 0; }
.ml-notfound__home a { color: hsl(182 100% 9%); text-decoration: underline; }
.ml-notfound__home a:hover { color: hsl(182 100% 9% / .9); }

/* ── Navigation editor canvas: separate the menu items so each is clickable ──
 * Editing a flat menu (Footer Partners / Legal / Utility) in Site Editor →
 * Navigation lays the items out in one row, and the theme.json global
 * blockGap:0 emits layout CSS that lands after this sheet inside the canvas and
 * zeroes the container gap (measured: 11 items, gap 0px, boxes butted edge to
 * edge — "Oomi" ends at x=36 and "VitaePro" starts at x=36, so a 1px miss
 * selects the neighbour). Same trap as the gallery gap above.
 *
 * Editor-only by construction: `.editor-styles-wrapper` never exists on the
 * front end, AND the footer/utility rows don't render core/navigation markup at
 * all (they print their own <a class="ml-ftr__partner"> etc. from the node
 * tree), so this cannot touch live output. */
.editor-styles-wrapper .wp-block-navigation__container {
	gap: 0.5rem !important;
	row-gap: 0.5rem !important;
}
.editor-styles-wrapper .wp-block-navigation__container > .wp-block-navigation-item {
	/* Bigger hit area than the 24px text line, without moving the text baseline. */
	padding-block: 0.25rem;
	padding-inline: 0.25rem;
	border-radius: 4px;
	outline: 1px dashed rgba(0, 44, 46, .22);
	outline-offset: 1px;
}
.editor-styles-wrapper .wp-block-navigation__container > .wp-block-navigation-item:hover {
	outline-color: rgba(0, 44, 46, .55);
	background: rgba(0, 44, 46, .04);
}

/* ══════════════════════════════════════════════════════════════════
 * Picker-block empty state — EDITOR ONLY
 *
 * A curated element with nothing picked renders '' on the live page (project
 * rule: nothing picked → the section does not exist). In the editor the same
 * block would then show Gutenberg's "Block returned empty" banner and read as
 * broken, so includes/block-empty-state.php emits this card for the
 * block-renderer REST request instead. Never reachable on the front end.
 * ══════════════════════════════════════════════════════════════════ */

.ml-editor-placeholder {
	display: flex;
	flex-direction: column;
	gap: 0.375rem;
	align-items: center;
	justify-content: center;
	min-height: 8rem;
	padding: 2rem 1.25rem;
	border: 2px dashed rgba(0, 44, 46, 0.28);
	border-radius: 0.75rem;
	background: rgba(0, 44, 46, 0.03);
	text-align: center;
}

.ml-editor-placeholder__label {
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	color: var(--wp--preset--color--brand-teal);
}

.ml-editor-placeholder__hint {
	font-size: 0.8125rem;
	line-height: 1.25rem;
	color: rgba(0, 44, 46, 0.65);
}

/* ══════════════════════════════════════════════════════════════════
 * The same card INSIDE A PATTERN PREVIEW (`--preview`, added only by the two
 * preview guards: editor-blocks.js previewCard() and the element bundle's
 * usePreview()). A different problem from the canvas placeholder above, and the
 * reason the pattern list was unreadable:
 *
 * Gutenberg renders a pattern preview at viewportWidth 1200 px and scales the
 * result into the inserter's 231 px column — a factor of 0.19. Measured before
 * this block existed: the card's 8rem height became a 25 px grey sliver and its
 * 12 px label rendered at 2.3 px, so "Mainospaikka (leveä)", "Mainospaikka
 * (suuri)" and "Seuralogot" were three identical unreadable strips. The pattern
 * list also shows no titles of its own, so that label is the ONLY thing naming
 * the pattern.
 *
 * So this variant is sized in the PREVIEW's coordinate space, not the canvas's:
 * type large enough to survive ÷5 (68 px → ~13 px), and a wireframe whose
 * silhouette matches the block being inserted, so the patterns are told apart by
 * SHAPE before the label is even read. Wireframes only — never sample rows,
 * scores or logos (project rule: no fabricated data, not even in a thumbnail).
 * ══════════════════════════════════════════════════════════════════ */

.ml-editor-placeholder--preview {
	gap: 2rem;
	min-height: 0;
	padding: 3rem 3.5rem;
	border-width: 6px;
	border-radius: 1.75rem;
}

.ml-editor-placeholder--preview .ml-editor-placeholder__label {
	font-size: 68px;
	line-height: 1.1;
	letter-spacing: 0.06em;
}

/* The hint is ~7 px once scaled — noise, not information. The label carries it. */
.ml-editor-placeholder--preview .ml-editor-placeholder__hint { display: none; }

/* Wireframe: neutral teal-tint shapes standing in for the block's own furniture. */
.ml-editor-placeholder__wire {
	display: flex;
	gap: 1.5rem;
	align-items: center;
	justify-content: center;
	width: 100%;
}
.ml-editor-placeholder__wire > i {
	display: block;
	background: rgba(0, 44, 46, 0.16);
	border-radius: 0.75rem;
}

/* Ad slots — the box is drawn at the creative's TRUE aspect ratio (1640×140 vs
 * 1640×320), which is the only difference between the two Mainospaikka patterns
 * and now the first thing visible in the list. */
.ml-editor-placeholder--ad .ml-editor-placeholder__wire > i {
	width: 100%;
	border-radius: 0.5rem;
	background: rgba(0, 44, 46, 0.1);
	border: 4px dashed rgba(0, 44, 46, 0.22);
}
.ml-editor-placeholder--ad-leaderboard .ml-editor-placeholder__wire > i { aspect-ratio: 1640 / 140; }
.ml-editor-placeholder--ad-billboard .ml-editor-placeholder__wire > i { aspect-ratio: 1640 / 320; }

/* Club strip — a row of crest tiles (circles, like the live .ml-clubstrip__tile). */
.ml-editor-placeholder--strip .ml-editor-placeholder__wire > i {
	width: 88px;
	height: 88px;
	border-radius: 999px;
}

/* Match strip / card row — a row of match cards. */
.ml-editor-placeholder--cards .ml-editor-placeholder__wire > i {
	flex: 1 1 0;
	height: 190px;
}

/* Team / card grid. */
.ml-editor-placeholder--grid .ml-editor-placeholder__wire {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 1.5rem;
}
.ml-editor-placeholder--grid .ml-editor-placeholder__wire > i { height: 150px; }

/* Table-shaped elements (sarjataulukko, pistepörssi, ottelulistat): stacked rows,
 * the top one darker for the header band. */
.ml-editor-placeholder--rows .ml-editor-placeholder__wire {
	flex-direction: column;
	gap: 0.75rem;
}
.ml-editor-placeholder--rows .ml-editor-placeholder__wire > i {
	width: 100%;
	height: 46px;
	border-radius: 0.375rem;
}
.ml-editor-placeholder--rows .ml-editor-placeholder__wire > i:first-child {
	height: 58px;
	background: rgba(0, 44, 46, 0.34);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * BARE PAGE CONTENT — the net for blocks typed OUTSIDE a Sisältöalue
 * ═══════════════════════════════════════════════════════════════════════════
 * Every editorial style above is scoped to `.ml-content`, which exists only
 * because a pattern (or the page seed) put that group into the CONTENT. A page
 * the customer creates and types straight into has no such wrapper, so its
 * blocks got: no rhythm at all (theme.json blockGap "0" → core's flow reset
 * zeroes every margin-block), the 760 cap CENTRED instead of left-flush at the
 * gutter (measured 2026-08-03: x=340 vs the wrapper's x=24 at 1440), and none
 * of the design's list/quote/table styling.
 *
 * Why this duplicates values instead of reusing `.ml-content …`: those are
 * DESCENDANT selectors. `page.html` also serves the data-view pages (ottelut,
 * pistepörssi, joukkueet, pelaaja, ottelu), so a descendant rule would reach
 * inside the ml/* React elements and restyle their paragraphs. The net is
 * therefore DIRECT-CHILD only — a bare block the customer typed can only ever
 * be a direct child of post-content. Keep the values in sync with the
 * `.ml-content` rules above; the editor mirror lives in assets/editor-page.css.
 *
 * Scope: page templates only (`.ml-page` = page.html, `.ml-sisaltosivu` =
 * sisaltosivu.html). front-page.html and single.html are deliberately excluded
 * — the article body has its own `.ml-article__body` contract.
 */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, figure, pre, hr, .wp-block-buttons, .wp-block-table, .wp-block-embed, .wp-block-image, .wp-block-quote, .wp-block-details):not(.alignwide):not(.alignfull) {
	/* Same column as `.ml-content > *`: 760 wide, flush under the page title.
	 * The left offset must track the SAME centred 1640 track the hero's
	 * `.ml-container` uses — post-content is NOT capped, so a plain
	 * `margin-inline: 0 auto` pins bare text to the root padding at every width
	 * and it drifts away from the H1 as soon as the viewport passes the cap
	 * (measured: text x=24 against the title's 140 at 1920, 460 at 2560 — 436px
	 * adrift). `max(0px, (100% - 1640px)/2)` reproduces the cap's own centring:
	 * 0 below it (x=24 at 1440, x=16 at 390) and the container's left inset above
	 * it (30 at 1700, 140 at 1920, 460 at 2560 — each equal to the wrapped pages'
	 * measured x).
	 * `!important` is not defensive here, it is REQUIRED: core's constrained-layout
	 * rule ships `margin-left: auto !important; margin-right: auto !important`, so
	 * no amount of specificity can re-align a child without it (measured: the
	 * plain rule left every paragraph centred at x=340). `.ml-content` carries the
	 * same `!important` for the same reason. */
	max-width: 760px;
	margin-inline: max(0px, calc((100% - 1640px) / 2)) auto !important;
}
/* The same left anchor for EVERY other unaligned top-level block — anything the
 * customer drops in that the type list above doesn't name (a picker block, a
 * pattern's bare block, a classic block). Core caps every non-aligned child at
 * the 760 content size and CENTRES it, so without this such a block sat at
 * x=340 while all the text sat at x=24 (measured on the customer's test page:
 * the Mainospaikka and the Tulevat ottelut mini). Margin only — the 760 cap is
 * core's and stays. Aligned blocks are excluded because alignment belongs to
 * core's layout: every data-view element renders alignwide/alignfull and must
 * keep its own rail. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :not(.alignwide):not(.alignfull):not(.alignleft):not(.alignright) {
	margin-inline: max(0px, calc((100% - 1640px) / 2)) auto !important;
}

/* Wide tier for wide CONTENT blocks, mirroring `.ml-content`'s (tables/galleries/
 * embeds/video 1100). Keyed on the block TYPE, never on `.alignwide`. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(.wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video):not(.alignwide):not(.alignfull) { max-width: 1100px; }
/* …and the SAME 1100 when one of those four is set to "Leveä". Inside a Sisältöalue every
 * alignwide child is 1100 (`.ml-content > .alignwide`), so the removed Taulukko-osio/Video
 * patterns — both of which emitted `align:wide` — rendered at 1100. Now that those blocks are
 * inserted bare, they must land on the same rail or the same table is 1640 wide typed straight
 * onto a page and 1100 inside a Sisältöalue (measured: 1640 vs 1100 at 1920).
 * Still keyed on the four CONTENT block types, so it cannot reach a data-view page: every ml/*
 * element renders its own markup and is never a `.wp-block-table|gallery|embed|video`.
 * `.wp-block-image` is deliberately NOT in the list — an image set to Leveä/Koko leveys keeps
 * core's tier on purpose (see the alignment note below; the Kuva pattern shipped it unaligned). */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(.wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video).alignwide { max-width: 1100px; }
/* …and their 2rem top margin back. Core ships `:root :where(.is-layout-constrained) >
 * .alignwide { margin-block-start: 0 !important }`, which no plain rule can beat — so an
 * alignwide table/video sat flush against the paragraph above it while the same block inside a
 * Sisältöalue (a FLOW layout, where that rule doesn't apply) had 32px (measured: mt 0 vs 32).
 * !important + a higher specificity than core's (0,3,1 vs 0,2,0) is the only way back. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(.wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video).alignwide { margin-block-start: 2rem !important; }
/* An ALIGNED block keeps core's own tier (wide → 1640, full → bleed). Both caps above
 * are keyed on the block type, so before this exclusion a bare image set to "Leveä" or
 * "Koko leveys" was still clamped to the 760 text column — the other half of "image size
 * cant be changed" (measured: an alignfull image rendered 760, not 1440). Inside a
 * `.ml-content` band the equivalent tiers already existed (`.ml-content > .alignwide`). */
/* NB: `.alignwide` and `.alignfull` are deliberately NOT touched — page.html also
 * serves the data-view pages (ottelut, sarjataulukko, pistepörssi, joukkueet,
 * pelaaja, ottelu), whose ml/* element is a top-level `.alignwide` block. Measured:
 * an `.alignwide { max-width: 1100px }` here shrank pistepörssi from 1392 to 1100,
 * and a `margin-inline: 0` on `.alignfull` clamped the page hero from 0/1440 to
 * 24/1392 (core's constrained layout supplies the negative margins that let an
 * alignfull child escape the root padding). Both alignments belong to core. */

/* Vertical breathing: a wrapped Sisältöalue supplies it with padding-block
 * (1.5/2.5rem, 2.5/4rem ≥768). Bare content has no wrapper to pad, so the first
 * text block after the hero (always an alignfull group) and the last block carry
 * it as margins instead. */
/* MARGIN, not padding — corrected 2026-08-11 by direct measurement, and the values are
 * exactly the wrapper's padding-block. The wrapped path does NOT add the block's own margin
 * to the container padding: core zeroes it (`:root :where(.is-layout-flow) > :first-child {
 * margin-block-start: 0 }` / `> :last-child { margin-block-end: 0 }`), so a Sisältöalue's first
 * block always sits at exactly the container padding — measured 40 at 1920, 24 at 390, for both
 * a heading and a paragraph. Padding here ADDED to that: the block's box landed right but its
 * TEXT sat one padding lower and everything after it shifted (H2 text 40px low at 1920, and the
 * paragraph after it at 885 vs the wrapped 845). A margin replaces the block's own margin, which
 * is precisely the wrapped behaviour. Higher specificity than the per-type typography rules
 * below (0,3,1 vs 0,2,1), so it wins wherever they set a shorthand `margin`.
 * Residual, accepted: an `.alignwide` table/video as the FIRST block after the hero keeps the
 * 2rem from the alignwide rule above (core's `margin-block-start: 0 !important` for aligned
 * children of a constrained layout forces that rule to be !important, so it outranks this one).
 * Every unaligned block type — the ones a page actually starts with — matches exactly. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .alignfull + :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, figure, pre, .wp-block-buttons, .wp-block-table) { margin-block-start: 1.5rem; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :last-child:is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, figure, pre, .wp-block-buttons, .wp-block-table) { margin-block-end: 2.5rem; }
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .alignfull + :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, figure, pre, .wp-block-buttons, .wp-block-table) { margin-block-start: 2.5rem; }
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :last-child:is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, figure, pre, .wp-block-buttons, .wp-block-table) { margin-block-end: 4rem; }
}
/* The four wide content types are the exception at the END of a page: their `.ml-content
 * .wp-block-table|gallery|embed|video { margin: 2rem 0 }` rule TIES core's last-child zeroing at
 * (0,2,0) and wins by order, so the wrapped path really does put 2rem + the wrapper's padding
 * below them (measured 32+64 at 1920 against the 64 a paragraph gets). Mirror the sum, or a page
 * ending in a table sits 32px closer to the footer than the same table in a Sisältöalue. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :last-child:is(.wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video) { margin-block-end: 4.5rem; }
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :last-child:is(.wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video) { margin-block-end: 6rem; }
}

/* Typography — mirrors `.ml-content h2/h3/h4`, `p`, `ul`, `ol`, `blockquote`, `hr`. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(h2, h3, h4) { margin: 2.5rem 0 0.75rem; font-weight: 800; color: var(--wp--preset--color--brand-teal); letter-spacing: -0.025em; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > h2 { font-size: 1.5rem; line-height: 2rem; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > h3 { font-size: 1.25rem; line-height: 1.75rem; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > h4 { font-size: 1.125rem; line-height: 1.75rem; }
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > h2 { font-size: 1.875rem; line-height: 2.25rem; }
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > h3 { font-size: 1.5rem; line-height: 2rem; }
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > h4 { font-size: 1.25rem; line-height: 1.75rem; }
}
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > p { margin: 1rem 0; font-size: 15px; line-height: 1.5; color: var(--wp--preset--color--foreground); }
@media (min-width: 768px) { :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > p { font-size: 1rem; line-height: 1.5rem; } }
/* Ingressi — mirror of `.ml-content p.is-style-lead` (the registered "Ingressi" paragraph
 * style, sl-mestaruusliiga-core/includes/block-styles.php). A paragraph is the one block a
 * customer types straight onto a page, so the style has to hold outside a Sisältöalue too. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > p.is-style-lead { font-size: 1.125rem; color: var(--wp--preset--color--muted-foreground); line-height: 1.75rem; }
@media (min-width: 768px) { :is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > p.is-style-lead { font-size: 1.25rem; line-height: 1.75rem; } }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(ul, ol) { margin: 1rem 0; padding-left: 1.25rem; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > ul { list-style: none; padding-left: 0; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > ul li { position: relative; padding-left: 1.25rem; margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > ul li::before { content: ""; position: absolute; left: 0; top: 0.6em; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--wp--preset--color--brand-lime); }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > ol { list-style: decimal; padding-left: 1.5rem; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > ol li { margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > ol li::marker { color: var(--wp--preset--color--brand-teal); font-weight: 800; }
/* md:text-base, the pair of `.ml-content ul li, .ml-content ol li` — without it a bare
 * Luettelo stayed at the mobile 15/22.5 on desktop while the same list inside a
 * Sisältöalue was 16/24 (measured at 1920). */
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(ul, ol) li { font-size: 1rem; line-height: 1.5rem; }
}
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > hr { margin: 2.5rem 0; border: 0; border-top: 1px solid var(--wp--preset--color--border); }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) { margin: 2rem 0; padding-left: 1.5rem; border-left: 4px solid var(--wp--preset--color--brand-lime); }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) p { font-size: 1.25rem; font-weight: 800; line-height: 1.375; color: var(--wp--preset--color--brand-teal); }
/* Paragraph rhythm INSIDE the quote. `.ml-content p { margin: 1rem 0 }` is a descendant rule so
 * it reaches the quote's paragraphs; the bare layer's `> p` cannot (direct child), which left a
 * multi-paragraph Lainaus with its lines jammed together (measured: mb 0 vs the wrapped 16).
 * first-child stays 0 the way core's flow-layout rule zeroes it in the wrapped path. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) p { margin-block: 1rem; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) p:first-child { margin-block-start: 0; }
/* md:text-2xl, the pair of `.ml-content blockquote p` — a responsive variant that brings its
 * own 32px line-height (keeping the 1.375 ratio renders 33px and every quote ~6px taller than
 * the design). Missing here, a bare Lainaus was 20/27.5 on desktop against the wrapped 24/32. */
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) p { font-size: 1.5rem; line-height: 2rem; }
}
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) cite { display: block; margin-top: 0.75rem; font-style: normal; font-size: 0.875rem; line-height: 1.25rem; color: var(--wp--preset--color--muted-foreground); }
/* The design's quote marks are presentation, not content (same as `.ml-content`),
 * so a bare Quote block gets them too — otherwise a quote typed straight onto a
 * page looks different from one inside a Sisältöalue. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) p:first-of-type::before { content: '"'; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) p:last-of-type::after { content: '"'; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(blockquote, .wp-block-quote) cite::before { content: '\2014\a0'; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > figure { margin-block: 2rem; }
/* margin-BLOCK, not `margin: 2rem 0`: the shorthand also set margin-inline to 0, which
 * cancels the negative margins core's constrained layout uses to let an `.alignfull`
 * child escape the root padding — an image set to "Koko leveys" stopped at the content
 * rail (measured 24/1392 instead of 0/1440) purely because of this shorthand. */
/* max-width, not width — see the `.ml-content figure img` note: `width` made WP's
 * "Kuvan koko" control inert and upscaled a thumbnail to the full column. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > figure img { max-width: 100%; height: auto; }

/* ── A gallery typed straight onto a page gets the design's grid too ────────
 * The gallery layout lived only under `.ml-content`, so a BARE core/gallery fell
 * through to core's own flex row, which distributes width by aspect ratio: a
 * 3-image gallery of one 768×128 banner and two 150×150 logos came out 779 + 161 +
 * 161 instead of three equal cells (measured on the customer's test page — "the 3
 * image gallery doesn't work if the images are smaller"). Same declarations as the
 * `.ml-content` block above, so a gallery looks the same wrapped or bare. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-gallery {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 0.75rem;
	align-items: start;
}
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-gallery { grid-template-columns: repeat(3, 1fr); }
}
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-gallery .wp-block-image { margin: 0; width: auto; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-gallery .wp-block-image img { max-width: 100%; height: auto; display: block; margin-inline: auto; }
/* The design's gallery (BlockRenderer "gallery") is UNCROPPED h-auto; core's is-cropped
 * squares every image. Neutralise it here as well, so the canvas and the live page agree
 * whatever the gallery's imageCrop attr says. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-gallery.is-cropped .wp-block-image { height: auto !important; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-gallery.is-cropped .wp-block-image img { height: auto !important; width: auto !important; max-width: 100%; object-fit: unset; margin-inline: auto; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-image:is(.alignwide, .alignfull) img { width: 100%; }
/* core/video is a <figure> too, so the rules above already give it the column and
 * rhythm — but a <video> is not an <img>, and unstyled it renders at the default
 * 300×150 instead of filling the column. Mirror of `.ml-content .wp-block-video`. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-video video { display: block; width: 100%; height: auto; border-radius: 0.75rem; background: #000; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > figure figcaption { margin-top: 0.5rem; font-size: 0.875rem; font-style: italic; color: var(--wp--preset--color--muted-foreground); }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-buttons { margin: 1.5rem 0; gap: 0.75rem; }
/* Editorial link treatment (lime underline), mirroring `.ml-content a`. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(p, ul, ol, blockquote, figure) a:where(:not(.wp-element-button)) { font-weight: 600; color: var(--wp--preset--color--brand-teal); text-decoration: underline; text-decoration-color: var(--wp--preset--color--brand-lime); text-decoration-thickness: 2px; text-underline-offset: 4px; transition: text-decoration-color .15s; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(p, ul, ol, blockquote, figure) a:where(:not(.wp-element-button)):hover { text-decoration-color: var(--wp--preset--color--brand-teal); }
/* …and the ↗ on one that leaves the page — mirror of the `.ml-content :is(p, li, td)` pair
 * above (read its comment for what counts as leaving). Without this the same link is arrowed
 * inside a Sisältöalue and bare on a page typed straight into, which is exactly the kind of
 * disagreement the bare-content net exists to close. Direct-child scoped for the reason the
 * whole net is: page.html also serves the data-view pages, and a descendant `a[target=_blank]`
 * rule would reach inside the ml/* React elements' own markup. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > p a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button)),
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(ul, ol) li a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button)),
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-table td a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button)) {
	display: inline-flex;
	align-items: baseline;
	gap: 0.125rem;
}
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > p a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button))::after,
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(ul, ol) li a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button))::after,
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-table td a:is([target="_blank"], [href$=".pdf" i], [href$=".doc" i], [href$=".docx" i], [href$=".xls" i], [href$=".xlsx" i], [href$=".ppt" i], [href$=".pptx" i], [href$=".zip" i], [href$=".csv" i]):where(:not(.wp-element-button))::after {
	content: "";
	width: 0.875rem;
	height: 0.875rem;
	align-self: center;
	background: currentColor;
	-webkit-mask: var(--ml-arrow-mask) no-repeat center / contain;
	mask: var(--ml-arrow-mask) no-repeat center / contain;
}
/* Table basics — mirrors the `.ml-content .wp-block-table` block above. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-table { margin: 2rem 0; overflow-x: auto; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-table table { width: 100%; border-collapse: collapse; font-size: 0.875rem; line-height: 1.25rem; border: 1px solid var(--wp--preset--color--border); border-radius: 0.375rem; overflow: hidden; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-table thead { background: var(--wp--preset--color--brand-teal); color: #fff; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-table th { text-align: left; padding: 0.75rem 1rem; font-weight: 800; text-transform: uppercase; font-size: 0.75rem; line-height: 1rem; letter-spacing: 0.05em; border: 0; }
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-table td { padding: 0.625rem 1rem; line-height: 1.25rem; border: 0; border-top: 1px solid var(--wp--preset--color--border); }

/* ── An empty paragraph must occupy a LINE, not collapse away ──────────────
 * Customers press Enter to add breathing room. The editor shows each empty
 * paragraph as a full line box (its placeholder), but on the front end
 * `<p></p>` has no line box at all: height 0, and its margins collapse THROUGH
 * it, so N blank lines render as ZERO extra space (measured 2026-08-03: h=0 for
 * every empty p). Give it one line's min-height so the editor and the live page
 * agree — this preserves the author's spacing rather than inventing content. */
/* Scope note (2026-08-13): these were DIRECT-CHILD selectors, so an Enter pressed inside any
 * section wrapper — the sisältölaatikko / brand band above all — still collapsed to nothing on
 * the live page while the editor showed a full line ("if i add empty block there it wont add
 * space in live page"). The band nests two levels (band → grid → column), so no `>` chain
 * reaches it. Descendant selectors instead: a blank line is a blank line at any depth of page
 * content. Headings are included because Enter after a heading leaves an empty HEADING block. */
.ml-content :is(p, h1, h2, h3, h4, h5, h6):empty,
.ml-brandband :is(p, h1, h2, h3, h4, h5, h6):empty,
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content :is(p, h1, h2, h3, h4, h5, h6):empty,
/* Same for an ARTICLE body. The rule above shipped for pages and content boxes only, so an
 * uutinen still swallowed the author's blank lines: measured on the customer's own new article
 * (2026-08-04) four empty paragraphs between the text lines rendered as no gap at all, while the
 * editor showed four full lines — the "spaces don't work" report. `.ml-article__body` is the
 * post-content of single.html, so its paragraphs are direct children exactly like a page's. */
.ml-article__body > p:empty { min-height: 1.5em; }

/* ═══════════════════════════════════════════════════════════════════════════
 * EMPTIED PATTERN SHELL = NOTHING (customer 2026-08-03: "if sisältölaatikko is
 * removed it leaves some kinda background still there")
 * ═══════════════════════════════════════════════════════════════════════════
 * Every band pattern is a WRAPPER group carrying the chrome (gradient + the two
 * blurred blobs on `.ml-brandband`, the section gap on `.ml-sect-gap*`, the
 * padding on `.ml-content`) plus editable children inside it. Delete the
 * children — or the inner grid — and the shell survives as a real block, so the
 * page kept 64 px of section margin and, in the editor, a 118-px teal band with
 * Gutenberg's group appender in it (both measured on the customer's own test
 * page). No markup shape can prevent that: a wrapper always outlives its last
 * child. So collapse it instead, which is also the site's standing rule —
 * "empty = nothing on the live page, not even a placeholder".
 *
 * `.ml-brandband` is checked for CONTENT anywhere inside (not just direct
 * children) because it has two nesting levels: emptying either one leaves a
 * shell that still paints the gradient. The others only ever wrap blocks
 * directly, so `:has(> *)` is enough and cannot mistake a rendered dynamic
 * block for emptiness. The editor mirror (assets/editor-page.css) deliberately
 * does NOT hide these — a hidden block cannot be selected and deleted; it
 * strips the chrome and labels the leftover instead. */
.ml-brandband:not(:has(:is(p, h1, h2, h3, h4, h5, h6, .wp-block-list, img, figure, video, iframe, .wp-block-button, [data-ml-widget]))),
:is(.ml-content, .ml-partners, .ml-sect-gap, .ml-sect-gap-sm):not(:has(> *)) {
	display: none !important;
}

/* ── A pattern dropped INSIDE a wrapper group still tracks the page rail ────
 * Patterns are `alignwide` (the data/band sections) or `alignfull .ml-content`.
 * Alignment is a LAYOUT service: post-content is `constrained` + root padding,
 * so an alignwide child there lands on the rail (24/1392 at 1440 — measured).
 * One level down a `flow` group applies no cap at all, so the band bled
 * edge-to-edge (x=0 w=1440). Customers do nest — the old page seed shipped a
 * wrapper group around the hero and the customer's test page had every pattern
 * inside it.
 * Two exclusions, both load-bearing: `.ml-content` owns its own 760/1100/full
 * tiers on purpose, and a `.has-global-padding` group (what core gives a
 * CONSTRAINED group here) already insets its children by the root padding and
 * lands them on the rail by itself — netting those too double-counted the gutter
 * (measured x=48 w=1344 at 1440). */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-group:not(.ml-content):not(.has-global-padding) > .alignwide {
	width: auto;
	max-width: none;
	/* Both sides get the same inset, so the width resolves to the rail: 358 at
	 * 390, 1392 at 1440, 1640 at 1920 — identical to a top-level alignwide.
	 * `!important` beats core's `margin-left/right: auto !important`. */
	margin-inline: max(1rem, calc((100% - 1640px) / 2)) !important;
}
@media (min-width: 640px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .wp-block-group:not(.ml-content):not(.has-global-padding) > .alignwide {
		margin-inline: max(1.5rem, calc((100% - 1640px) / 2)) !important;
	}
}

/* ═══════════════════════════════════════════════════════════════════════════
 * BRAND BAND TEXT IS CONTEXT-PROOF  (customer 2026-08-13: "when adding
 * sisältöbox to article the text colors are broken")
 * ═══════════════════════════════════════════════════════════════════════════
 * The Sisältölaatikko pattern is the brand band, and the band paints its copy
 * white / lime on a dark teal gradient. Every surrounding text net repaints it,
 * because they all out-specify a plain `p.ml-brandband__text` (0,1,1):
 *
 *   context                        eyebrow            title          lead text
 *   page (direct child)            lime 11px  ✓       white 36px ✓   white/80 ✓
 *   .ml-article__body (an article) TEAL 16px  ✗       white 30px ✗   TEAL      ✗
 *   .ml-content (a Sisältöalue)    TEAL 16px  ✗       TEAL 30px  ✗   TEAL      ✗
 *
 * (measured 2026-08-13 at 1440 on all three; teal-on-teal = invisible.) The
 * culprits are `.ml-article__body p` / `h2` (0,2,1) and `.ml-content p` / `h2`
 * (0,1,1, later in this file than the band section) — the band's own rules
 * simply lost. So the band's typography is authored HERE instead: after every
 * net, at `.ml-brandband p.ml-…` (0,2,1), which ties the article net and wins on
 * order and beats the content net outright. Values are verbatim from the band
 * section above (Home.tsx) — the front page is unchanged by this, it just gets
 * the same declarations at a higher specificity.
 *
 * The first rule is the generic net: ANY block the customer adds inside the band
 * (a plain paragraph, a list, a heading, a link) must stay readable on the
 * gradient, in an article and a Sisältöalue too. `:is(.ml-brandband)` is the
 * specificity bump that lets it reach (0,2,1) — the band's own class, written
 * twice on purpose, not a stray. The selectors also cover .ml-vpromo — the
 * video variant's copy column reuses these exact type classes (VideoPromo.tsx
 * left column is verbatim the brand band's). */
:is(.ml-brandband, .ml-vpromo):is(.ml-brandband, .ml-vpromo) :is(p, li, cite, figcaption, h1, h2, h3, h4, h5, h6) {
	color: #fff;
}
:is(.ml-brandband, .ml-vpromo):is(.ml-brandband, .ml-vpromo) :is(p, li, td) a:where(:not(.wp-element-button)) {
	color: #fff;
	text-decoration-color: var(--wp--preset--color--brand-lime);
}

:is(.ml-brandband, .ml-vpromo) p.ml-brandband__eyebrow {
	margin: 0 0 0.75rem;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.22em;
	color: var(--wp--preset--color--brand-lime);
}
@media (min-width: 768px) {
	:is(.ml-brandband, .ml-vpromo) p.ml-brandband__eyebrow { font-size: 0.875rem; line-height: 1.25rem; }
}

/* BrandBand headline — Home.tsx:350 `text-2xl md:text-4xl font-extrabold
 * leading-tight tracking-tight`: 24px (lh 1.25) below md, 36px (lh 40px, the
 * text-4xl line-height overrides leading-tight at md) flat at every width ≥768.
 * NO lg/xl step — it never grows past 36px. (Earlier this copied the hero
 * carousel's 26→48px scale by mistake, so the headline was oversized and
 * wrapped on desktop.) */
/* Tag-agnostic: the front-page headline is now the page <h1> (WCAG page-has-heading-one);
 * it was h2. Match on class + both tags so the styling holds either way. NB the global
 * theme.json h1 is uppercase (PageHero) — this headline is sentence-case in the design
 * (Home.tsx has no uppercase class), so the promotion must undo the inherited transform. */
:is(.ml-brandband, .ml-vpromo) :is(h1, h2, h3).ml-brandband__title {
	margin: 0;
	font-size: 1.5rem;         /* text-2xl 24px */
	font-weight: 800;
	line-height: 1.25;         /* leading-tight */
	letter-spacing: -0.025em;  /* tracking-tight */
	color: #fff;
	text-transform: none;
}

@media (min-width: 768px) { /* md:text-4xl 36px; text-4xl line-height 2.5rem=40px wins over leading-tight */
	:is(.ml-brandband, .ml-vpromo) :is(h1, h2, h3).ml-brandband__title { font-size: 2.25rem; line-height: 2.5rem; }
}

/* Home.tsx:354 `mt-3 text-white/80 text-sm md:text-base max-w-xl`. Both line-heights
 * are AUTHORED per breakpoint: text-sm = 14px/20px, text-base = 16px/24px. Without
 * them the global 1.5 ratio applied — which coincidentally lands on 24px at md but
 * gives 21px (not 20px) below it, so the paragraph ran 3px tall and pushed the CTAs,
 * logos and the band's own height out of parity on mobile only. */
:is(.ml-brandband, .ml-vpromo) p.ml-brandband__text {
	margin: 0.75rem 0 0;
	color: rgb(255 255 255 / 0.8);
	font-size: 0.875rem;
	line-height: 1.25rem;
	max-width: 36rem;
}

@media (min-width: 768px) {
	:is(.ml-brandband, .ml-vpromo) p.ml-brandband__text { font-size: 1rem; line-height: 1.5rem; }
}

/* The CTA row's own rhythm, for the same reason: `.ml-content .wp-block-buttons`
 * and the article net move it to 24/24 where the design says 20/0. */
:is(.ml-brandband, .ml-vpromo) .wp-block-buttons.ml-brandband__ctas { margin: 1.25rem 0 0; }

/* Same guard for the section header the Yhteistyökumppanit pattern carries — it is
 * readable dark-on-light either way, but the article net still resized it
 * (measured in an uutinen: eyebrow 16 px instead of 11, title 30 px with a 40 px
 * top margin instead of 24/4). Values verbatim from § Etusivu sections. */
.ml-secthead p.ml-secthead__eyebrow {
	margin: 0;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.2em;
	color: hsl(182 100% 9% / 0.7);
}
@media (min-width: 768px) {
	.ml-secthead p.ml-secthead__eyebrow { font-size: 0.875rem; line-height: 1.25rem; }
}
.ml-secthead :is(h1, h2, h3).ml-secthead__title {
	margin: 0.25rem 0 0;
	font-size: 1.25rem;
	line-height: 1.75rem;
	font-weight: 800;
	letter-spacing: -0.025em;
	text-transform: uppercase;
	color: var(--wp--preset--color--foreground);
}
@media (min-width: 768px) {
	.ml-secthead :is(h1, h2, h3).ml-secthead__title { font-size: 1.5rem; line-height: 2rem; }
}

/* ── Customer blocks INSIDE the Yhteistyökumppanit band ──────────────────────
 * (customer 2026-08-14, /1153-2/: no gap between the pattern and the text, and
 * the text runs the whole band wide instead of staying in the text column)
 * With the enter guard on (editor-chrome-enter-guard.js), Enter never escapes a
 * group — so text typed right after the logo grid lands INSIDE .ml-partners as
 * the band's direct child. No net reached it there: the band is an alignwide
 * FLOW group, so core's blockGap-0 reset zeroed every margin (the text sat glued
 * to the logos and to itself, measured 0 px) and each paragraph spanned the whole
 * band (1392 px at 1440 against the 760 text column). The Sisältölaatikko already
 * carries its own inside-the-band net (§ BRAND BAND above); this is the partners
 * equivalent, and every value is the § BARE PAGE CONTENT mirror — keep them in
 * sync: the same 760 cap, left-flush without the bare net's max() anchor or
 * !important (the band is alignwide, so its left edge IS the text column's left
 * edge at every width, and a flow group has no auto-centring to fight), the same
 * body rhythm and typography, and the grid→text boundary reads as the section
 * gap — the same 3/4 rem the text would get if it sat AFTER the band, so a block
 * that landed inside the band is indistinguishable from one that escaped it.
 * The etusivu is untouched: its band ends with the sl-ml/partners block, which
 * renders the same .ml-partners__grid class the net excludes, and nothing
 * follows it. `.block-list-appender` is excluded so the canvas's own ⊕ appender
 * doesn't pick up the section gap and pad the band in the editor only. */
.ml-partners > :not(.ml-secthead):not(.ml-partners__grid):not(.block-list-appender) { max-width: 760px; margin-inline: 0 auto; }
/* …with one exception the net above cannot see: in the BLOCK EDITOR a server-rendered block is
 * wrapped in an unclassed <div>, so the logo grid is no longer a direct child of .ml-partners and
 * the wrapper collects the 760 cap instead. The customer saw the result as "yhteistyökumppanit is
 * very small in editor mode" (2026-08-20): the canvas drew a 760px, 4-column grid where the front
 * end runs the full alignwide 8. Release only the wrapper that holds the grid — twice, because
 * :has() is the precise form but a browser without it drops the whole selector, and the
 * editor-scoped fallback (unclassed direct-child div, which no real content block ever is) keeps
 * the fix working there regardless. Both are overrides, so a dropped line costs nothing. */
/* Specificity note: the net above is (0,4,0) — :not() lends its argument's weight, so a plain
 * `.ml-partners > *:has(…)` at (0,2,0) loses and the cap stays. Both overrides therefore repeat
 * the net's own :not() chain and add one selector on top. */
.ml-partners > :not(.ml-secthead):not(.ml-partners__grid):not(.block-list-appender):has(> .ml-partners__grid) { max-width: none; margin-inline: 0; }
.editor-styles-wrapper .ml-partners > :not(.ml-secthead):not(.ml-partners__grid):not(.block-list-appender):not([class]) { max-width: none; margin-inline: 0; }
.ml-partners > .ml-partners__grid + :not(.block-list-appender) { margin-block-start: 3rem; }
.ml-partners > p { margin: 1rem 0; font-size: 15px; line-height: 1.5; color: var(--wp--preset--color--foreground); }
.ml-partners > :is(h2, h3, h4) { margin: 2.5rem 0 0.75rem; font-weight: 800; color: var(--wp--preset--color--brand-teal); letter-spacing: -0.025em; }
.ml-partners > h2 { font-size: 1.5rem; line-height: 2rem; }
.ml-partners > h3 { font-size: 1.25rem; line-height: 1.75rem; }
.ml-partners > h4 { font-size: 1.125rem; line-height: 1.75rem; }
.ml-partners > :is(ul, ol) { margin: 1rem 0; padding-left: 1.25rem; }
.ml-partners > ul { list-style: none; padding-left: 0; }
.ml-partners > ul li { position: relative; padding-left: 1.25rem; margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
.ml-partners > ul li::before { content: ""; position: absolute; left: 0; top: 0.6em; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--wp--preset--color--brand-lime); }
.ml-partners > ol { list-style: decimal; padding-left: 1.5rem; }
.ml-partners > ol li { margin: 0.5rem 0; font-size: 15px; line-height: 1.5; }
.ml-partners > ol li::marker { color: var(--wp--preset--color--brand-teal); font-weight: 800; }
@media (min-width: 768px) {
	.ml-partners > .ml-partners__grid + :not(.block-list-appender) { margin-block-start: 4rem; }
	.ml-partners > p { font-size: 1rem; line-height: 1.5rem; }
	.ml-partners > h2 { font-size: 1.875rem; line-height: 2.25rem; }
	.ml-partners > h3 { font-size: 1.5rem; line-height: 2rem; }
	.ml-partners > h4 { font-size: 1.25rem; line-height: 1.75rem; }
	.ml-partners > :is(ul, ol) li { font-size: 1rem; line-height: 1.5rem; }
}

/* …and the same trap in a `.ml-sect-gap` / `.ml-sect-gap-sm` wrapper (the spacing groups the
 * etusivu seed wraps around an ad or a carousel): text landing inside one got the full band
 * width and zero margins, exactly like the partners case above (measured on the gap sweep:
 * p at 1392px, mt/mb 0, glued to the ad). Same medicine, compact — width to the text column,
 * body-paragraph rhythm, and the block→text boundary is the section gap. Cap ONLY typed
 * text-ish blocks: the etusivu seed's gap wrappers also hold designed section patterns
 * (.ml-nc carousel, .ml-clubstrip, .ml-ng) that are NOT sl-ml/* blocks — a :not(sl-ml)
 * blanket squeezed the full-bleed carousel to the 760 column (2026-08-14 regression). */
:is(.ml-sect-gap, .ml-sect-gap-sm) > :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, table, figure):not(.alignwide):not(.alignfull) { max-width: 760px; margin-inline: 0 auto; }
:is(.ml-sect-gap, .ml-sect-gap-sm) > :is([class*="wp-block-sl-ml-"], [class*="wp-block-ml-"]) + :not(.block-list-appender) { margin-block-start: 3rem; }
:is(.ml-sect-gap, .ml-sect-gap-sm) > p { margin: 1rem 0; font-size: 15px; line-height: 1.5; color: var(--wp--preset--color--foreground); }
@media (min-width: 768px) {
	:is(.ml-sect-gap, .ml-sect-gap-sm) > :is([class*="wp-block-sl-ml-"], [class*="wp-block-ml-"]) + :not(.block-list-appender) { margin-block-start: 4rem; }
	:is(.ml-sect-gap, .ml-sect-gap-sm) > p { font-size: 1rem; line-height: 1.5rem; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * PAGE SECTION RHYTHM — ONE gap between stacked patterns  (customer 2026-08-13:
 * "check every pattern so the gaps are perfect, right now if i add image to page
 * and some other pattern theres no gap")
 * ═══════════════════════════════════════════════════════════════════════════
 * Measured before this block (page.html at 1440, one page per pattern): EVERY
 * pattern-to-pattern boundary was ZERO — the two Mainospaikat, the ottelunauha,
 * Valmentajat, and both sidebar minis butted edge to edge — and an image set to
 * Leveä / Koko leveys sat flush against the pattern above it, because the
 * alignment guard at the top of this file zeroes `margin-block-start` on every
 * aligned child of a constrained layout and throws the figure's own 2rem away.
 * Whatever gap DID appear came from whichever neighbour happened to own a
 * margin: 16 px after a paragraph, 32 px after an image, 64 px for the
 * Sisältölaatikko / Yhteistyökumppanit. Five values, none of them a decision.
 *
 * The contract: a boundary that touches a SECTION block is the design's section
 * gap, 3rem / 4rem ≥768 — the value `.ml-brandband` and `.ml-partners` already
 * carry, so the two patterns the customer meets most keep their exact spacing
 * and everything else joins them. Body rhythm is untouched (16 px between
 * paragraphs, 12 px under a heading, 32 px around a figure): those are the
 * design's own values, and a heading still HUGS the section it introduces —
 * hence the `:not(h1)…:not(h6)` on the preceding side.
 *
 * `!important` throughout for one reason: the guard at the top of this file is
 * `!important`, and every pattern is alignwide or alignfull.
 *
 * TWO blocks are NEUTRAL — a boundary next to them keeps exactly what it had:
 *   `.ml-pagehero`  the data-view pages (ottelut, sarjataulukko, pistepörssi,
 *                   joukkueet, pelaaja, ottelu) are hero + one element and their
 *                   reviewed hero→view gap is the element's own padding.
 *   `.ml-content`   a Sisältöalue pads itself (40/64 at ≥768) and every seeded
 *                   content page is built from it — including the six that stack
 *                   a data view under a band (/naiset|miehet/pelaajarekisteri,
 *                   /naiset|miehet/valmentajat, the tilastot pages), all
 *                   verified unchanged.
 */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :not(.ml-pagehero):not(.ml-content):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) + :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-sect-gap, .ml-sect-gap-sm, .wp-block-group.alignwide, .wp-block-group.alignfull):not(.ml-content),
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-sect-gap, .ml-sect-gap-sm, .wp-block-group.alignwide, .wp-block-group.alignfull):not(.ml-pagehero):not(.ml-content) + :not(.ml-content) {
	margin-block-start: 3rem !important;
}
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :not(.ml-pagehero):not(.ml-content):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) + :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-sect-gap, .ml-sect-gap-sm, .wp-block-group.alignwide, .wp-block-group.alignfull):not(.ml-content),
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-sect-gap, .ml-sect-gap-sm, .wp-block-group.alignwide, .wp-block-group.alignfull):not(.ml-pagehero):not(.ml-content) + :not(.ml-content) {
		margin-block-start: 4rem !important;
	}
}

/* The five data views that carry their dedicated page's container padding
 * (`main .wp-block-ml-standings|pisteporssi|livescores|livescore|teams` above —
 * the design's MainContainer py-6 md:py-8, 24/32 px) would ADD it to the gap:
 * 96 px against a Sisältölaatikko's 64, and 128 px between two of them. The
 * padding is invisible whitespace on a transparent wrapper, so it reads as gap —
 * drop it on the sides where the section gap owns the boundary and the perceived
 * gap is 48/64 everywhere. Both NEUTRAL neighbours (and a heading, which hugs)
 * keep it, so hero → view is untouched; `:has(+ …)` keeps it on the LAST section
 * of a page, which still needs its breathing room above the footer. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :not(.ml-pagehero):not(.ml-content):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) + :is(.wp-block-ml-standings, .wp-block-ml-pisteporssi, .wp-block-ml-livescores, .wp-block-ml-livescore, .wp-block-ml-teams) {
	padding-block-start: 0 !important;
}
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :is(.wp-block-ml-standings, .wp-block-ml-pisteporssi, .wp-block-ml-livescores, .wp-block-ml-livescore, .wp-block-ml-teams):has(+ :not(.ml-content)) {
	padding-block-end: 0 !important;
}

/* ── The same contract INSIDE a Sisältöalue ──────────────────────────────────
 * A pattern dropped into a band is one level down, so the rules above (direct
 * children of post-content) can't see it — measured in a band before this:
 * 64 px above a Sisältölaatikko but 16 px below it, 16 px + the view's own
 * padding for a Pistepörssi, and 16/16 for a Mainospaikka (which has no padding
 * of its own, so the ad artwork nearly touched the paragraph). Same gap, same
 * heading-hug exception; the band's own padding still owns its first and last
 * child, which is why these are `+` rules only.
 * No `!important` needed here (a band is a FLOW group, so the alignment guard
 * never fires) and no editor mirror either — the band is a real block, so the
 * canvas gets these very rules through add_editor_style. */
/* `.ml-cardgrid` joined 2026-08-14 (gap sweep): the Korttiruudukko is a section-like unit —
 * bordered cards — but sat only the 16px body rhythm from the text the customer types after
 * it inside the same Sisältöalue, where every other pattern boundary reads 3/4rem. No seeded
 * or ported page has a sibling next to its cardgrid (checked: the grid is its wrapper's only
 * child everywhere), so only customer-typed neighbours move. */
.ml-content > :not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) + :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-cardgrid),
.ml-content > :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-cardgrid) + * {
	margin-block-start: 3rem;
}
@media (min-width: 768px) {
	.ml-content > :not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) + :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-cardgrid),
	.ml-content > :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-cardgrid) + * {
		margin-block-start: 4rem;
	}
}
/* …and the five padded views drop their dedicated page's container padding in a
 * band too: the band pads its own first/last child, and every inner boundary is
 * now the section gap. */
.ml-content > :is(.wp-block-ml-standings, .wp-block-ml-pisteporssi, .wp-block-ml-livescores, .wp-block-ml-livescore, .wp-block-ml-teams) {
	padding-block: 0;
}

/* An ALIGNED content block gets its own rhythm back. The guard at the top of this
 * file zeroes margin-block-start on every aligned child, which is right for the
 * design's stacked full-bleed sections but wrong for a Kuva the customer set to
 * Leveä: measured 0 px between a Sisältöalue and the image below it — the
 * reported "no gap". 2rem = exactly what the same block gets unaligned, so an
 * image's spacing no longer depends on its width setting. The section rules above
 * out-specify this one (0,7,0 vs 0,5,0), so a pattern → wide image boundary is
 * still the section gap. Takes over from the narrower alignwide
 * table/gallery/embed/video rule in § BARE PAGE CONTENT for every adjacency; that
 * one still owns the case this pair can't match, a wide table as the page's very
 * first block. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > :not(.ml-pagehero) + :is(figure, .wp-block-image, .wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video, .wp-block-buttons, .wp-block-quote, blockquote, .wp-block-details, pre, ul, ol, h1, h2, h3, h4, h5, h6, p):is(.alignwide, .alignfull) {
	margin-block-start: 2rem !important;
}
/* …and directly under the hero it is the same 1.5/2.5rem an UNALIGNED first block
 * gets (§ BARE PAGE CONTENT's `.alignfull + …` rule, which the guard's
 * `!important` was beating): measured 0 px above both alignwide headings on
 * /seurat, and 32 px above a wide table where the unaligned one had 40. */
:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .ml-pagehero + :is(figure, .wp-block-image, .wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video, .wp-block-buttons, .wp-block-quote, blockquote, .wp-block-details, pre, ul, ol, h1, h2, h3, h4, h5, h6, p):is(.alignwide, .alignfull) {
	margin-block-start: 1.5rem !important;
}
@media (min-width: 768px) {
	:is(.ml-page, .ml-sisaltosivu) > .wp-block-post-content > .ml-pagehero + :is(figure, .wp-block-image, .wp-block-table, .wp-block-gallery, .wp-block-embed, .wp-block-video, .wp-block-buttons, .wp-block-quote, blockquote, .wp-block-details, pre, ul, ol, h1, h2, h3, h4, h5, h6, p):is(.alignwide, .alignfull) {
		margin-block-start: 2.5rem !important;
	}
}

/* ── Article body: the same one-gap contract at the article's own 2rem scale ──
 * `.ml-article__body [class*="wp-block-ml-"] { margin: 2rem 0 }` covers the ml/*
 * views only, so inside an uutinen a Sisältölaatikko kept its page-scale 64/0
 * and a Mainospaikka got nothing at all (measured: Yhteistyökumppanit →
 * Mainospaikka = 0 px). One value for every section block, matching the 2rem the
 * article's own figures and data views already use. */
.ml-article__body > :is([class*="wp-block-ml-"], [class*="wp-block-sl-ml-"], .ml-brandband, .ml-partners, .ml-content, .ml-sect-gap, .ml-sect-gap-sm) {
	margin-block: 2rem;
}
/* The five data views' own `main .wp-block-ml-…` container padding (24/32 px) is
 * their DEDICATED page's MainContainer, and inside an article body it lands on top
 * of the 2rem above — 64 px against every other section's 32, and 32 px more than
 * the editor canvas shows (`main` doesn't exist there, so the padding never
 * applied in the editor: a live/editor disagreement as well as an uneven gap). */
.ml-article__body > :is(.wp-block-ml-standings, .wp-block-ml-pisteporssi, .wp-block-ml-livescores, .wp-block-ml-livescore, .wp-block-ml-teams) {
	padding-block: 0;
}
/* A Sisältöalue (or the Korttiruudukko, which ships inside one) is alignfull, so
 * inside the article card it pulled itself 24 px past the body's text column on
 * each side (measured x=64 w=944 against the body's 88/896). Clamp it to the
 * column and let the rule above own its rhythm — the same reset
 * `.ml-content .ml-content` gets when one band nests in another. */
.ml-article__body > .ml-content {
	max-width: none !important;
	margin-inline: 0 !important;
	padding-inline: 0 !important;
	padding-block: 0 !important;
}

/* Sivupohja (template) edit view: core swaps the page's real content for generic description
 * paragraphs — next to the fully-rendered header/footer it reads as a broken page. Dress it as
 * a labelled note that points at the real editing surface. Editor-only (add_editor_style).
 *
 * Detector, both halves load-bearing:
 *   [data-type="core/post-content"] — the post-content BLOCK, i.e. a template view. In the POST
 *     editor (article/page, renderingMode post-only) core puts the same `wp-block-post-content`
 *     class on the canvas ROOT container (`.is-root-container`, no data-type, not a block) — so
 *     without this the note also sat on top of every article whose body was still empty
 *     (customer, tasklist 2026-08-13: "when editing article remove text 'Sivun sisältö…'").
 *   :not(:has(…__block)) — no nested block wrappers, i.e. core's description placeholder rather
 *     than a real content tree, so the rule can never touch actual page content. */
.editor-styles-wrapper .wp-block-post-content[data-type="core/post-content"]:not(:has(.block-editor-block-list__block)) {
	margin: 2.5rem auto;
	padding: 1.5rem 2rem;
	border: 1px dashed rgb(16 59 44 / 0.35);
	border-radius: 12px;
	background: rgb(16 59 44 / 0.04);
}
.editor-styles-wrapper .wp-block-post-content[data-type="core/post-content"]:not(:has(.block-editor-block-list__block))::before {
	content: 'Sivun sisältö — muokataan kohdassa Sivut → (avaa sivu), ei sivupohjassa.';
	display: block;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-size: 13px;
	color: var(--wp--preset--color--brand-teal);
	margin-bottom: 0.75rem;
}
.editor-styles-wrapper .wp-block-post-content[data-type="core/post-content"]:not(:has(.block-editor-block-list__block)) > p {
	color: rgb(16 59 44 / 0.55);
	font-size: 14px;
	line-height: 1.5;
	margin: 0.25rem 0;
}
/* When editor-template-preview.js swaps the placeholder for the LIVE page (.sl-ml-tpl-live),
 * core's description paragraphs hide, the note keeps pointing at the real editing surface, and
 * the frame runs edge to edge inside the band. These carry the same [data-type] as the note rules
 * they override — source order alone no longer decides now that the note rule matches on the
 * attribute too (it would otherwise out-specify these and re-label the live preview). */
.editor-styles-wrapper .wp-block-post-content[data-type="core/post-content"].sl-ml-tpl-live > p { display: none; }
.editor-styles-wrapper .wp-block-post-content[data-type="core/post-content"].sl-ml-tpl-live { padding: 0.75rem 0 0; border: 0; background: none; }
/* No label once the LIVE page is rendered: the preview speaks for itself, and the note only sat
 * on top of it. The dashed placeholder note above still applies to templates with no preview. */
.editor-styles-wrapper .wp-block-post-content[data-type="core/post-content"].sl-ml-tpl-live::before { content: none; }
/* Full-bleed inside the constrained post-content layout: the live page is a full-width design,
 * so previewing it at the 760px text column renders the tablet layout instead of the page the
 * visitor sees. Standard full-bleed formula — the canvas's own width is the viewport here. */
.editor-styles-wrapper .wp-block-post-content[data-type="core/post-content"].sl-ml-tpl-live > .sl-ml-tpl-preview {
	width: 100vw;
	max-width: none;
	margin-left: calc(50% - 50vw);
}

/* ═══ ETUSIVUN VIDEONOSTO (source VideoPromo.tsx) ═══════════════════════════
 * The "18 seuraa" brand band's video variant: same gradient panel and copy
 * column (the text reuses the .ml-brandband__* type classes), with a chrome-
 * less looping YouTube embed filling the right half (desktop) / the top
 * (mobile). The scripted video area is the sl-ml/video-embed block; overlay
 * gradients hide YouTube's own chrome. Layout-only rules — safe at file end. */
.ml-vpromo {
	position: relative;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	margin-top: 1.5rem;
	border-radius: 1rem;
	color: #fff;
	min-height: 320px;
	background: linear-gradient(to bottom right, var(--wp--preset--color--brand-teal), #0a3b3a, var(--wp--preset--color--brand-teal));
}
/* VideoPromo.tsx:84 swaps the section gradient at md — teal→teal→#0a3b3a (dark corner
 * bottom-right, under the video) instead of the mobile teal→#0a3b3a→teal. Without the
 * swap the desktop blend behind the video reads visibly different from the design. */
@media (min-width: 768px) {
	.ml-vpromo {
		background: linear-gradient(to bottom right, var(--wp--preset--color--brand-teal), var(--wp--preset--color--brand-teal), #0a3b3a);
	}
}
/* The frame boots hidden (front end only — render.php adds --boot) and fades in on the
 * player's first REAL playing state, so YouTube's start chrome (big play button, spinner,
 * white iframe) never shows; until then the pane is the design's plain teal. video-promo.js
 * adds is-playing (4s fallback if the event stream never comes up). */
.ml-vembed--boot .ml-vembed__frame { opacity: 0; }
.ml-vembed--boot.is-playing .ml-vembed__frame { opacity: 1; transition: opacity .4s ease; }
/* Both glow circles sit on the LEFT in this variant (VideoPromo.tsx) — the right is video. */
.ml-vpromo::before,
.ml-vpromo::after {
	content: "";
	position: absolute;
	width: 360px;
	height: 360px;
	border-radius: 50%;
	filter: blur(64px);
	pointer-events: none;
	z-index: 1;
	background: hsl(182 100% 9% / 0.2);
}
.ml-vpromo::before { left: -5rem; top: -6rem; }
.ml-vpromo::after { left: -6rem; bottom: -6rem; }
@media (min-width: 768px) {
	.ml-vpromo { margin-top: 2rem; min-height: 400px; }
	.ml-vpromo::before,
	.ml-vpromo::after { background: hsl(59 85% 47% / 0.1); }
}

.ml-vpromo__content {
	position: relative;
	z-index: 2;
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	justify-content: center;
	padding: 1.5rem;
}
@media (min-width: 768px) { .ml-vpromo__content { width: 50%; padding: 2.5rem; } }
@media (min-width: 1024px) { .ml-vpromo__content { padding: 3rem; } }
/* Same wrap net as the brand band's own copy (see § customer-added blocks). */
.ml-vpromo__content > * { min-width: 0; }

.ml-vembed {
	position: relative;
	width: 100%;
	aspect-ratio: 16 / 9;
	min-height: 180px;
	overflow: hidden;
	background: var(--wp--preset--color--brand-teal);
}
@media (min-width: 768px) {
	.ml-vembed {
		position: absolute;
		top: 0;
		bottom: 0;
		right: 0;
		width: 50%;
		height: 100%;
		aspect-ratio: auto;
		min-height: 0;
	}
}
/* EDITOR ONLY (the wrapper exists just on the canvas): the sl-ml/video-embed block's own
 * wrapper takes the video pane's geometry, so clicking the video selects the video block
 * (its YouTube / own-file settings) instead of the surrounding group. */
.ml-vpromo > [data-type="sl-ml/video-embed"] { position: relative; }
@media (min-width: 768px) {
	.ml-vpromo > [data-type="sl-ml/video-embed"] { position: absolute; top: 0; right: 0; bottom: 0; width: 50%; }
	.ml-vpromo > [data-type="sl-ml/video-embed"] .ml-vembed { right: auto; left: 0; width: 100%; }
}
/* The iframe is oversized and centered so the 16:9 video COVERS the pane (the
 * source's w-[260%]/[220%]/[200%] ladder); pointer-events off = YouTube's own
 * hover chrome can never appear. */
.ml-vembed__frame {
	pointer-events: none;
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	width: 100%;
	aspect-ratio: 16 / 9;
	height: auto;
	border: 0;
}
@media (min-width: 768px)  { .ml-vembed__frame, .ml-vembed__poster { width: 260%; } }
@media (min-width: 1024px) { .ml-vembed__frame, .ml-vembed__poster { width: 220%; } }
@media (min-width: 1280px) { .ml-vembed__frame, .ml-vembed__poster { width: 200%; } }
/* An uploaded media-library video (is-file variant) covers the pane like the iframe does. */
.ml-vembed video.ml-vembed__frame { object-fit: cover; }
/* The video's own thumbnail over the frame only until the first REAL playing state —
 * it replaces the old plain-teal boot pane so the pre-play moment (~1 s) shows the video's
 * look instead of an empty box, and it doubles as the placeholder when an engine blocks
 * muted autoplay (poster + our play button). Same geometry as the frame so the hand-off
 * is seamless. */
.ml-vembed__poster {
	pointer-events: none;
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	width: 100%;
	aspect-ratio: 16 / 9;
	height: auto;
	object-fit: cover;
	transition: opacity .5s ease;
}
.ml-vembed.is-playing .ml-vembed__poster { opacity: 0; visibility: hidden; transition: opacity .5s ease, visibility 0s .5s; }
/* Paused (video-promo.js is-paused, only after a real playing state): YouTube's paused chrome
 * (centre play button, title bar, "Watch on YouTube") is not removable with controls=0, so the
 * pane shows the video's thumbnail again over the fading frame and keeps OUR controls; play
 * brings the frame back. Frame opacity, not visibility, so the two cross-fade instead of the teal
 * backdrop flashing between them (customer 2026-08-27). */
.ml-vembed__frame { transition: opacity .5s ease; }
.ml-vembed.is-paused .ml-vembed__frame { opacity: 0; }
.ml-vembed.is-playing.is-paused .ml-vembed__poster { opacity: 1; visibility: visible; transition: opacity .5s ease; }
/* YouTube's start chrome (center play/pause pill) overlays the first ~4 s of playback and
 * no embed parameter removes it (measured: bare autoplay, API start, early seek, playsinline
 * on/off — always there). It shows exactly as in the design source: covering it was tried
 * and rejected (a full poster delays visible playback; a local blur disc read as an
 * artifact). An uploaded videoUrl (native <video>) has no such chrome. */

.ml-vembed__shield,
.ml-vembed__fade,
.ml-vembed__fade-left,
.ml-vembed__bar {
	position: absolute;
	z-index: 1;
	pointer-events: none;
}
.ml-vembed__shield { inset: 0; pointer-events: auto; cursor: default; }
/* Bottom-edge soften on mobile, left-edge on desktop (from-[#0a3b3a]/80 via-teal/20). */
.ml-vembed__fade {
	inset: 0;
	background: linear-gradient(to top, rgb(10 59 58 / 0.8), hsl(182 100% 9% / 0.2), transparent);
}
.ml-vembed__fade-left { display: none; }
/* Hides YouTube's top and bottom bars (h-16 gradients). */
.ml-vembed__bar { left: 0; right: 0; height: 4rem; }
.ml-vembed__bar.is-top { top: 0; background: linear-gradient(to bottom, var(--wp--preset--color--brand-teal), transparent); }
.ml-vembed__bar.is-bottom { bottom: 0; background: linear-gradient(to top, #0a3b3a, transparent); }
@media (min-width: 768px) {
	.ml-vembed__fade { background: linear-gradient(to right, var(--wp--preset--color--brand-teal), hsl(182 100% 9% / 0.3), transparent); }
	.ml-vembed__fade-left {
		display: block;
		top: 0;
		bottom: 0;
		left: 0;
		width: 33.3333%;
		background: linear-gradient(to right, var(--wp--preset--color--brand-teal), hsl(182 100% 9% / 0.7), transparent);
	}
}

.ml-vembed__controls {
	position: absolute;
	bottom: 0.75rem;
	right: 0.75rem;
	z-index: 2;
	display: flex;
	align-items: center;
	gap: 0.375rem;
}
.ml-vembed__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2rem;
	height: 2rem;
	padding: 0;
	border-radius: 9999px;
	background: hsl(182 100% 9% / 0.6);
	color: var(--wp--preset--color--brand-lime);
	-webkit-backdrop-filter: blur(12px);
	backdrop-filter: blur(12px);
	border: 1px solid rgb(255 255 255 / 0.25);
	cursor: pointer;
	transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.ml-vembed__btn:hover { background: var(--wp--preset--color--brand-lime); color: var(--wp--preset--color--brand-teal); }
.ml-vembed__btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp--preset--color--brand-lime); }
.ml-vembed__icowrap { display: inline-flex; }
.ml-vembed__icowrap.is-hidden { display: none; }

/* ═══ ETUSIVUN UUTISKIRJE (source NewsletterSignup.tsx) ═════════════════════
 * Full-width teal gradient panel: copy column left (native blocks), the
 * sl-ml/newsletter-signup form block right. Underline-style inputs, lime
 * accents, and a thank-you state the block's JS swaps in. Submissions are NOT
 * stored yet — the design source defers the newsletter-service integration
 * (see ROADMAP Decisions). Layout-only rules — safe at file end. */
.ml-nlsign {
	position: relative;
	overflow: hidden;
	margin-top: 3rem;
	border-radius: 1rem;
	color: #fff;
	background: linear-gradient(to bottom right, var(--wp--preset--color--brand-teal), #0a3b3a, var(--wp--preset--color--brand-teal));
}
@media (min-width: 768px) { .ml-nlsign { margin-top: 4rem; } }
/* Lime glows: right-top 420px @ 15%, left-bottom 360px @ 10% (blur-3xl). */
.ml-nlsign::before,
.ml-nlsign::after {
	content: "";
	position: absolute;
	border-radius: 50%;
	filter: blur(64px);
	pointer-events: none;
}
.ml-nlsign::before { right: -6rem; top: -7rem; width: 420px; height: 420px; background: hsl(59 85% 47% / 0.15); }
.ml-nlsign::after { left: -6rem; bottom: -7rem; width: 360px; height: 360px; background: hsl(59 85% 47% / 0.10); }

.ml-nlsign .ml-nlsign__grid {
	position: relative;
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 2rem;
	padding: 1.5rem;
}
@media (min-width: 768px) { .ml-nlsign .ml-nlsign__grid { padding: 2.5rem; } }
@media (min-width: 1024px) {
	.ml-nlsign .ml-nlsign__grid {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
		gap: 3.5rem;
		padding: 3rem;
	}
}
.ml-nlsign .ml-nlsign__grid > * { min-width: 0; }

/* Copy column type — same defensive specificity as the brand band (the article/
 * content nets otherwise resize these on Sisältöalue-embedded uses). */
.ml-nlsign p.ml-nlsign__eyebrow {
	margin: 0 0 0.75rem;
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.22em;
	color: var(--wp--preset--color--brand-lime);
}
@media (min-width: 768px) {
	.ml-nlsign p.ml-nlsign__eyebrow { font-size: 0.875rem; line-height: 1.25rem; }
}
.ml-nlsign :is(h2, h3).ml-nlsign__title {
	margin: 0;
	font-size: 1.5rem;         /* text-2xl 24px */
	font-weight: 800;
	line-height: 1.25;         /* leading-tight */
	letter-spacing: -0.025em;  /* tracking-tight */
	color: #fff;
	text-transform: none;
}
@media (min-width: 768px) {
	.ml-nlsign :is(h2, h3).ml-nlsign__title { font-size: 2.25rem; line-height: 2.5rem; }
}
.ml-nlsign p.ml-nlsign__text {
	margin: 1rem 0 0;
	color: rgb(255 255 255 / 0.8);
	font-size: 0.875rem;
	line-height: 1.25rem;
	max-width: 28rem; /* max-w-md */
}
@media (min-width: 768px) {
	.ml-nlsign p.ml-nlsign__text { font-size: 1rem; line-height: 1.5rem; }
}

/* Form column (the block). space-y-6 rows; names side-by-side ≥640px. */
@media (min-width: 1024px) { .ml-nlsign__form-col { padding-left: 1rem; } }
.ml-nlsign__form { display: flex; flex-direction: column; gap: 1.5rem; }
/* The REAL subscription form is the customer's Mailchimp (mc4wp form 1477) whose HTML carries
 * the design's field classes — the mc4wp wrapper itself just needs the design's form column
 * flow, and its response alerts the design's message look (lime, like __error). */
.ml-nlsign .mc4wp-form-fields { display: flex; flex-direction: column; gap: 1.5rem; }
.ml-nlsign .mc4wp-response .mc4wp-alert,
.ml-nlsign .mc4wp-response p { margin: 1rem 0 0; font-size: 0.9375rem; line-height: 1.5; font-weight: 600; color: var(--wp--preset--color--brand-lime); }
.ml-nlsign__names { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.25rem; }
@media (min-width: 640px) { .ml-nlsign__names { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; } }
.ml-nlsign__label {
	display: block;
	margin: 0 0 0.25rem;
	font-size: 0.75rem; /* text-xs (12/16) — NewsletterSignup.tsx labelClass */
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.18em;
	color: var(--wp--preset--color--brand-lime);
}
.ml-nlsign__input {
	width: 100%;
	background: transparent;
	border: 0;
	border-bottom: 2px solid rgb(255 255 255 / 0.4);
	border-radius: 0;
	outline: none;
	padding: 0.625rem 0;
	font-size: 1rem;
	line-height: 1.5rem;
	color: #fff;
	transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
@media (min-width: 768px) { .ml-nlsign__input { font-size: 1.125rem; line-height: 1.75rem; } }
.ml-nlsign__input::placeholder { color: rgb(255 255 255 / 0.6); }
.ml-nlsign__input:focus { border-bottom-color: var(--wp--preset--color--brand-lime); }

.ml-nlsign__consent { display: flex; align-items: flex-start; gap: 0.75rem; }
.ml-nlsign__check {
	margin-top: 0.125rem;
	height: 1rem;
	width: 1rem;
	flex-shrink: 0;
	border-radius: 0.125rem;
	border: 2px solid rgb(255 255 255 / 0.5);
	background: transparent;
	accent-color: var(--wp--preset--color--brand-lime);
}
.ml-nlsign__check:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp--preset--color--brand-lime); }
.ml-nlsign__consent-label { font-size: 0.875rem; line-height: 1.25rem; color: rgb(255 255 255 / 0.8); }
.ml-nlsign__consent-label a {
	font-weight: 600;
	color: #fff;
	text-decoration: underline;
	text-decoration-color: var(--wp--preset--color--brand-lime);
	text-decoration-thickness: 2px;
	text-underline-offset: 4px;
}
.ml-nlsign__consent-label a:hover { text-decoration-color: #fff; }

.ml-nlsign__error { font-size: 0.875rem; line-height: 1.25rem; font-weight: 600; color: var(--wp--preset--color--brand-lime); }
/* No error → screen-reader-only (the source toggles sr-only on the live region). */
.ml-nlsign__error.is-empty {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* Submit — the brand-band lime CTA pill, px-6 py-3. */
.ml-nlsign__submit {
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.75rem 1.5rem;
	border-radius: 9999px;
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
	border: 2px solid var(--wp--preset--color--brand-lime);
	font-size: 0.75rem;
	line-height: 1rem;
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	cursor: pointer;
	transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.ml-nlsign__submit:hover { background: var(--wp--preset--color--brand-teal); color: var(--wp--preset--color--brand-lime); }
.ml-nlsign__submit:focus-visible { outline: none; box-shadow: 0 0 0 2px #fff; }
.ml-nlsign__arrow { margin-top: -1px; transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); }
.ml-nlsign__submit:hover .ml-nlsign__arrow { transform: translateX(2px); }

/* Thank-you state (swapped in by newsletter-signup.js). */
.ml-nlsign__done { height: 100%; display: flex; flex-direction: column; justify-content: center; gap: 0.75rem; }
.ml-nlsign__done-circle {
	display: grid;
	place-items: center;
	width: 3rem;
	height: 3rem;
	border-radius: 50%;
	background: var(--wp--preset--color--brand-lime);
	color: var(--wp--preset--color--brand-teal);
}
.ml-nlsign p.ml-nlsign__done-title { margin: 0; font-size: 1.25rem; line-height: 1.75rem; font-weight: 800; color: #fff; }
@media (min-width: 768px) { .ml-nlsign p.ml-nlsign__done-title { font-size: 1.5rem; line-height: 2rem; } }
.ml-nlsign p.ml-nlsign__done-text {
	margin: 0;
	color: rgb(255 255 255 / 0.8);
	font-size: 0.875rem;
	line-height: 1.25rem;
	max-width: 28rem;
}
@media (min-width: 768px) { .ml-nlsign p.ml-nlsign__done-text { font-size: 1rem; line-height: 1.5rem; } }
