/**
 * Site footer — brand block + 4-column nav + trust-badge row + bottom bar.
 *
 * Ports SiteFooter.tsx (source/src/components/SiteFooter.tsx) to a
 * server-rendered dynamic block. Colour tokens follow site-header.css's
 * hard-coded-HSL convention; refactor to var(--token) when the
 * pixel-parity audit pass lands (see HANDOFF.md).
 */

.esteri-footer {
	position: relative;
	overflow: hidden;
	color: hsl(0 0% 100%);
	background: linear-gradient(160deg, hsl(222, 50%, 8%) 0%, hsl(222, 68%, 20%) 50%, hsl(222, 68%, 33%) 100%);
}

.esteri-footer__sheen {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 1px;
	background: hsl(0 0% 100% / 0.10);
	z-index: 10;
	pointer-events: none;
}

/* Same hairline, as a pseudo-element, for the BLOCK-COMPOSED footer: a core
 * group cannot emit a decorative empty <div>, and an author could delete one
 * anyway. Scoped to the --blocks modifier rather than `:not(:has(…))` so the
 * monolith and this version can never paint it twice (two 1px lines at 0.10
 * alpha composite to a visibly brighter 0.19), and so no browser without
 * `:has()` silently loses the line altogether. */
.esteri-footer--blocks::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 1px;
	background: hsl(0 0% 100% / 0.10);
	z-index: 10;
	pointer-events: none;
}

.esteri-footer__inner {
	padding-block: 4rem;
}

/* ─── Top row: brand block + 4-col nav ─────────────────────────────── */

.esteri-footer__top {
	display: grid;
	grid-template-columns: 1fr;
	gap: 3rem;
}
/* Two classes on purpose, exactly like `.esteri-footer .esteri-footer__brand >
 * * + *` below. Once the footer became a core/group composition this box is a
 * flow-layout CHILD, and core's own layout CSS zeroes child margins; a bare
 * `.esteri-footer__top` (0,1,0) lost and the band under the top row collapsed
 * — measured 56px missing at 1920/768/390, i.e. the whole footer 56px short. */
.esteri-footer .esteri-footer__top {
	margin-bottom: 3.5rem;
}
@media (min-width: 1024px) {
	.esteri-footer__top {
		grid-template-columns: 320px 1fr;
		gap: 4rem;
	}
}

/* Brand column */

/* Source is `<div className="space-y-6">` — a BLOCK box with owl margins, whose
 * first child is an `inline-flex` logo wrapper. Ported as flex+gap, the logo
 * became a flex item (flex items are blockified), which dropped the inline line
 * box the design's logo sits in — and with it 6.8px of descender slack under
 * the 60px logo. Result: the whole footer rendered 6.8px shorter than the
 * design on EVERY page, and each block below the logo sat 6.8px high.
 * Reproduce the mechanism instead of padding a magic number: block box + owl
 * margin (= space-y-6), logo stays inline-level so the strut comes back and
 * scales with the footer's font/line-height.
 * refs: header-footer-and-menus.md § footer; pixel-parity-audit.md
 *       § "What to measure" (found by tools/spacing-parity.mjs, 2 routes). */
.esteri-footer__brand {
	display: block;
}
/* Two classes on purpose: `.esteri-footer__brand > * + *` is only (0,1,0)
 * (universal + combinators add nothing), which LOSES to the tagline's own
 * `p.esteri-footer__tagline { margin: 0 }` (0,1,1) — the owl then skipped
 * exactly one child and the footer came out 24px short. */
.esteri-footer .esteri-footer__brand > * + * {
	margin-top: 1.5rem;   /* space-y-6 */
}

/* ─── Editor canvas: the same owl, one level down ────────────────────────────
 *
 * Saku, 2026-08-10: "footer editor doesnt have same spaces as live page".
 *
 * The owl above is a CHILD-combinator rule, so it depends on the brand column's
 * children being the five things the design puts there. Live they are:
 *
 *   .esteri-footer__brand > figure.esteri-footer__logo   (core/image)
 *                         > p.esteri-footer__tagline     ┐
 *                         > a.esteri-footer__more        │ emitted by
 *                         > address.esteri-footer__address  esteri/footer-brand,
 *                         > a.esteri-footer__cta         │ deliberately WITHOUT
 *                         > div.esteri-footer__social    ┘ a wrapper of its own
 *
 * — five owl gaps. In the canvas that block is an ordinary block, so THREE
 * wrappers sit between the group and that markup (measured, don't count them
 * from the source): `useBlockProps()`'s div, chromeTextEdit's preview host, and
 * the `<div>` `RawHTML` always emits inside ServerSideRender. The owl lands on
 * the outermost of them: ONE gap, not five. Measured 1440, both editor surfaces:
 * the footer stood 943px against 1038px live — 4 × 24px missing — while every
 * other footer box matched.
 *
 * So restate the owl for the canvas's DOM. Address the five items BY NAME rather
 * than by depth: a child-combinator chain has to be re-counted every time core
 * or chromeTextEdit adds a wrapper (the first cut of this fix guessed two levels
 * and silently moved the 24px onto the RawHTML div, which measures exactly like
 * no fix at all). Every one of the five is preceded by the logo or by another of
 * them, so all five take the margin, exactly as live. `.editor-styles-wrapper`
 * is on the canvas body, so none of this matches on the front end.
 *
 * (0,4,0) beats `p.esteri-footer__tagline { margin: 0 }` (0,1,1) — the same tie
 * the note above records. An author's own block dropped into the brand column is
 * a REAL child of the group in the canvas, so the front-end owl still gives it
 * the rhythm; only this block's server-rendered innards need the restatement.
 * refs: gotchas.md § "A front-end rule scoped to a TEMPLATE wrapper has no
 *       counterpart in the canvas — mirror it or your fix makes parity worse". */
