/**
 * Site header — mega menu, utility bar, mobile drawer.
 *
 * Ports SiteHeader.tsx (source/src/components/SiteHeader.tsx) to a
 * server-rendered dynamic block. CSS-only hover + focus-within
 * dropdowns; JS only owns the mobile drawer + lang dropdown toggle.
 */

:root {
	--esteri-megamenu-gap: 12px;
	--esteri-header-h-sm: 64px;
	--esteri-header-h-lg: 88px;
}

/* The FSE header template part wraps this block in
 * <header class="wp-block-template-part">, whose box is only as tall as the
 * header itself (~130px) and sits as a sibling of <main> inside .wp-site-blocks.
 * A position:sticky on the inner .esteri-header-root therefore had no
 * containing-block travel room and unstuck the instant the page scrolled past
 * the header — the reported "nav bar doesn't stay when scrolling" bug. Make the
 * template-part wrapper itself sticky: its parent (.wp-site-blocks) spans the
 * full page, so sticky has room to pin. Scoped to the `header` tag so the
 * footer template part (footer.wp-block-template-part) is unaffected.
 * Mirrors SiteHeader.tsx `<header className="sticky top-0 z-[110]">`. */
header.wp-block-template-part {
	position: sticky;
	top: 0;
	z-index: 110;
}

.esteri-header-root {
	position: sticky;
	top: 0;
	z-index: 110;
}

/* The mega panel is `position: fixed; top: auto` (SiteHeader.tsx verbatim), so its
 * top is its static position — right under the nav row. Chromium resolves that
 * against the STUCK header; Firefox resolves it against the un-stuck layout and
 * adds the scroll distance, so after scrolling the "open" panel landed scrollY px
 * below the header, off-screen from ~800px down (measured 2026-09-28: Firefox
 * panel top = 77 + scrollY, Chromium 77 at every depth; the Lovable source has the
 * same bug). Making the sticky root the containing block for its fixed
 * descendants puts the static position inside the header in both engines, and the
 * root spans the viewport, so `left/right: 0` still reach the edges. Desktop only:
 * the mobile drawer is the other fixed descendant and must stay viewport-fixed. */
@media (min-width: 1024px) {
	.esteri-header-root { will-change: transform; }
}

/* Logged-in admin bar. WP fixes #wpadminbar at top:0 (32px; 46px at ≤782px) and
 * pushes the document down with html{margin-top}. A sticky header at top:0 then
 * slides UNDER the bar once the page scrolls, and the fixed mega-menu panel —
 * top:auto, i.e. its static position, which still carries that html margin —
 * lands 32px below the nav row: the "gap in the dropdown when the WordPress
 * bar is active" (measured 2026-09-18 at scrollY 400: gap 44px vs the 12px
 * design gap). Pinning the sticky wrappers below the bar with core's own
 * height variable (admin-bar.css sets it for both breakpoints) puts the header
 * back in view and the panel's static position back on the nav row (12px at
 * scrollY 0 and 400). The drawer gets the same offset so it does not open under
 * the bar. Editors only — visitors never have the bar. */
.admin-bar header.wp-block-template-part,
.admin-bar .esteri-header-root { top: var(--wp-admin--admin-bar--height, 32px); }
.admin-bar .esteri-drawer {
	top: var(--wp-admin--admin-bar--height, 32px);
	max-height: calc(100dvh - var(--wp-admin--admin-bar--height, 32px));
}

.esteri-header {
	background: hsl(0 0% 100%);
	transition: background-color 0.3s ease;
}

/* ─── Utility bar (top dark strip — desktop only) ─────────────────── */

.esteri-utility {
	display: none;
	border-bottom: 1px solid hsl(220 14% 88%);
	background: hsl(0 0% 100%);
	overflow: visible;                 /* lang menu drops below the bar at rest */
	max-height: 48px;                  /* source max-h-12; content is 40px tall */
	transition: max-height 0.3s ease, border-color 0.3s ease;
}
@media (min-width: 640px) {
	.esteri-utility { display: block; }
}
/* Source SiteHeader.tsx: on scroll the slim utility bar animates to max-h-0 +
 * border-transparent, leaving only the main bar pinned. site-header.js toggles
 * .is-scrolled with hysteresis (collapse y>60 / expand y<=5). Switch overflow to
 * hidden ONLY when collapsed (overflow:visible is needed for the lang dropdown at
 * rest); the JS closes the lang menu on scroll so nothing is left clipped. */
.esteri-header-root.is-scrolled .esteri-utility {
	max-height: 0;
	overflow: hidden;
	border-color: transparent;
}

.esteri-utility__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	height: 40px;
	/* Source: `flex items-center justify-between h-10` — no gap; the two ends
	 * are pushed apart by justify-between, not a gap. */
}

/* `p.` selector (0,1,1), exactly like `p.esteri-footer__tagline`: the tagline is
 * a core/paragraph now (so an author can put blocks next to it), and a plain
 * `.esteri-utility__tagline` (0,1,0) TIES with WP global styles'
 * `:root :where(p)` and loses on source order — measured line-height 24px
 * instead of 1, which made the box 24px tall and shifted it 4.5px up inside the
 * 40px bar. `margin: 0` for the same reason. */
p.esteri-utility__tagline,
.esteri-utility__tagline {
	display: none;
	font-family: 'Space Mono', monospace;
	font-size: 0.9375rem;   /* source text-sm = 15px (design sm override), was 14px */
	font-weight: 500;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: hsl(222 68% 33%);
	line-height: 1;
	margin: 0;
}
/* `p.` here too: the base rule above had to become (0,1,1) to beat
 * `:root :where(p)`, and specificity outranks media-query source order — a bare
 * (0,1,0) rule here lost and the tagline stayed display:none at every width. */
@media (min-width: 1024px) {
	p.esteri-utility__tagline,
	.esteri-utility__tagline { display: inline-flex; align-items: center; }
}

.esteri-utility__right {
	display: flex;
	align-items: center;
	gap: 1.25rem;
}