/* ⚠️ Scoped to `.wp-block-esteri-footer-brand` — the MONOLITH — and nothing else.
 *
 * The restatement below is only needed while five elements share ONE block
 * wrapper, because then the front-end owl finds one child and pays out one gap
 * instead of five. Since the column was split into footer-tagline / -more /
 * -address / -cta, each piece is its own child of the group, so
 * `.esteri-footer .esteri-footer__brand > * + *` — which is NOT editor-scoped and
 * therefore applies in the canvas too — already gives every one of them the
 * 24px, on both surfaces, measured.
 *
 * Leaving the by-name list unscoped would be actively harmful now: it would also
 * hit the split blocks' inner elements, stacking a second 24px inside a wrapper
 * that already has one. Margin collapsing happens to hide that today, which is
 * exactly the kind of accident that turns into a 24px drift the moment a
 * `display` or `overflow` somewhere stops the collapse.
 *
 * The real win is what the author sees: a block DROPPED into the brand column is
 * an ordinary child of the group, so one selector now feeds it the same rhythm in
 * the canvas as on the front end. Under the by-name list it got 24px live and 0
 * in the editor — Saku, 2026-08-11: "the gap is not same as in live page". */
.editor-styles-wrapper .esteri-footer .esteri-footer__brand > .wp-block-esteri-footer-brand {
	margin-top: 0;
}
.editor-styles-wrapper .wp-block-esteri-footer-brand .esteri-footer__tagline,
.editor-styles-wrapper .wp-block-esteri-footer-brand .esteri-footer__more,
.editor-styles-wrapper .wp-block-esteri-footer-brand .esteri-footer__address,
.editor-styles-wrapper .wp-block-esteri-footer-brand .esteri-footer__cta,
.editor-styles-wrapper .wp-block-esteri-footer-brand .esteri-footer__social {
	margin-top: 1.5rem;
}

/* The block appender — the "+" that adds a social icon, or a block to the brand
 * column — is painted by core for a LIGHT canvas: measured rgb(30,30,30) on both
 * the button and its <svg>, which is 1.05:1 against this footer's dark gradient.
 * Invisible (Saku, 2026-08-11: "the social media plus button is very dark and you
 * cant really see it" — the social icons beside it measure 17.57:1).
 *
 * Repainted for a dark surface, and scoped to `.esteri-footer` so it covers every
 * appender the footer can show — the social row, the brand column, the address
 * box — rather than only the one that got reported. `.editor-styles-wrapper`
 * keeps all of it off the front end, which has no appenders at all. */
.editor-styles-wrapper .esteri-footer .block-editor-button-block-appender {
	color: hsl(0 0% 100% / 0.85);
	background: hsl(0 0% 100% / 0.10);
	box-shadow: inset 0 0 0 1px hsl(0 0% 100% / 0.20);
	border-radius: var(--radius, 4px);
}
/* `fill: currentColor` rather than a literal: core sets fill on the glyph
 * directly, so inheriting the colour above is what actually moves it. */
.editor-styles-wrapper .esteri-footer .block-editor-button-block-appender svg {
	fill: currentColor;
}
/* In the icon row the "+" sits among 44px circles, so it reads as one of them. */
.editor-styles-wrapper .esteri-footer__social-list .block-editor-button-block-appender {
	border-radius: 50%;
}

/* The CTA's core/buttons wrapper paints its own "Lisää painike" appender next
 * to the button whenever it is selected. The footer's design has exactly one
 * button, and the author who genuinely wants a second one still has the
 * top-left inserter and the hover "+" between blocks — so the inline appender
 * is only an invitation to break the layout (Saku, 2026-08-11: "remove lisää
 * painike from the ota yhteyttä box in footer"). Scoped to the FOOTER's
 * buttons row only: every other core/buttons on the site (loppu-CTA,
 * service-cta) keeps its appender, and the social row's "+" — a different
 * container (.wp-block-social-links, not .wp-block-buttons) — is untouched. */
.editor-styles-wrapper .esteri-footer .wp-block-buttons > .block-list-appender {
	display: none;
}

.esteri-footer__logo {
	/* inline-level on purpose — see the __brand note above: the design's logo
	 * sits in a line box whose descender slack is part of the footer's height. */
	display: inline-block;
	width: 200px;
	height: 60px;
	background-color: hsl(0 0% 100%);
	-webkit-mask-image: var(--esteri-logo);
	mask-image: var(--esteri-logo);
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	-webkit-mask-position: left center;
	mask-position: left center;
}

/* The logo is a core/image now, so it can be swapped with the normal image UI
 * ("still I cant edit the esteri image thats in footer"). A block cannot emit a
 * masked <span>, so the white silhouette comes from a filter instead: every
 * non-transparent pixel goes black then inverts to white, alpha untouched — the
 * same result the alpha mask gave, for any artwork the customer picks.
 *
 * `display: inline-block` on the figure is load-bearing, not cosmetic: see the
 * `.esteri-footer__brand` note above — the logo has to stay inline-level so its
 * line box keeps the descender slack that is part of the footer's height. */
figure.esteri-footer__logo {
	display: inline-block;
	width: 200px;
	height: 60px;
	margin: 0;
	/* A figure inherits a WHITE background here (measured rgb(255,255,255)) — on
	 * the dark footer that painted a solid white 200×60 block over the white
	 * silhouette, so the logo "disappeared" into a rectangle. */
	background: transparent;
	/* Load-bearing. An inline-block's baseline is the baseline of its LAST LINE
	 * BOX — and core's image CSS puts `vertical-align: bottom` on the <img>, which
	 * lifts that baseline ~6.8px off the figure's bottom edge. The empty <span>
	 * this replaced had no line box at all, so its baseline WAS the bottom margin
	 * edge and the full descender slack sat below it. Result of the difference:
	 * the whole footer measured 6.8px shorter on every page — the exact number the
	 * `.esteri-footer__brand` note above records from the first time this was got
	 * wrong. Per CSS 2.1 §10.8.1 an overflow other than `visible` makes the
	 * baseline the bottom margin edge again, which restores it. Nothing is
	 * clipped: the img is exactly 200×60. */
	overflow: hidden;
}
figure.esteri-footer__logo img {
	width: 100%;
	height: 100%;
	object-fit: contain;
	object-position: left center;
	filter: brightness(0) invert(1);
}

.esteri-footer__logo-text {
	font-family: 'Space Mono', monospace;
	font-weight: 700;
	letter-spacing: 0.05em;
	font-size: 1.5rem;
	color: hsl(0 0% 100%);
}

/* `p.` selector (0,1,1) so this beats WP global-styles' `:root :where(p)`
   {font-size: base/16px} rule, which ties a plain `.esteri-footer__tagline`
   (0,1,0) and wins on source order — that forced the tagline to 16px and wrapped
   it a line longer than the source (text-sm = 15px). */
p.esteri-footer__tagline {
	font-size: 0.9375rem;   /* source text-sm (design sm override = 15px) */
	/* source SiteFooter.tsx:96 is `text-sm … leading-relaxed`, and a leading-* class
	 * OVERRIDES the size token's paired line-height: 1.625, not 1.55 (the body value
	 * this had inherited) and not the token's 24px either. */
	line-height: 1.625;
	color: hsl(0 0% 100% / 0.6);
	max-width: 280px;
	margin: 0;
}