.esteri-utility__links {
	display: none;
	align-items: center;
	gap: 1.25rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
@media (min-width: 768px) { .esteri-utility__links { display: flex; } }
.esteri-utility__links a {
	font-size: 15px;
	font-weight: 500;
	color: hsl(222 30% 12%);
	text-decoration: none;
	padding: 2px 0;
	position: relative;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
.esteri-utility__links a::after {
	content: '';
	position: absolute;
	left: 0; right: 0; bottom: -2px;
	height: 2px;
	background: hsl(74 87% 40%);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.esteri-utility__links a:hover::after,
.esteri-utility__links a.is-current::after { transform: scaleX(1); }   /* current page — render.php */

.esteri-utility__socials {
	display: none;
	align-items: center;
	gap: 0.25rem;              /* source: social row is `flex items-center gap-1` (4px) */
	margin: 0;
	padding: 0;
	list-style: none;
}
@media (min-width: 768px) { .esteri-utility__socials { display: flex; } }
.esteri-utility__social-link {
	/* Source: each icon is a `w-7 h-7 inline-flex items-center justify-center
	 * text-foreground hover:text-brand-green` box — a uniform 28px hit-target so
	 * every glyph reads the same size, full-strength foreground (not 50% faded),
	 * green on hover. */
	display: inline-flex;
	width: 28px;
	height: 28px;
	align-items: center;
	justify-content: center;
	color: hsl(222 30% 12%);
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
	text-decoration: none;
}
.esteri-utility__social-link:hover { color: hsl(74 87% 40%); }

.esteri-utility__sep {
	display: none;
	width: 1px;
	height: 12px;
	background: hsl(220 14% 88%);
}
@media (min-width: 768px) { .esteri-utility__sep { display: block; } }

/* Language switcher in utility bar */
/* Source: the trigger is a DIRECT flex child of the `flex items-center gap-5`
 * utility row (Radix `asChild` renders no wrapper). We need this div for the
 * dropdown's positioning context, so it has to carry the centring itself —
 * as a plain block it lays the inline-flex button out on a text baseline and
 * the descender space under it lifts the whole control ~1.4px above the
 * utility links it sits beside. */
.esteri-utility__lang { position: relative; display: flex; align-items: center; }
.esteri-utility__lang-trigger {
	/* Source: the trigger carries `nav-link-hover nav-link-hover-flush` — the same
	 * green underline-sweep the utility links use — so it needs the relative
	 * position + ::after below. Without it the trigger had no hover feedback. */
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	background: transparent;
	border: 0;
	padding: 2px 0;
	cursor: pointer;
	font-size: 15px;
	font-weight: 500;
	color: hsl(222 30% 12%);
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
.esteri-utility__lang-trigger::after {
	content: '';
	position: absolute;
	left: 0; right: 0; bottom: 0;          /* nav-link-hover-flush: flush left/right, bottom:0 */
	height: 2px;
	background: hsl(74 87% 40%);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.esteri-utility__lang-trigger:hover::after,
.esteri-utility__lang-trigger:focus-visible::after,
.esteri-utility__lang-trigger[aria-expanded="true"]::after { transform: scaleX(1); }
.esteri-utility__lang-code {
	text-transform: uppercase;
	letter-spacing: 0.08em;
}
.esteri-utility__lang-menu {
	position: absolute;
	right: 0;
	top: calc(100% + 8px);
	min-width: 160px;
	margin: 0;
	padding: 6px;
	list-style: none;
	background: hsl(0 0% 100%);
	border: 1px solid hsl(220 14% 88%);
	border-radius: var(--radius, 4px);
	box-shadow: var(--shadow-card, 0 8px 24px -8px hsl(222 30% 12% / 0.15));
	z-index: 120;
}
.esteri-utility__lang-menu[hidden] { display: none; }
.esteri-utility__lang-menu a {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 8px 12px;
	font-size: 15px;
	color: hsl(222 30% 12%);
	text-decoration: none;
	border-radius: var(--radius, 4px);
}
.esteri-utility__lang-menu a:hover { background: hsl(220 30% 97%); }
.esteri-utility__lang-menu a.is-current { color: hsl(222 68% 33%); font-weight: 600; }
.esteri-utility__lang-code-mono {
	font-family: 'Space Mono', monospace;
	font-size: 15px;
	letter-spacing: 0.12em;
	color: hsl(222 10% 42%);
}
.esteri-utility__lang-menu a.is-current .esteri-utility__lang-code-mono { color: hsl(222 68% 33%); }

/* ─── Main bar ────────────────────────────────────────────────────── */

.esteri-header__bar {
	position: relative;
	z-index: 50;
	background: hsl(0 0% 100%);
	border-bottom: 1px solid hsl(220 14% 88%);
}

.esteri-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	/* min-height, not height (Saku, 2026-08-07: "if i add more pages to main
	 * menu it doesnt wrap it brokes"). The nav row is allowed to wrap now, and a
	 * fixed height would let the second line spill out of the white bar. The
	 * designed height is unchanged while the menu fits on one line, which is
	 * every current configuration. Safe to grow: `--esteri-header-h-lg` is read
	 * ONLY here (the mobile drawer's padding-top uses the -sm var), so no page
	 * offset depends on the desktop bar staying 88px. */
	min-height: var(--esteri-header-h-sm);
	/* Deliberately NOT flex-wrap here — only the nav row itself wraps. Letting
	 * the BAR wrap threw the search + "Ota yhteyttä" group onto its own line at
	 * the left edge (measured: CTA at x=100, y=141), which is worse than the
	 * problem. The bar stays one row: logo left, wrapped nav in the middle,
	 * actions pinned right. */
}
@media (min-width: 1024px) {
	.esteri-header__inner { min-height: var(--esteri-header-h-lg); }
}

.esteri-header__left {
	display: flex;
	align-items: center;
	gap: 24px;
	min-width: 0;
}
/* source: gap-6 lg:gap-8 min-[1280px]:gap-10 min-[1450px]:gap-12 — every step
 * is load-bearing; the lg + 1450 steps were missing (see the stepped-scale note
 * on .esteri-megamenu__top-link below). */
@media (min-width: 1024px) { .esteri-header__left { gap: 32px; } }
@media (min-width: 1280px) { .esteri-header__left { gap: 40px; } }
@media (min-width: 1450px) { .esteri-header__left { gap: 48px; } }

.esteri-header__logo {
	display: inline-flex;
	align-items: center;
	flex-shrink: 0;
	text-decoration: none;
}
.esteri-header__logo-img {
	/* source: h-8 lg:h-9 min-[1280px]:h-10 min-[1450px]:h-11 w-auto object-contain
	 * — no max-width in the source; capping width squashes the 4:1 logo. */
	height: 32px;
	width: auto;
	object-fit: contain;
	display: block;
}
@media (min-width: 1024px) { .esteri-header__logo-img { height: 36px; } }
@media (min-width: 1280px) { .esteri-header__logo-img { height: 40px; } }
@media (min-width: 1450px) { .esteri-header__logo-img { height: 44px; } }

/* The logo is a core/image now, so it is swapped with the normal image UI. Two
 * changes do the work of the old <a class="esteri-header__logo"><img> pair:
 *  - the FIGURE takes the anchor's role (the class lands on it), so it needs its
 *    figure margin and its inherited white background cleared;
 *  - the height steps move to a DESCENDANT selector, because a block cannot put a
 *    class on its own <img>.
 * No baseline trick is needed here, unlike the footer: `.esteri-header__left` is a
 * flex container, so the logo is a flex item and never sits in a line box. The
 * `.esteri-header__logo-img` rules above stay for the monolith. */
figure.esteri-header__logo {
	display: inline-flex;
	align-items: center;
	flex-shrink: 0;
	margin: 0;
	background: transparent;
}
figure.esteri-header__logo > a {
	display: flex;
	align-items: center;
	text-decoration: none;
}
/* `figure.…logo img` (0,1,2), not `.…logo img` (0,1,1): core's own
 * `.wp-block-image img { height: auto }` is (0,1,1) too, and it is printed after
 * this file — the tie went to core, the logo rendered at its natural 300×75 and
 * the whole bar grew 11px. */
figure.esteri-header__logo img {
	height: 32px;
	width: auto;
	object-fit: contain;
	display: block;
	max-width: none;
}
@media (min-width: 1024px) { figure.esteri-header__logo img { height: 36px; } }
@media (min-width: 1280px) { figure.esteri-header__logo img { height: 40px; } }
@media (min-width: 1450px) { figure.esteri-header__logo img { height: 44px; } }

.esteri-header__logo-text {
	font-weight: 700;
	font-size: 22px;
	color: hsl(222 68% 33%);
	letter-spacing: -0.02em;
}

/* ─── Megamenu (desktop) ──────────────────────────────────────────── */

.esteri-megamenu { display: none; }
@media (min-width: 1024px) { .esteri-megamenu { display: block; } }

.esteri-megamenu__top {
	display: flex;
	align-items: center;
	--esteri-mega-top-gap: 2px;
	gap: var(--esteri-mega-top-gap);
	margin: 0;
	padding: 0;
	list-style: none;
	/* nowrap keeps each LABEL on one line ("Huolto ja tuki" never splits); it does
	 * not stop the flex container from wrapping between items. */
	white-space: nowrap;
	/* Wrap to a second row instead of running past the bar. Measured before this
	 * (12 top-level items): the row overflowed the CTA by 417–641px and put
	 * 134–480px of horizontal scroll on the whole document at EVERY width up to
	 * 1920 — the menu was simply unusable once an author added a few pages
	 * (Saku: "if i add more pages to main menu it doesnt wrap it brokes").
	 * row-gap gives the wrapped line air; the bar is min-height so it grows. */
	flex-wrap: wrap;
	row-gap: 4px;
}
/* Both flex ancestors must be allowed to shrink below max-content, or the nav
 * never reaches its wrap point and just overflows as before. `.esteri-header__left`
 * already sets min-width:0; `.esteri-megamenu` is the block between it and the row. */
.esteri-megamenu { min-width: 0; }
/* source: gap-0.5 min-[1100px]:gap-1 min-[1280px]:gap-1.5 min-[1450px]:gap-2 */
@media (min-width: 1100px) { .esteri-megamenu__top { --esteri-mega-top-gap: 4px; } }
@media (min-width: 1280px) { .esteri-megamenu__top { --esteri-mega-top-gap: 6px; } }
@media (min-width: 1450px) { .esteri-megamenu__top { --esteri-mega-top-gap: 8px; } }

.esteri-megamenu__item {
	position: relative;
}

/* Horizontal hover bridge — fills the flex gap to the LEFT of each item
   (every item after the first) so moving the pointer sideways between top
   links never lands on dead space. Without it the cursor crosses the gap
   where no item is :hover, the open panel begins its 0.15s hide, and the
   menu flickers/disappears mid-transit. Absolutely positioned + sized to
   the gap var, so it adds NO layout — visual spacing is unchanged. Pairs
   with the vertical `.has-children::after` bridge below. */
.esteri-megamenu__item + .esteri-megamenu__item::before {
	content: '';
	position: absolute;
	top: 0;
	bottom: 0;
	right: 100%;
	width: var(--esteri-mega-top-gap, 6px);
	pointer-events: auto;
}

.esteri-megamenu__top-link {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	/* Stepped scale, verbatim from SiteHeader.tsx's top-level nav link:
	 *   px-1.5 min-[1100px]:px-2 min-[1280px]:px-2.5 min-[1450px]:px-3, py-2,
	 *   text-sm min-[1280px]:text-[15px] min-[1450px]:text-base
	 * (this design overrides `sm` to 15px, so text-sm == the 1280 step == 15px).
	 * The port previously ran 10/14/16px inline padding with no 1100 step — 4px
	 * per side × 6 items = +48px of nav width in the TIGHTEST desktop range,
	 * which is what pushed "Yhteystiedot" under the "Ota yhteyttä" CTA at
	 * 1024–1090. A dropped intermediate step in a stepped scale is a real defect,
	 * not a rounding choice: the source author sized each range to fit. */
	padding: 8px 6px;
	background: transparent;
	border: 0;
	font: inherit;
	font-size: 15px;
	line-height: 24px;          /* text-sm AND text-base both pair with 24px, so one
	                             * value covers every step of the stepped scale */
	font-weight: 500;
	color: hsl(222 30% 12%);
	text-decoration: none;
	cursor: pointer;
	white-space: nowrap;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
@media (min-width: 1100px) { .esteri-megamenu__top-link { padding: 8px 8px; } }
@media (min-width: 1280px) { .esteri-megamenu__top-link { padding: 8px 10px; font-size: 15px; } }
@media (min-width: 1450px) { .esteri-megamenu__top-link { padding: 8px 12px; font-size: 16px; } }

.esteri-megamenu__top-label {
	position: relative;
	display: inline-block;
}
.esteri-megamenu__top-label::after {
	content: '';
	position: absolute;
	left: 0; right: 0; bottom: -4px;
	height: 2px;
	background: hsl(74 87% 40%);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.esteri-megamenu__top-link:hover .esteri-megamenu__top-label::after,
.esteri-megamenu__item:hover .esteri-megamenu__top-label::after,
.esteri-megamenu__item:focus-within .esteri-megamenu__top-label::after,
.esteri-megamenu__item.is-current .esteri-megamenu__top-label::after { transform: scaleX(1); }
/* ^ is-current: the section of the page being viewed keeps its underline (Saku
 * 2026-11-05). The design has no active state, so every current-page cue in this
 * file is that link's own hover cue, held. render.php sets the classes. */
/* SiteHeader.tsx:349 — the top item whose panel is OPEN reads `text-muted-foreground`
 * (openDropdown === item.label); items without a panel keep `text-foreground` on hover.
 * The theme-wide link hover that used to tint every top link blue is gone (theme.json). */
.esteri-megamenu__item.has-children:hover > .esteri-megamenu__top-link,
.esteri-megamenu__item.has-children:focus-within > .esteri-megamenu__top-link { color: hsl(222 10% 42%); }

.esteri-megamenu__top-chev {
	transition: transform 0.2s;
}
.esteri-megamenu__item:hover .esteri-megamenu__top-chev,
.esteri-megamenu__item:focus-within .esteri-megamenu__top-chev { transform: rotate(180deg); }

.esteri-megamenu__item.has-children::after {
	/* Hover bridge — same height as the dropdown gap so the cursor
	   doesn't trigger mouseleave mid-transit. */
	content: '';
	position: absolute;
	left: 0; right: 0;
	top: 100%;
	height: var(--esteri-megamenu-gap);
	pointer-events: auto;
}

.esteri-megamenu__panel {
	position: fixed;
	left: 0;
	right: 0;
	top: auto;
	margin-top: var(--esteri-megamenu-gap);
	background: hsl(0 0% 100%);
	border-bottom: 1px solid hsl(220 14% 88%);
	box-shadow: 0 10px 32px -12px hsl(222 30% 12% / 0.18);
	z-index: 60;
	visibility: hidden;
	opacity: 0;
	transition: opacity 0.15s ease, visibility 0s linear 0.15s;
}
.esteri-megamenu__item.has-children:hover > .esteri-megamenu__panel,
.esteri-megamenu__item.has-children:focus-within > .esteri-megamenu__panel {
	visibility: visible;
	opacity: 1;
	transition: opacity 0.15s ease;
}
/* Scroll closes the open dropdown (source onScroll → setOpenDropdown(null)). The
 * desktop menu is CSS-hover-driven, so JS sets data-esteri-mega-suppress on scroll
 * to force-hide the hovered panel; entering a top item again clears it so a fresh
 * hover re-opens. !important beats the :hover/:focus-within open rule above. */
.esteri-header-root[data-esteri-mega-suppress] .esteri-megamenu__panel {
	visibility: hidden !important;
	opacity: 0 !important;
}
/* Escape closed this panel (site-header.js adds .is-dismissed until the pointer
 * leaves the item or focus moves out of it). The top link reads CLOSED again, so
 * it matches aria-expanded="false". A state the mouse-only source never reaches,
 * so no hover visual moves. */
.esteri-megamenu__item.has-children.is-dismissed > .esteri-megamenu__panel {
	visibility: hidden !important;
	opacity: 0 !important;
}
.esteri-megamenu__item.has-children.is-dismissed > .esteri-megamenu__top-link { color: hsl(222 30% 12%); }
.esteri-megamenu__item.is-dismissed .esteri-megamenu__top-chev { transform: none; }

.esteri-megamenu__panel-inner {
	padding: 32px var(--wire-edge) 24px;
}

/* Undo the top row's `white-space: nowrap` (.esteri-megamenu__top, needed so the
 * top-level labels never break) — it INHERITS into the dropdown, where the
 * source wraps freely (SiteHeader.tsx: the child desc is plain
 * `text-sm leading-snug`). Without this reset a long row description cannot
 * wrap: it runs past the column and is silently cut off by the row's
 * `overflow: hidden` (geometry checks can't see it — clipping hides overflow). */
.esteri-megamenu__panel { white-space: normal; }

.esteri-megamenu__grid {
	display: grid;
	gap: 32px;
	/* minmax(0,…) — a bare `1fr` is minmax(auto,1fr) and cannot shrink below its
	 * content's min-content width, so one long child description widened this
	 * column and pushed the rows out of the panel instead of wrapping. */
	grid-template-columns: 300px minmax(0, 1fr);
}

/* Feature tile (column 1) */
.esteri-megamenu__feature {
	position: relative;
	display: block;
	overflow: hidden;
	aspect-ratio: 4 / 5;
	background: hsl(222 30% 12%);
	border-radius: var(--radius, 4px);
	text-decoration: none;
}
.esteri-megamenu__feature-img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 0.5s ease-out;
}
.esteri-megamenu__feature:hover .esteri-megamenu__feature-img { transform: scale(1.04); }
.esteri-megamenu__feature-scrim {
	position: absolute;
	inset: 0;
	background: linear-gradient(180deg, hsl(222 30% 12% / 0) 30%, hsl(222 30% 12% / 0.85) 100%);
}
.esteri-megamenu__feature-body {
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	padding: 16px;
	color: hsl(0 0% 100%);
}
.esteri-megamenu__feature-sweep {
	display: block;
	width: 32px;
	height: 2px;
	margin-bottom: 8px;
	background: hsl(74 87% 40%);
	transform-origin: left;
	transition: transform 0.3s ease-out;
}
.esteri-megamenu__feature:hover .esteri-megamenu__feature-sweep { transform: scaleX(2.5); }
.esteri-megamenu__feature-title {
	display: block;
	font-size: 16px;
	font-weight: 700;
	line-height: 1.2;
}
.esteri-megamenu__feature-desc {
	display: block;
	margin-top: 4px;
	font-size: 15px;
	line-height: 1.4;
	color: hsl(0 0% 100% / 0.75);
}

/* Right column children */
.esteri-megamenu__children {
	display: grid;
	grid-template-columns: 1fr;
	gap: 4px;
	margin: 0;
	padding: 0;
	list-style: none;
	align-self: start;
}
.esteri-megamenu__children.is-two-col {
	/* minmax(0,1fr), NOT 1fr: with a bare 1fr the widest row description sets the
	 * track's min-content width, so a long desc (e.g. "Ilmankuivaajat uima-
	 * allastiloihin ja erikoiskohteisiin") made the two columns UNEVEN
	 * (260px/386px at 1024) and spilled the text past the panel edge instead of
	 * wrapping onto a second line. canonical-patterns.md § equal-split grid. */
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 4px;                 /* source: `grid gap-1` (4px) on both axes */
	/* Column-first fill (grid-auto-flow:column + grid-template-rows) is set
	 * inline per-panel in render.php since the row count = ceil(n/2) varies. */
}

.esteri-megamenu__child {
	position: relative;
	display: flex;
	align-items: flex-start;
	gap: 12px;
	padding: 12px 12px 12px 16px;
	border-radius: var(--radius, 4px);
	text-decoration: none;
	color: inherit;
	overflow: hidden;
	transition: background-color 0.15s ease;
}
.esteri-megamenu__child:hover,
.esteri-megamenu__child.is-current { background: hsl(220 30% 97%); }

.esteri-megamenu__child-rail {
	position: absolute;
	left: 0;
	top: 12px;
	bottom: 12px;
	width: 3px;
	background: hsl(74 87% 40%);
	transform: scaleY(0);
	transition: transform 0.2s ease;
}
.esteri-megamenu__child:hover .esteri-megamenu__child-rail,
.esteri-megamenu__child.is-current .esteri-megamenu__child-rail { transform: scaleY(1); }

.esteri-megamenu__child-thumb-wrap {
	flex-shrink: 0;
	display: none;
	width: 56px;
	height: 42px;
	border-radius: var(--radius, 4px);
	overflow: hidden;
	background: hsl(220 30% 95%);
}
@media (min-width: 1280px) { .esteri-megamenu__child-thumb-wrap { display: block; } }
.esteri-megamenu__child-thumb {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.esteri-megamenu__child-body { flex: 1; min-width: 0; }
.esteri-megamenu__child-title {
	display: block;
	margin-bottom: 4px;         /* source mb-1 */
	font-size: 15px;
	/* `text-sm` in THIS design is a size+line-height PAIR — tailwind.config.ts:23
	 * `sm: ['0.9375rem', { lineHeight: '1.5rem' }]` = 15px/24px. Setting only the
	 * font-size let the body's 1.55 win (23.25px), so every row sat 0.75px short.
	 * Whenever you port a `text-sm` element, port its 24px line-height too. */
	line-height: 24px;
	font-weight: 600;           /* source font-semibold */
	color: hsl(222 30% 12%);
	transition: color 0.15s;
}
.esteri-megamenu__child:hover .esteri-megamenu__child-title,
.esteri-megamenu__child.is-current .esteri-megamenu__child-title { color: hsl(222 68% 33%); }
.esteri-megamenu__child-desc {
	display: block;
	font-size: 15px;
	/* source `leading-snug` = 1.375 (→ 20.625px at 15px). The 1.4 here was an
	 * approximation that rendered 21px. */
	line-height: 1.375;
	color: hsl(222 10% 42%);
	/* A menu row is one line by design (every curated child_desc is 23–40
	 * chars → 21px, row 73px). The row text now has a last-resort fallback to
	 * Kortin kuvaus, which runs 62–138 chars and pushed the row to 93px
	 * (Saku, 2026-08-07: "the menu desc cant be that long then so it doesnt
	 * work now"). Bound it at two lines so an unfilled short field can never
	 * distort the panel; ellipsis instead of a mid-word cut. Zero effect on
	 * every existing row — they are all one line. */
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
}

/* Foot row */
.esteri-megamenu__foot {
	margin-top: 24px;
	padding-top: 16px;
	border-top: 1px solid hsl(220 14% 88%);
	display: flex;
	align-items: center;
	justify-content: space-between;
}
.esteri-megamenu__foot-text { font-size: 15px; line-height: 24px; color: hsl(222 10% 42%); }  /* source SiteHeader.tsx:475 `text-sm` pair */
.esteri-megamenu__foot-cta {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: 15px;
	font-weight: 600;
	color: hsl(222 68% 33%);
	text-decoration: none;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
	line-height: 24px;  /* source SiteHeader.tsx:479 `text-sm font-semibold` pair */
}
.esteri-megamenu__foot-cta:hover { color: hsl(222 68% 45%); }
.esteri-megamenu__foot-cta svg { transition: transform 0.2s; }
.esteri-megamenu__foot-cta:hover svg { transform: translate(2px, -2px); }

/* ─── Right cluster (desktop search + CTA + mobile burger) ───────── */

.esteri-header__right {
	display: flex;
	align-items: center;
	/* This ONE element plays both of the source's clusters: below lg it is the
	 * mobile `flex lg:hidden items-center gap-1` (4px), at lg+ it is the desktop
	 * `hidden lg:flex … gap-2` (8px). The 8px base was the desktop value leaking
	 * down, making the mobile cluster 8px wide-of-source. */
	gap: 4px;
	flex-shrink: 0;
}
/* source: gap-2 min-[1280px]:gap-3 min-[1450px]:gap-4 — the 1280 step was
 * carrying the 1450 value (16px), stealing 4px from the tight range's budget. */
@media (min-width: 1024px) { .esteri-header__right { gap: 8px; } }
@media (min-width: 1280px) { .esteri-header__right { gap: 12px; } }
@media (min-width: 1450px) { .esteri-header__right { gap: 16px; } }

.esteri-header__search {
	/* Mobile/tablet (<lg) search trigger — toggles the slide-down panel. The
	 * desktop (lg+) magnifier is the separate `.esteri-header__dsearch` expanding
	 * field below, so hide this one at lg+ (source splits the two: `flex lg:hidden`
	 * mobile button vs `hidden lg:flex` desktop expand). */
	display: inline-flex;
	/* source SiteHeader.tsx:561-565 `p-2` + `<Search size={20}/>` = 20+16 = 36,
	 * NOT the burger's 40 (`p-2` + `<Menu size={24}/>`). Two buttons, two icon
	 * sizes — copying the burger's box made the mobile cluster 4px too wide. */
	width: 36px;
	height: 36px;
	align-items: center;
	justify-content: center;
	background: transparent;
	border: 0;
	color: hsl(222 30% 12%);
	cursor: pointer;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
	border-radius: var(--radius, 4px);
}
.esteri-header__search:hover { color: hsl(222 68% 33%); }
@media (min-width: 1024px) { .esteri-header__search { display: none; } }

/* ─── Desktop expanding search (lg+) ─────────────────────────────── */
/* Port of SiteHeader.tsx searchOpen: a w-10 magnifier that expands to w-64
 * (w-72 ≥1280) with a fade-in input + green accent line; submits to WP search. */
.esteri-header__dsearch {
	display: none;
	position: relative;
	align-items: center;
	height: 40px;
	width: 40px;
	flex-shrink: 0;
	border: 1px solid transparent;
	border-radius: var(--radius, 4px);
	background: transparent;
	overflow: hidden;
	transition: width 0.3s ease, margin-left 0.3s ease, background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}
@media (min-width: 1024px) { .esteri-header__dsearch { display: flex; } }
/* The open field grows LEFTWARD OVER the bar instead of pushing it: the flex
 * item keeps its closed 40px of layout width (`margin-left: 40px - <open
 * width>`) and the extra 216/248px is painted outside its box, so the row's
 * used width never changes and nothing reflows.
 *
 * WHY this deviates from the source (which just lets the field take the space):
 * the source nav is `whitespace-nowrap` with no wrapping and no `min-width:0`,
 * so it CANNOT reflow — the row would simply overflow. Ours wraps on purpose
 * (`.esteri-megamenu__top { flex-wrap: wrap }`, see the note there — Saku,
 * 2026-08-07: "if i add more pages to main menu it doesnt wrap it brokes"),
 * which turned the source's harmless squeeze into a menu that jumps to two rows
 * the moment the magnifier is clicked (measured 2026-09-22 at 1280 and 1600:
 * nav rows 1 → 2, last item x 823 → 371). Pinning the right edge keeps BOTH
 * behaviours: the author may add menu items, and opening search never moves them.
 *
 * Where the source has free space between the nav and the CTA — 1440 and wider
 * with the current 6 items — the painted result is pixel-identical to the
 * source, because the field's right edge is fixed either way and it grows into
 * that same gap. Below that width it overlays the nav's tail rather than
 * shoving it; the field has an opaque background, border and shadow, and closes
 * on outside-click/Escape, so nothing underneath is left unreachable. */
.esteri-header__dsearch.is-open {
	width: 16rem;
	margin-left: calc(40px - 16rem);
	/* Above the nav row it now paints over (both are position:relative inside
	 * the same bar, so without this the later-in-DOM nav items would win only by
	 * source order — the panel is what must be on top). */
	z-index: 3;
	background: hsl(0 0% 100%);
	border-color: hsl(220 14% 88%);
	box-shadow: var(--shadow-sm, 0 1px 2px hsl(222 30% 12% / 0.06));
	/* Let the fade below paint outside the box. `overflow: hidden` stays on the
	 * CLOSED state, which is the one that needs it (it clips the input while the
	 * width animates shut). */
	overflow: visible;
}
@media (min-width: 1280px) {
	.esteri-header__dsearch.is-open { width: 18rem; margin-left: calc(40px - 18rem); }
}
/* A short white fade off the field's left edge, so a nav label the overlay
 * happens to cross dissolves under it instead of being sliced mid-glyph by the
 * border (at 1280 that is "Tietoa meist|ä"). Fades IN with the field. */
.esteri-header__dsearch::before {
	content: '';
	position: absolute;
	right: 100%;
	top: -1px;
	bottom: -1px;
	width: 56px;
	background: linear-gradient(to right, hsl(0 0% 100% / 0), hsl(0 0% 100%));
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.3s ease;
}
.esteri-header__dsearch.is-open::before { opacity: 1; }
.esteri-header__dsearch-btn {
	flex-shrink: 0;
	width: 40px;
	height: 40px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: transparent;
	border: 0;
	color: hsl(222 30% 12%);
	cursor: pointer;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
.esteri-header__dsearch-btn:hover { color: hsl(222 10% 42%); }
.esteri-header__dsearch.is-open .esteri-header__dsearch-btn { color: hsl(222 68% 33%); }
.esteri-header__dsearch-input {
	flex: 1;
	min-width: 0;
	height: 100%;
	background: transparent;
	border: 0;
	outline: none;
	font-size: 15px;
	color: hsl(222 30% 12%);
	padding-right: 8px;
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.2s ease 0.1s;
}
.esteri-header__dsearch-input::placeholder { color: hsl(222 10% 42%); }
.esteri-header__dsearch.is-open .esteri-header__dsearch-input {
	opacity: 1;
	pointer-events: auto;
}
.esteri-header__dsearch-accent {
	position: absolute;
	left: 0; right: 0; bottom: 0;
	height: 2px;
	background: hsl(74 87% 40%);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform 0.3s ease;
	pointer-events: none;
}
.esteri-header__dsearch.is-open .esteri-header__dsearch-accent { transform: scaleX(1); }

/* The source has TWO "Ota yhteyttä" spans — the `<lg` one in the mobile-actions
 * cluster (gap-1.5 px-3 py-2 text-sm, arrow 14) and the `≥lg` one in the desktop
 * cluster (gap-1 px-2 py-1.5 → 1100:gap-1.5 px-2.5 py-2 → 1280:px-3 → 1450:gap-2
 * px-4 text-base; arrow 12 → 1100:14 → 1450:16). WP renders ONE element for both,
 * so every range gets its own step below. Base = the source's `<lg` span. */
.esteri-header__cta {
	display: none;
	align-items: center;
	gap: 6px;
	padding: 8px 12px;
	background: hsl(74 87% 40%);
	color: hsl(222 30% 12%);
	font-size: 15px;   /* source: text-sm (design sm override = 15px), was 14px */
	line-height: 24px; /* text-sm/text-base pair (was body 1.55 -> 23.25/24.8px) */
	font-weight: 600;
	text-decoration: none;
	white-space: nowrap;
	border-radius: var(--radius, 4px);
	transition: background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
@media (min-width: 640px)  { .esteri-header__cta { display: inline-flex; } }
/* Desktop cluster takes over at lg — its tightest step is the smallest of all. */
@media (min-width: 1024px) { .esteri-header__cta { gap: 4px; padding: 6px 8px; } }
@media (min-width: 1100px) { .esteri-header__cta { gap: 6px; padding: 8px 10px; } }
@media (min-width: 1280px) { .esteri-header__cta { padding: 8px 12px; } }
@media (min-width: 1450px) { .esteri-header__cta { gap: 8px; padding: 8px 16px; font-size: 16px; } } /* source: min-[1450px]:text-base */
/* Arrow: ArrowRight size={12} min-[1100px]:!size-[14px] min-[1450px]:!size-4.
 * The markup ships width/height="14" (the <lg + 1100–1449 value), so only the
 * two outer steps need overriding. */
@media (min-width: 1024px) and (max-width: 1099px) { .esteri-header__cta svg { width: 12px; height: 12px; } }
@media (min-width: 1450px) { .esteri-header__cta svg { width: 16px; height: 16px; } }
.esteri-header__cta:hover { background: hsl(222 68% 33%); color: hsl(0 0% 100%); }
.esteri-header__cta svg { transition: transform 0.2s; }
.esteri-header__cta:hover svg { transform: translateX(4px); }   /* source SiteHeader.tsx:552 group-hover:translate-x-1 */

/* Burger */
.esteri-header__burger {
	display: inline-flex;
	width: 40px;
	height: 40px;
	margin-right: -8px;
	align-items: center;
	justify-content: center;
	background: transparent;
	border: 0;
	color: hsl(222 30% 12%);
	cursor: pointer;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
@media (min-width: 1024px) { .esteri-header__burger { display: none; } }
.esteri-header__burger:hover { color: hsl(222 68% 33%); }
.esteri-header__burger-x { display: none; }
.esteri-header-root.is-drawer-open .esteri-header__burger-icon { display: none; }
.esteri-header-root.is-drawer-open .esteri-header__burger-x { display: block; }
.esteri-header-root.is-drawer-open .esteri-header__burger { color: hsl(0 0% 100%); }
/* Drawer open → header is transparent over the dark gradient. Flip the logo +
 * search icon to white so they read against it (source swaps to a white masked
 * logo + `text-primary-foreground` search icon when mobileOpen). brightness(0)
 * blacks every opaque pixel, invert(1) makes it white — works on the colour PNG.
 *
 * TWO logo selectors, because the header carries two shapes. `.…__logo-img` is
 * the <img> the site-header MONOLITH renders; the live header part has used a
 * core/image since the logo became swappable, which paints
 * `figure.esteri-header__logo > a > img.wp-image-NN` and matched NEITHER rule.
 * So the open drawer showed the full-colour PNG on the dark gradient while the
 * source showed a white silhouette (Saku, 2026-08-11: "atleast the logo is not
 * same in mobile nav" — paired shots in design-screenshots/drawer confirmed it).
 * The class-scope drift is the recurring mask from the parity root-causes list:
 * a block swap moved the element and left the state rule behind. */
.esteri-header-root.is-drawer-open .esteri-header__logo-img,
.esteri-header-root.is-drawer-open figure.esteri-header__logo img { filter: brightness(0) invert(1); }
.esteri-header-root.is-drawer-open .esteri-header__logo-text { color: hsl(0 0% 100%); }
.esteri-header-root.is-drawer-open .esteri-header__search { color: hsl(0 0% 100%); }

/* ─── Mobile search panel (slide-down beneath the bar) ───────────── */
/* Port of SiteHeader.tsx's mobile search panel. Hidden ≥1024 so the desktop
 * search button (which this same panel is bound to) stays inert there. */
.esteri-mobile-search {
	position: relative;
	z-index: 50;
	background: hsl(0 0% 100%);
	border-bottom: 1px solid hsl(220 14% 88%);
}
.esteri-mobile-search[hidden] { display: none; }
@media (min-width: 1024px) { .esteri-mobile-search { display: none; } }

.esteri-mobile-search__inner { padding-block: 12px; }

.esteri-mobile-search__field {
	position: relative;
	display: flex;
	align-items: center;
	height: 44px;
	background: hsl(0 0% 100%);
	border: 1px solid hsl(220 14% 88%);
	border-radius: var(--radius, 4px);
	box-shadow: var(--shadow-sm, 0 1px 2px hsl(222 30% 12% / 0.06));
}
.esteri-mobile-search__icon {
	flex-shrink: 0;
	width: 44px;
	height: 44px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: hsl(222 68% 33%);
}
.esteri-mobile-search__input {
	flex: 1;
	min-width: 0;
	height: 100%;
	background: transparent;
	border: 0;
	outline: none;
	font-size: 15px;
	color: hsl(222 30% 12%);
	padding-right: 8px;
}
.esteri-mobile-search__input::placeholder { color: hsl(222 10% 42%); }
.esteri-mobile-search__close {
	flex-shrink: 0;
	width: 40px;
	height: 40px;
	margin-right: 4px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: transparent;
	border: 0;
	color: hsl(222 10% 42%);
	cursor: pointer;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
.esteri-mobile-search__close:hover { color: hsl(222 30% 12%); }
.esteri-mobile-search__accent {
	position: absolute;
	left: 0; right: 0; bottom: 0;
	height: 2px;
	background: hsl(74 87% 40%);
	pointer-events: none;
}

/* ─── Mobile drawer ──────────────────────────────────────────────── */

.esteri-drawer {
	position: fixed;
	left: 0;
	right: 0;
	/* Cover the full screen (top:0), not just below the header. The header bar
	 * (z-110) sits ON TOP and goes transparent when open, so the drawer's dark
	 * gradient shows behind it — that dark backdrop is what makes the white
	 * burger-X and white CTA (set below) legible. With top:header-h the header
	 * sat over the white page instead and the white burger vanished. The
	 * padding-top reserves the header's height so drawer content clears it.
	 * Mirrors Lovable's `fixed inset-0 top-0 pt-[64px]`. */
	top: 0;
	padding-top: var(--esteri-header-h-sm);
	bottom: 0;
	/* Cap to the viewport + clip overflow so the fixed drawer can never exceed
	 * the screen height. The __scroll child (overflow-y:auto) handles internal
	 * scroll when the menu is long. Without the cap, a fullpage screenshot's
	 * captureBeyondViewport expands the fixed element's containing block to the
	 * whole document, stretching the drawer to the full scroll height and
	 * inflating the captured page height (caught on the ArticlesPage 390 gate:
	 * real content 8512px, captured 11568px). dvh tracks the mobile dynamic
	 * viewport so the open drawer stays correct with browser chrome too. */
	max-height: 100dvh;
	overflow: hidden;
	/* Closed drawer is off-screen (opacity:0) — skip rendering its (tall,
	 * image-heavy) mega-menu contents entirely. `contain: size` (implied by
	 * content-visibility:hidden) stops that inner content from leaking into
	 * documentElement.scrollHeight, which a fullpage screenshot's
	 * captureBeyondViewport otherwise inflates by the full menu height. Also a
	 * perf win. Re-enabled to `visible` on open below. */
	content-visibility: hidden;
	z-index: 40;
	display: flex;
	flex-direction: column;
	background: linear-gradient(160deg, hsl(222 50% 8%) 0%, hsl(222 68% 20%) 55%, hsl(222 68% 33%) 100%);
	color: hsl(0 0% 100%);
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.25s ease;
}
.esteri-drawer[aria-hidden="false"] {
	pointer-events: auto;
	opacity: 1;
	content-visibility: visible;
}
@media (min-width: 1024px) { .esteri-drawer { display: none; } }

.esteri-header-root.is-drawer-open .esteri-header,
.esteri-header-root.is-drawer-open .esteri-header__bar {
	background: transparent;
	border-bottom-color: transparent;
}
.esteri-header-root.is-drawer-open .esteri-utility { display: none; }
.esteri-header-root.is-drawer-open .esteri-header__cta {
	background: hsl(0 0% 100%);
	color: hsl(222 30% 12%);
}

.esteri-drawer__scroll {
	flex: 1;
	overflow-y: auto;
	padding: 24px;
}

.esteri-drawer__home,
.esteri-drawer__top-link {
	display: block;
	padding: 16px 0;
	/* Tailwind size tokens are size+LINE-HEIGHT PAIRS; setting only the size lets
	 * the body's 1.55 win. text-lg = 18px/28px, text-base = 16px/24px,
	 * text-sm (overridden in tailwind.config.ts) = 15px/24px. */
	font-size: 18px;
	line-height: 28px;          /* source text-lg pair (was body 1.55 -> 27.9px) */
	font-weight: 600;
	color: hsl(0 0% 100%);
	text-decoration: none;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
.esteri-drawer__home {
	border-bottom: 1px solid hsl(0 0% 100% / 0.1);
}
.esteri-drawer__home:hover,
.esteri-drawer__top-link:hover,
.esteri-drawer__home.is-current,
.esteri-drawer__top-link.is-current { color: hsl(74 87% 40%); }   /* current page — render.php */
.esteri-drawer__top-link.is-static { cursor: default; }

.esteri-drawer__list { list-style: none; margin: 0; padding: 0; }
.esteri-drawer__row {
	border-bottom: 1px solid hsl(0 0% 100% / 0.1);
}
.esteri-drawer__row:last-child { border-bottom: 0; }
.esteri-drawer__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
}
.esteri-drawer__head .esteri-drawer__top-link { flex: 1; }

.esteri-drawer__expand {
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 999px;
	background: hsl(0 0% 100% / 0.1);
	border: 1px solid hsl(0 0% 100% / 0.25);
	color: hsl(0 0% 100%);
	cursor: pointer;
	transition: background-color 0.2s, transform 0.2s;
}
.esteri-drawer__expand:hover { background: hsl(0 0% 100% / 0.2); }
.esteri-drawer__expand[aria-expanded="true"] {
	background: hsl(74 87% 40%);
	border-color: hsl(74 87% 40%);
	color: hsl(222 30% 12%);
}
.esteri-drawer__expand[aria-expanded="true"] svg { transform: rotate(180deg); }
.esteri-drawer__expand svg { transition: transform 0.2s; }

.esteri-drawer__children {
	list-style: none;
	margin: 0;
	padding: 0 0 16px;
}
.esteri-drawer__children[hidden] { display: none; }
/* The source's expanded panel is `pb-4 space-y-1` (SiteHeader.tsx) — that
 * `space-y-1` is a 4px margin-top on every row after the first, measured live on
 * the dev server: row 0 `mt:0px`, rows 1…n `mt:4px`. This port carried the `pb-4`
 * (the padding above) but not the owl, so every child row sat 4px tighter than the
 * design and the drift accumulated down the list — 8 rows deep the WP list ran
 * ~32px short (Saku, 2026-08-11: "the mobile navigation doesnt look same as in
 * lovable mobile menu"). The rows are `<li>` wrappers here, not bare <a>, so the
 * owl goes on the list item. */
.esteri-drawer__children > li + li { margin-top: 4px; }
.esteri-drawer__children a {
	display: block;
	padding: 12px 16px;         /* source child row: `px-4 py-3` — verified correct */
	border-radius: var(--radius, 4px);
	text-decoration: none;
	transition: background-color 0.15s;
}
.esteri-drawer__children a:hover,
.esteri-drawer__children a.is-current { background: hsl(0 0% 100% / 0.05); }
.esteri-drawer__children a.is-current :is(.esteri-drawer__child-title, .esteri-drawer__all-title) { color: hsl(74 87% 40%); }
.esteri-drawer__child-title {
	display: block;
	/* No margin under the title, and the description below is INLINE — both
	 * measured off the source, where the row is `<span class="block …">` +
	 * `<span class="text-sm …">` (SiteHeader.tsx: the second span has no `block`).
	 * The block title makes its own 24px line, the inline description then makes a
	 * 25px LINE BOX from the anchor's own strut. This port had display:block plus a
	 * 2px margin, which is 24+2+24 = 50 against the design's 49 — every child row
	 * ran 1px tall, and with the missing space-y-1 owl above the two errors pulled
	 * the list in opposite directions. */
	font-size: 15px;
	line-height: 24px;          /* source text-sm pair (was body 1.55 -> 23.25px) */
	font-weight: 500;
	color: hsl(0 0% 100%);
}
.esteri-drawer__child-desc {
	display: inline;   /* source: no `block` on this span — see __child-title above */
	font-size: 15px;
	line-height: 24px;          /* source text-sm pair */
	color: hsl(0 0% 100% / 0.6);
}

/* "Kaikki <label> →" lead link prepended to each expanded drawer section. The
 * <a> itself inherits block/padding/hover from `.esteri-drawer__children a`, and
 * the subtitle reuses __child-desc. The source title is green (text-brand-green),
 * but green now marks the CURRENT page in this menu, so a lead that was green on
 * every page read as "you are here" (Saku 2026-11-05: "kaikki ratkaisut is now
 * green all the time") — white like the other rows, green only on its own page
 * (`.is-current`, render.php). */
.esteri-drawer__all-title {
	display: block;
	font-size: 15px;
	line-height: 24px;          /* source text-sm pair (was body 1.55 -> 23.25px) */
	font-weight: 500;
	color: hsl(0 0% 100%);
}
/* The arrow at 1.4× the label (Saku 2026-11-05: "the arrow needs to be bit
 * bigger"); line-height 0 keeps the 24px line box. */
.esteri-drawer__all-arrow {
	font-size: 1.4em;
	line-height: 0;
	vertical-align: -0.05em;
}

.esteri-drawer__cta {
	margin-top: 24px;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	/* source SiteHeader.tsx CTA: `w-full … py-3` — VERTICAL padding only. The stray
	 * 16px inline padding just shrank the label box on a full-width centred button. */
	padding: 12px 0;
	background: hsl(74 87% 40%);
	color: hsl(222 30% 12%);
	font-size: 16px;
	line-height: 24px;          /* source text-base pair (was body 1.55 -> 24.8px) */
	font-weight: 600;
	text-decoration: none;
	border-radius: var(--radius, 4px);
}

.esteri-drawer__lang {
	margin-top: 32px;
	padding-top: 24px;
	border-top: 1px solid hsl(0 0% 100% / 0.1);
	display: flex;
	align-items: center;
	gap: 8px;
	color: hsl(0 0% 100% / 0.6);
}
.esteri-drawer__lang-btn {
	display: inline-flex;
	padding: 6px 12px;
	font-family: 'Space Mono', monospace;
	font-size: 15px;
	letter-spacing: 0.08em;
	color: hsl(0 0% 100% / 0.6);
	text-decoration: none;
	transition: background-color 0.2s, color 0.2s;
}
.esteri-drawer__lang-btn:hover { color: hsl(0 0% 100%); }
.esteri-drawer__lang-btn.is-current {
	background: hsl(0 0% 100%);
	color: hsl(222 30% 12%);
}

.esteri-drawer__utility {
	margin-top: 24px;
	display: flex;
	flex-wrap: wrap;
	gap: 16px;
}
.esteri-drawer__utility a {
	font-family: 'Space Mono', monospace;
	font-size: 15px;
	color: hsl(0 0% 100% / 0.8);
	text-decoration: none;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
.esteri-drawer__utility a:hover { color: hsl(0 0% 100%); }
.esteri-drawer__utility a.is-current { color: hsl(74 87% 40%); }

/* Social icon row at the drawer bottom (source: same channels as the utility
 * bar, white/60 → white on hover). */
.esteri-drawer__socials {
	margin-top: 24px;
	display: flex;
	align-items: center;
	gap: 12px;
}
.esteri-drawer__social-link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: hsl(0 0% 100% / 0.6);
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
	text-decoration: none;
}
.esteri-drawer__social-link:hover { color: hsl(0 0% 100%); }

/* Body scroll-lock when drawer is open (added by JS). */
body.esteri-drawer-open {
	overflow: hidden;
}

/* Reduced-motion override. */
@media (prefers-reduced-motion: reduce) {
	.esteri-megamenu__panel,
	.esteri-megamenu__top-chev,
	.esteri-megamenu__top-label::after,
	.esteri-megamenu__feature-img,
	.esteri-megamenu__feature-sweep,
	.esteri-megamenu__child-rail,
	.esteri-drawer,
	.esteri-drawer__expand,
	.esteri-drawer__expand svg,
	.esteri-utility__links a::after,
	.esteri-utility__lang-trigger::after,
	.esteri-utility__social-link,
	.esteri-mobile-search__close,
	.esteri-drawer__social-link,
	.esteri-header__dsearch,
	.esteri-header__dsearch-btn,
	.esteri-header__dsearch-input,
	.esteri-header__dsearch-accent {
		transition: none;
	}
}

/* An author-picked social icon (esteriImage on the Ylävalikko nav item) instead
 * of a bundled brand glyph — clamp it to the glyph's box so an arbitrary
 * media-library image can never stretch the row. `contain` keeps a non-square
 * logo whole rather than cropping it. */
.esteri-social-icon-img {
	display: block;
	width: 16px;
	height: 16px;
	object-fit: contain;
}
/* Same fixed-size pin as site-footer.css — see the note there for why the box
 * being 16×16 is not the same as the artwork filling it. */
.esteri-utility__social-link > svg,
.esteri-utility__social-link > .esteri-social-icon-img {
	flex-shrink: 0;
}

/* Same optical correction as site-footer.css — WordPress's own marks are drawn
 * on a 20/24 grid, this design's are edge-to-edge. See the note there. */
.esteri-social-icon--wp {
	transform: scale(1.2);
	transform-origin: center;
}
/* The silhouette treatment — same rule as site-footer.css, and the reason it is
 * expressed as `currentColor` rather than a fixed colour: the footer wants the
 * image white on navy, this bar wants it dark on white, and one declaration
 * serves both. See esteri_social_icon_markup() for which uploads qualify. */
.esteri-social-icon-img--mask {
	background-color: currentColor;
	-webkit-mask-image: var(--esteri-social-icon);
	mask-image: var(--esteri-social-icon);
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	-webkit-mask-position: center;
	mask-position: center;
}

/* ─── Editor canvas: make the chrome's SELECTION visible ─────────────────
 *
 * Saku, 2026-08-10: "when you click edit site and click example header you dont
 * see if its selected or not". It WAS selected — the sidebar, the block toolbar
 * and the breadcrumb all said Header — but the ring was invisible. WordPress
 * draws it as an `::after` on the block element at `z-index: 1`, and this file
 * puts `z-index: 110` on the sticky chrome; a descendant at 110 paints straight
 * over a pseudo-element at 1, so the ring sat behind the opaque white bar. The
 * footer needs it too (its sheen is at z-index 10).
 *
 * It lives HERE rather than in the editor-settings stylesheet because
 * `block_editor_settings_all` styles do not reach the canvas in the site
 * editor's TEMPLATE view — measured 0 of our rules there, while the same styles
 * do arrive in a template-PART editor. A block stylesheet loads in both.
 * `.editor-styles-wrapper` is on the canvas body, so this is editor-only.
 *
 * `!important` is load-bearing, and the reason is the whole bug. Core sets the
 * ring's z-index in SIX different state rules; the one that bites is
 *
 *   .block-editor-block-list__layout .block-editor-block-list__block
 *     :not([contenteditable=true]):focus::after { … z-index: 1 }
 *
 * at (0,4,1) — an attribute `:not()` and a `:focus` both count in the class
 * column — which out-ranks any plain three-class selector, and core's sheets are
 * injected after this file so a tie loses too. That rule only matches when the
 * block has real DOM FOCUS, which is exactly the difference between a probe and
 * a customer: `dispatch( 'core/block-editor' ).selectBlock()` selects WITHOUT
 * focusing, so the ring measured z-index 200 and screenshotted correctly, while
 * one trusted press measured z-index 1 and painted nothing. Verify this rule
 * with tools/editor-realpress.mjs (or a CDP Input press), never with a dispatch
 * — a synthetic selection cannot see this bug at all.
 *
 * Racing the specificity would mean out-running whichever of those six rules
 * core edits next, for a pseudo-element no other author intent competes for. */
.editor-styles-wrapper .block-editor-block-list__block.wp-block-template-part::after {
	z-index: 200 !important;
}