.esteri-footer__more {
	display: inline-flex;
	align-items: center;
	gap: 0.375rem;
	font-size: 0.9375rem;   /* source text-sm = 15px (design sm override), not standard 14px */
	font-weight: 500;
	color: hsl(0 0% 100%);
	text-decoration: none;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
	width: fit-content;
}
.esteri-footer__more-label {
	position: relative; /* source: nav-link-hover-flush — the underline SWEEPS in */
	padding-bottom: 0.125rem;
	line-height: 24px;   /* source SiteFooter.tsx:100 `text-sm font-medium` pair (measured 24px) */
}
.esteri-footer__more-label::after {
	content: '';
	position: absolute;
	bottom: 0;
	left: 0;
	right: 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-footer__more:hover,
.esteri-footer__more:focus-visible {
	color: hsl(74 87% 40%); /* brand-green */
}
.esteri-footer__more:hover .esteri-footer__more-label::after,
.esteri-footer__more:focus-visible .esteri-footer__more-label::after {
	transform: scaleX(1);
}
.esteri-footer__more svg {
	transition: transform 0.2s ease;
}
.esteri-footer__more:hover svg,
.esteri-footer__more:focus-visible svg {
	transform: translateX(4px);
}

.esteri-footer__address {
	display: flex;
	flex-direction: column;
	gap: 0.375rem;
	font-size: 0.9375rem;   /* source text-sm = 15px (design sm override), not standard 14px */
	line-height: 24px;      /* …and text-sm is a PAIR — source :105 `space-y-1.5 text-sm` = 15/24 */
	font-style: normal;
	color: hsl(0 0% 100% / 0.6);
}
/* The lines are `core/paragraph` blocks now, so the author can press Enter at
 * the end of one and get the next (Saku, 2026-08-11 — that is the whole reason
 * the box stopped being three fixed slots). A paragraph brings its own margin
 * and theme.json gives `core/paragraph` a lineHeight of 1.6, so both are pinned
 * back to what the `<span>`s computed to: margin 0, and size/leading inherited
 * from the box above (15px/24px). `.esteri-footer__address p` is (0,1,1), which
 * beats theme.json's `:root :where(.wp-block-paragraph)` (0,1,0) and any bare
 * `:where(p)` (0,0,1).
 *
 * `color` is deliberately NOT set here: paragraphs inherit the box's muted white
 * on their own, and pinning it at (0,1,1) would beat `.esteri-footer__company`
 * (0,1,0) and turn the company line from white to muted. */
.esteri-footer__address p {
	margin: 0;
	font-size: inherit;
	line-height: inherit;
}
.esteri-footer__company {
	color: hsl(0 0% 100%);
}
/* iOS Safari's phone-number detection wraps the plain-text number in its own
 * `<a href="tel:…" x-apple-data-detectors>`, which then picked up theme.json's
 * link colour (primary blue, on the blue footer) plus the UA underline — Safari's
 * default link look instead of the muted address line. Keep the tap-to-call, but
 * paint it as the line it sits in. `!important` because WebKit writes inline
 * `color` / `text-decoration-color` onto that anchor in some iOS versions. */
.esteri-footer__address a[href^="tel:"],
.esteri-footer__address a[x-apple-data-detectors] {
	color: inherit !important;
	text-decoration: none !important;
}

.esteri-footer__cta {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	background: hsl(74 87% 40%);
	color: hsl(222 68% 20%);
	font-weight: 600;
	/* Source footer CTA = .btn-primary (index.css): text-base 16px + py-[14px]
	   px-7 → 52px tall. Was 15px/12px 1.25rem = 47px, shifting everything below
	   it ~5px on every page (cont.71 ink-gap oracle, the site-wide −5 flag). */
	font-size: 1rem;
	line-height: 1.5;
	padding: 14px 1.75rem;
	border-radius: var(--radius, 4px);
	text-decoration: none;
	transition: background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source .btn-primary: transition-all duration-150 */
	width: fit-content;
}
.esteri-footer__cta:hover,
.esteri-footer__cta:focus-visible {
	/* Source SiteFooter uses .btn-primary, whose :hover inverts the green fill to
	 * a white surface with dark text + hairline ring (index.css L322-326) — NOT a
	 * lighter green. Match it. */
	background: var(--wp--preset--color--background, #fff);
	color: var(--wp--preset--color--foreground, hsl(222 47% 11%));
	box-shadow: inset 0 0 0 1px hsl(0 0% 0% / 0.15);
}
.esteri-footer__cta:hover svg,
.esteri-footer__cta:focus-visible svg {
	transform: translateX(4px);   /* source SiteFooter.tsx:113 group-hover:translate-x-1 */
}
.esteri-footer__cta svg { transition: transform 0.2s ease; }

.esteri-footer__social {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	padding-top: 0.5rem;
}
/* `p.` selector (0,1,1) so the size beats WP global-styles' `:root :where(p)`
   {font-size: base/16px} (0,1,0), same as the tagline above. The label is a
   <p>; without the bump it rendered 16px instead of source text-sm (15px). */
p.esteri-footer__social-label {
	font-family: 'Space Mono', monospace;
	font-size: 0.9375rem;   /* source text-sm = 15px, not 0.75rem and not the 16px :where(p) tie */
	letter-spacing: 0.12em;
	color: hsl(0 0% 100% / 0.6);
	margin: 0;
	text-transform: uppercase;
}
.esteri-footer__social-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	gap: 0.75rem;
}
.esteri-footer__social-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	border-radius: 50%;
	background: hsl(0 0% 100% / 0.10);
	border: 1px solid hsl(0 0% 100% / 0.15);
	color: hsl(0 0% 100% / 0.8);
	transition: background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-all duration-150 */
}
.esteri-footer__social-icon:hover,
.esteri-footer__social-icon:focus-visible {
	background: hsl(74 87% 40%);
	border-color: hsl(74 87% 40%);
	color: hsl(222 68% 20%);
}

/* ── The footer row is a real core/social-links block ─────────────────────────
 *
 * Saku, 2026-08-11: the icons must be editable in the footer like any other
 * Gutenberg block. So the row is `core/social-links` in parts/footer.html, and
 * these rules carry this design onto core's markup. The DESIGN LIVES ON THE ROW
 * (`.esteri-footer__social-list`), never on a per-item class — an icon the
 * author adds tomorrow gets the same circle as the five shipped today.
 *
 * `.esteri-footer__social-list` above already supplies the flex row and the
 * 12px gap, because that class is written onto the block in the template part.
 * What is left is core's own chrome: it pads each <li>, paints a brand colour
 * behind it in the default style, and sizes its glyph to 24px.
 */
.esteri-footer__social-list.wp-block-social-links {
	/* core sets these from block gap / its own defaults — the row owns them */
	gap: 0.75rem;
	padding-left: 0;
}
/* The ink colour has to be set HERE, on the <li>, not on the anchor: core ships
   `.wp-block-social-links .wp-block-social-link.wp-social-link
   .wp-block-social-link-anchor { color: currentcolor }` at (0,4,0), which beats
   any plain class rule we could put on the anchor and re-inherits whatever the
   <li> holds. And what the <li> holds, in `is-style-logos-only`, is the BRAND
   colour — `:where(.wp-block-social-links.is-style-logos-only)
   .wp-social-link-facebook { color:#0866ff }` — so the mask below, which paints
   `currentColor`, rendered Facebook blue in a row that must be white-80%.
   `:where()` scores 0, so this (0,2,0) row-scoped rule wins cleanly. */
.esteri-footer__social-list .wp-social-link {
	margin: 0;
	padding: 0;
	background: none;
	/* core's own line-height on the <li> adds 4px under the circle */
	line-height: 0;
	color: hsl(0 0% 100% / 0.8);
}
/* The hover ink is set on the ANCHOR's own painted marks, not as a `color` on
   the <li>. `:focus-within` on the <li> was the obvious way and it is wrong: the
   block editor puts focus inside the anchor the moment the block is SELECTED, so
   a selected icon turned dark-on-dark — invisible on the dark footer — while its
   background stayed untouched (measured in the part editor: color rgb(10,16,31)
   on an unhovered, merely-selected block). Pairing the ink with the same trigger
   as the circle keeps the editor honest; memory `editor-must-mirror-live`. */
.esteri-footer__social-list .wp-block-social-link-anchor:hover::before,
.esteri-footer__social-list .wp-block-social-link-anchor:focus-visible::before {
	background-color: hsl(222 68% 20%);
}
.esteri-footer__social-list .wp-block-social-link-anchor:hover svg,
.esteri-footer__social-list .wp-block-social-link-anchor:focus-visible svg {
	fill: hsl(222 68% 20%);
}
/* The circle. Same geometry and transition as .esteri-footer__social-icon above,
   which still dresses the header/drawer rows and the monolith. */
.esteri-footer__social-list .wp-block-social-link-anchor {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	padding: 0;
	border-radius: 50%;
	background: hsl(0 0% 100% / 0.10);
	border: 1px solid hsl(0 0% 100% / 0.15);
	transition: background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.esteri-footer__social-list .wp-block-social-link-anchor:hover,
.esteri-footer__social-list .wp-block-social-link-anchor:focus-visible {
	background: hsl(74 87% 40%);
	border-color: hsl(74 87% 40%);
	color: hsl(222 68% 20%);   /* source: hover:text-accent-foreground */
}
/* Core's `.wp-block-social-links .wp-social-link a { border-bottom: 0 }` (0,2,1) out-specifies the
 * two-class rules above, so the circle shipped with NO bottom hairline and the hover never tinted
 * that edge (hover-sweep: `border-bottom-color` changed on the source only). Three classes win. */
.esteri-footer__social-list .wp-social-link .wp-block-social-link-anchor { border-bottom: 1px solid hsl(0 0% 100% / 0.15); }
/* Hover INK: core's anchor rule is `color: currentcolor` at (0,4,0), so a colour on the anchor's own
 * :hover (0,2,1) never wins — but currentcolor re-inherits the <li>, and the <li> is hovered whenever
 * its only child is. Source: `hover:text-accent-foreground` (brand navy) on the green circle. */
.esteri-footer__social-list .wp-social-link:hover,
.esteri-footer__social-list .wp-social-link:focus-within { color: hsl(222 68% 20%); }
.esteri-footer__social-list .wp-social-link .wp-block-social-link-anchor:hover,
.esteri-footer__social-list .wp-social-link .wp-block-social-link-anchor:focus-visible {
	/* core ALSO ships `.wp-block-social-links .wp-social-link a:hover { border-bottom: 0 }` at (0,3,1),
	 * which out-specifies the (0,3,0) rest rule above the moment the circle is hovered — so width and
	 * style must be restated here at (0,4,0), not just the colour (probed: 1px at rest, 0px on hover). */
	border-bottom: 1px solid hsl(74 87% 40%);
}
/* 16px of ink in a 44px circle — core draws 24px. `flex-shrink:0` because both
   the <svg> and the masked ::before are flex items in the round link. */
.esteri-footer__social-list .wp-block-social-link-anchor svg {
	width: 16px;
	height: 16px;
	fill: currentColor;
	flex-shrink: 0;
}
/* A service this design has no mark for keeps core's, scaled onto our optical
   size: measured with getBBox(), our marks fill 100% of their viewBox while
   core's 49 median 83.3% — exactly core's 20/24 icon grid — so a borrowed mark
   would paint 13.3px of ink beside our 16px. `transform-origin` is explicit
   because CSS Transforms gives an SVG element a `0 0` origin, which would scale
   the mark out of its own corner. A transform, not a tighter viewBox: an <svg>
   clips to its viewport, so full-bleed marks would lose their edges. */
.esteri-footer__social-list .wp-block-social-link-anchor svg {
	transform: scale(1.2);
	transform-origin: center;
}
/* …and where this design DOES have the mark, it wins over core's (the resolve
   order is: the design's own mark → core's mark). The per-service mask rules
   that hide core's <svg> and paint ours are generated from the one registry,
   esteri_social_icon_paths(), in includes/blocks.php — no second copy of the
   path data to drift. Those rules also drop the scale above, since our marks
   are already drawn edge-to-edge. */

/* Right: 4-column nav */

.esteri-footer__cols {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 2rem;
}
@media (min-width: 768px) {
	.esteri-footer__cols {
		grid-template-columns: repeat(4, 1fr);
	}
}

.esteri-footer__col-title {
	display: inline-block;
	font-family: 'Inter', system-ui, sans-serif;
	font-size: 1rem;
	line-height: 1.5rem; /* source h4 text-base pair 16/24 — the theme h2 lh (1.111) sat the text ~3px low */
	font-weight: 600;
	color: hsl(0 0% 100%);
	margin: 0 0 1rem;
	padding-bottom: 0.125rem;
	border-bottom: 2px solid hsl(74 87% 40%);
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}

.esteri-footer__col-title-link {
	color: inherit;
	text-decoration: none;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}

.esteri-footer__col-title-link:hover,
.esteri-footer__col-title-link:focus-visible {
	color: hsl(74 87% 40%); /* source h4 hover:text-brand-green */
}

.esteri-footer__col-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

.esteri-footer__col-link {
	display: inline-block;
	position: relative; /* source: nav-link-hover-flush — sweep, not a border flip */
	font-size: 0.9375rem;   /* source text-sm = 15px (design sm override), not standard 14px */
	color: hsl(0 0% 100%);
	text-decoration: none;
	padding-bottom: 0.125rem;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
	line-height: 24px;    /* source SiteFooter.tsx:150 `text-sm` pair (was body 1.55) */
}
.esteri-footer__col-link::after {
	content: '';
	position: absolute;
	bottom: 0;
	left: 0;
	right: 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-footer__col-link:hover,
.esteri-footer__col-link:focus-visible {
	color: hsl(74 87% 40%);
}
.esteri-footer__col-link:hover::after,
.esteri-footer__col-link:focus-visible::after {
	transform: scaleX(1);
}

/* ─── Trust-badge row ──────────────────────────────────────────────── */

.esteri-footer__badges {
	border-top: 1px solid hsl(0 0% 100% / 0.10);
	padding-block: 2.5rem;
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: 1rem;
}
@media (min-width: 640px) {
	.esteri-footer__badges { gap: 1.5rem; }
}

/* Keyed off the ROW, not just the per-badge class (Saku, 2026-08-10: "the images
 * i add should be same size as others are").
 *
 * The badges are core/image blocks so they can be added, replaced and reordered
 * — but `.esteri-footer__badge` is a className written into parts/footer.html,
 * and an image dropped in from the INSERTER carries nothing but
 * `wp-block-image`. Every rule below used to require that class, so an added
 * badge got none of them: measured against the design's 220×160 white card with
 * a 130px image, it came out transparent, unpadded, no minimum box, and its
 * image at its own natural 300×158.
 *
 * Matching `.esteri-footer__badges > .wp-block-image` as well means the row
 * itself confers the design — an author cannot add a badge that is the wrong
 * size, because nothing has to be typed onto it. The class stays as the second
 * selector so the three pre-placed badges are unaffected, and because a future
 * badge might legitimately live outside this row. */
.esteri-footer__badge,
.esteri-footer__badges > .wp-block-image {
	background: hsl(0 0% 100% / 0.95);
	border: 1px solid hsl(0 0% 100% / 0.10);
	border-radius: var(--radius, 4px);
	padding: 1rem 1.5rem;
	min-height: 160px;
	min-width: 220px;
	display: flex;
	align-items: center;
	justify-content: center;
	backdrop-filter: blur(4px);
	-webkit-backdrop-filter: blur(4px);
}
/* The badges are core/image blocks now (Saku: "for luottamus merkki i should be
 * able to add image and so its not locked"), so the image rule has to reach the
 * <img> a core/image emits as well — a block cannot put a class on its own
 * <img>. The row-scoped selector is what catches a badge the author ADDS, which
 * carries no per-badge class at all; it lands at the common 130px height, which
 * is what two of the three design badges use. */
.esteri-footer__badge-img,
.esteri-footer__badge img,
.esteri-footer__badges > .wp-block-image img {
	width: auto;
	object-fit: contain;
	max-width: 300px;
}
.esteri-footer__badge img,
.esteri-footer__badges > .wp-block-image img {
	height: 130px;
}
/* The design's first badge is shorter. A modifier rather than :first-child, so
 * reordering or inserting badges cannot silently retarget it.
 *
 * THREE classes, and the count is load-bearing: the row-scoped rule above is
 * (0,2,1), so the obvious `.esteri-footer__badge--sm img` at (0,1,1) now loses
 * and that badge would silently jump 80px → 130px. Two classes would only TIE
 * and leave it to source order. This file has been bitten by exactly this
 * arithmetic twice already (the tagline's line-height, `.esteri-footer__top`'s
 * margin) — spend the extra class. */
.esteri-footer .esteri-footer__badges .esteri-footer__badge--sm img {
	height: 80px;
}
/* core/image wraps the <img> in a <figure> with its own margin; the badge box is
 * the figure itself, so zero it or every badge grows by the figure's margin.
 * The row-scoped half covers an added one, whose figure carries no badge class. */
figure.esteri-footer__badge,
.esteri-footer__badges > figure.wp-block-image {
	margin: 0;
}

/* ─── Long author text must WRAP, never run out of its column ──────────
 *
 * Saku, 2026-08-10: "the footer can broke with alot of text". Measured with a
 * long unbroken token in each field: the brand column rendered 555px wide inside
 * its 320px grid track at 1920, and the bottom bar 601px inside 350px at 390.
 * `.esteri-footer` is `overflow: hidden`, so the page does not gain a scrollbar —
 * the text is simply CLIPPED, which is why it reads as the footer breaking rather
 * than as an overflow.
 *
 * `break-word`, not `anywhere`: `anywhere` also shrinks a box's min-content
 * width, which would let the grid and flex tracks resize around REAL content and
 * change the design's wrapping. `break-word` only ever acts when the text would
 * otherwise overflow.
 *
 * Listed per element rather than as `.esteri-footer *`: a blanket net over a
 * composable footer would also catch every block an author drops in, and this
 * file's history is full of nets that changed real content.
 */
.esteri-footer__tagline,
.esteri-footer__more-label,
.esteri-footer__address,
.esteri-footer__address span,
.esteri-footer__company,
.esteri-footer__cta,
.esteri-footer__social-label,
.esteri-footer__copy,
.esteri-footer__meta-link,
.esteri-footer__credit,
.esteri-footer__col-title {
	overflow-wrap: break-word;
}
/* The two column links are `inline-block` — shrink-to-fit boxes, whose width has
 * a min-content FLOOR. `break-word` does not lower min-content (that is exactly
 * what separates it from `anywhere`), so the box stayed 432px wide inside a 229px
 * column even though the text was allowed to break. These two need `anywhere`.
 * Safe here: real menu labels are single short words, so nothing about their
 * min-content changes, and the tracks around them are 1fr / fixed. */
.esteri-footer__col-title-link,
.esteri-footer__col-link {
	overflow-wrap: anywhere;
}
/* …and a grid/flex child must be allowed to shrink below its content's
 * min-content width, or the track itself grows past the container (the
 * `min-width: 0` that fixed the wrapping main-menu bar at cont.82o). */
.esteri-footer .esteri-footer__top > *,
.esteri-footer .esteri-footer__cols > *,
.esteri-footer .esteri-footer__bottom > *,
.esteri-footer .esteri-footer__bottom-left > *,
.esteri-footer .esteri-footer__legal > * {
	min-width: 0;
}
/* The CTA is an inline-flex pill that sizes to its LABEL, so a long label made
 * the button itself 555px wide inside a 320px column — the button, not the text,
 * was the overflow. Cap it at the column and let the label inside it break.
 * (The label is a child <span>, which is why the rule on the anchor alone did
 * nothing.) */
.esteri-footer__cta {
	max-width: 100%;
}
.esteri-footer__cta > span {
	overflow-wrap: break-word;
	min-width: 0;
}
/* The legal group is deliberately one line so the dot never orphans (see
 * footer-bottom/render.php), but "one line" must not mean "wider than the bar":
 * allow it to wrap only when it cannot fit. Real content still fits on one line. */
.esteri-footer__legal {
	flex-wrap: wrap;
}

/* ─── Bottom bar ──────────────────────────────────────────────────── */

.esteri-footer__bottom {
	border-top: 1px solid hsl(0 0% 100% / 0.10);
	padding-top: 1.5rem;
	padding-bottom: 6rem;
	display: flex;
	flex-direction: column;
	gap: 1rem;
}
@media (min-width: 768px) {
	.esteri-footer__bottom {
		flex-direction: row;
		justify-content: space-between;
		align-items: center;
		padding-right: 6rem;
		padding-bottom: 1.5rem;
		gap: 1.5rem;   /* source SiteFooter.tsx:189 `gap-4 md:gap-6` — the md STEP was dropped */
	}
}

.esteri-footer__bottom-left {
	display: flex;
	flex-direction: column;
	/* source :191 `gap-y-2 sm:gap-x-5` pins the two axes SEPARATELY — row 8px
	 * always, column 20px from sm. A single `gap` shorthand quadruples the
	 * row gap the moment this wrapping row wraps. */
	row-gap: 0.5rem;
}
@media (min-width: 640px) {
	.esteri-footer__bottom-left {
		flex-direction: row;
		align-items: center;
		flex-wrap: wrap;
		column-gap: 1.25rem;
	}
}

.esteri-footer__copy {
	font-family: 'Space Mono', monospace;
	font-size: 0.9375rem;   /* source text-sm = 15px (design sm override), not standard 14px */
	color: hsl(0 0% 100% / 0.5);
	line-height: 24px;    /* source SiteFooter.tsx:192 `text-sm font-mono` pair */
}

.esteri-footer__meta-link {
	position: relative; /* source: nav-link-hover-flush — sweep, not a border flip */
	font-family: 'Space Mono', monospace;
	font-size: 0.9375rem;   /* source text-sm = 15px (design sm override), not standard 14px */
	color: hsl(0 0% 100% / 0.7);
	text-decoration: none;
	padding-bottom: 0.125rem;
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
	line-height: 24px;    /* source SiteFooter.tsx:196 `text-sm font-mono` pair */
}
.esteri-footer__meta-link::after {
	content: '';
	position: absolute;
	bottom: 0;
	left: 0;
	right: 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-footer__meta-link:hover,
.esteri-footer__meta-link:focus-visible {
	color: hsl(74 87% 40%);
}
.esteri-footer__meta-link:hover::after,
.esteri-footer__meta-link:focus-visible::after {
	transform: scaleX(1);
}

/* Keep "Tietosuoja • Evästeet" together as one inline group so the dot never
 * wraps onto its own line when the bottom bar wraps at 768/390 (CC6). */
.esteri-footer__legal {
	display: inline-flex;
	align-items: center;
	gap: 1rem;
}

.esteri-footer__meta-dot {
	color: hsl(0 0% 100% / 0.20);
}

.esteri-footer__credit {
	font-family: 'Space Mono', monospace;
	font-size: 0.9375rem;   /* source text-sm = 15px (design sm override), not standard 14px */
	color: hsl(0 0% 100% / 0.5);
	line-height: 24px;    /* source SiteFooter.tsx:209 `text-sm font-mono` pair */
}
.esteri-footer__credit a {
	position: relative;
	color: hsl(0 0% 100% / 0.7);
	text-decoration: none;
	padding-bottom: 0.125rem;
	/* No rest-state underline — the design renders "Site Logic" as colour +
	   hover sweep only (`nav-link-hover nav-link-hover-flush`, no border).
	   An earlier build added a 1px hairline here as a deliberate WCAG 1.4.1
	   divergence (link distinguishable without colour); Saku reported it as a
	   parity defect on 2026-08-03 ("in footer the site logic doesnt have
	   underline in lovable"), and parity wins — same call as the tile-title
	   wrap and the footer column links. The non-colour affordances that remain:
	   the green ::after sweep on hover and a focus-visible outline below, so
	   keyboard users still get a non-colour cue.
	   refs: header-footer-and-menus.md § footer; accessibility-audit.md
	         (1.4.1 — the tradeoff this records). */
	transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.15s cubic-bezier(0.4, 0, 0.2, 1);   /* source: transition-colors */
}
.esteri-footer__credit a::after {
	content: '';
	position: absolute;
	bottom: -1px; /* sits where the design's sweep does (no rest hairline now) */
	left: 0;
	right: 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-footer__credit a:hover,
.esteri-footer__credit a:focus-visible {
	color: hsl(74 87% 40%);
	border-bottom-color: transparent;
}
.esteri-footer__credit a:hover::after,
.esteri-footer__credit a:focus-visible::after {
	transform: scaleX(1);
}
/* Keyboard cue that does not depend on colour — this is what carries the a11y
   weight now that the rest-state hairline is gone (see the note above). */
.esteri-footer__credit a:focus-visible {
	outline: 2px solid hsl(74 87% 40%);
	outline-offset: 2px;
}

/* 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;
}
/* FIXED size, guaranteed (Saku, 2026-08-10: "the images i add should be same
 * size as others are" → "so fixed size"). The box already measured 16×16 for
 * every icon, uploaded or bundled — but both are FLEX ITEMS inside the round
 * link, and a flex item's default `flex-shrink: 1` means the box is only fixed
 * until something makes the row tight. Pin it so it cannot be negotiated away.
 *
 * What is NOT fixable here: how much of that box the artwork fills. `contain`
 * scales the file to the box without cropping or distorting it, so a mark drawn
 * edge-to-edge fills all 16px and matches the bundled glyphs exactly, while
 * transparent margin baked INTO the file, or a wide wordmark, renders smaller —
 * measured side by side at 3×. No fit mode fixes that: `cover` would fill the
 * box by cropping the artwork, and for a square file (the padded case) it is
 * identical to `contain` anyway. It is the upload that has to be square and
 * edge-to-edge; the editor's own hint now says so. */
.esteri-footer__social-icon > svg,
.esteri-footer__social-icon > .esteri-social-icon-img {
	flex-shrink: 0;
}

/* A mark borrowed from WordPress's own social-link set, scaled onto this
 * design's optical size. 24/20 is core's icon grid, not a number tuned by eye:
 * measured with getBBox(), our five marks fill 100% of their viewBox while
 * core's 47 median 83.3% — exactly 20/24 — so GitHub painted 13.3px of ink
 * beside our 16px. See esteri_social_core_icon() for why this is a transform
 * and not a tighter viewBox (an <svg> clips; a transform does not).
 *
 * `transform-origin` is explicit because it is NOT `center` by default on an
 * SVG element — CSS Transforms gives SVG a `0 0` origin, which would scale the
 * mark out of its own corner. */
.esteri-social-icon--wp {
	transform: scale(1.2);
	transform-origin: center;
}
/* …drawn as a SILHOUETTE in the row's own colour, not as the picture. Saku,
 * 2026-08-10: "if i add image to footer it doesnt have white background like
 * others do" — an uploaded logo kept its own colours, so on the navy footer it
 * was dark-on-dark and effectively invisible next to the white glyphs. The
 * bundled marks are white because they are `fill="currentColor"`; masking the
 * image with `background-color: currentColor` gives it the same property, so it
 * follows the row everywhere with no per-surface rule — white at 80% here, near
 * black on the green hover, the utility bar's dark ink up in the header.
 * Only formats WITH an alpha channel get this (see esteri_social_icon_markup);
 * a JPEG would mask to a solid block. */
.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;
}
