/* Simple TT5 — scoped skin (front end + editor).
   Design tokens come from theme.json (--wp--preset--*). This file adds parity
   details theme.json can't express and the CSS half of the motion layer.
   Motion CSS only ACTS on elements that carry sa-* classes (added per block),
   so it is inert until those classes exist. */

:root {
	--sa-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
	--sa-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
	--sa-reveal-y: 24px;
	--sa-reveal-dur: 0.8s;
}

/* WP constrained layout centres any block narrower than contentSize (margin
   auto). Headlines with a max-width must opt out to stay left-aligned like live. */
.is-layout-constrained > .sa-headline-left { margin-inline-start: 0; margin-inline-end: auto; }
/* R0.9 (2026-07-24): the home hero H1 carried `max-width:62rem` as an INLINE style, which
   core/heading cannot regenerate -> Gutenberg flagged the block invalid ("Attempt recovery"
   would have overwritten the markup). The measurement moves here so the rendering is
   byte-identical while the block becomes valid. Scoped to the front page only. */
.home h1.sa-headline-left { max-width: 62rem; }

/* ---- Base ---- */
body {
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-rendering: optimizeLegibility;
}
::selection { background: var(--wp--preset--color--brand); color: var(--wp--preset--color--white); }

/* Eyebrow / kicker label: mono, tracked, with a leading rule (matches live) */
.sa-eyebrow {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--eyebrow);
	letter-spacing: 0.16em;
	text-transform: uppercase;
	display: inline-flex;
	align-items: center;
	gap: 0.7em;
	color: var(--wp--preset--color--muted);
}
.sa-eyebrow::before {
	content: "";
	width: 16px;
	height: 1px;
	background: currentColor;
	display: inline-block;
}

/* Small portfolio-card category label uses Clash Display (the one place it appears) */
.sa-card-label { font-family: var(--wp--preset--font-family--label); font-weight: 500; }

/* Card images must carry an explicit width: WP 6.7+ injects sizes="auto" on
   lazy images, and without CSS width the browser picks the SMALLEST srcset
   candidate (renders 200px thumbnails). */
.sa-portfolio-row img, .sa-blogthumb img { width: 100%; height: auto; }
.sa-portfolio-row figure, .sa-blogthumb { margin: 0 0 0.75rem; }

/* ---- Header / wordmark / nav ---- */
/* The real logo (2026-11-10, G: "you never use the simple as S-/-I-M-P-L-E"). Every logo
   is the one vector in assets/brand/simple-logo.svg, printed by [sa_logo] (functions.php).
   fill = currentColor, so the colour comes from here: white on blue/dark, brand blue on
   light. The dot is 50% opacity inside the SVG itself. Never type "s/mple" in its place. */
.sa-logo { display: block; height: auto; fill: currentColor; }
.sa-wordmark { display: block; flex: none; line-height: 0; color: #FFFFFF; text-decoration: none; }
/* 86 x 19 = Framer's header logo, measured live at 1440 and 390. */
.sa-header .sa-wordmark .sa-logo { width: 86px; }
.sa-header { position: sticky; top: 0; z-index: 100; }

/* Header background lives HERE, not as a preset class in markup (WP preset
   classes carry !important). Inner pages: dark bar. Front page: transparent,
   floating over the hero, wordmark centered + letter-spaced (matches live). */
body:not(.home) .sa-header { background-color: var(--wp--preset--color--ink); }
body.home .sa-header { position: absolute; top: 0; left: 0; right: 0; z-index: 100; }
/* Centred on the page, not in the flex row: the off-screen nav still takes ~20px there,
   which pushed the logo 10px left. top 23px = Framer's home logo (measured 1440 + 390). */
body.home .sa-header .sa-wordmark { position: absolute; top: 23px; left: 50%; transform: translateX(-50%); }
/* Home: no top hamburger (live has none) — the MENU pill opens the overlay.
   Keep the nav in the DOM (off-screen, not display:none) so its overlay + the
   MENU-pill JS click still work; just hide the open button visually. */
body.home .sa-header .wp-block-navigation__responsive-container-open {
	position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none;
}
/* The OPEN hamburger sits on the dark inner-page header, so it stays surface.
   The CLOSE button lives inside the overlay, which is now WHITE — it must take the
   overlay's own brand colour or it would be near-white on white (invisible). */
.sa-header .wp-block-navigation__responsive-container-open { color: var(--wp--preset--color--surface); }
.sa-header .wp-block-navigation__responsive-container-close { color: var(--wp--preset--color--brand); }
/* Full-screen overlay menu — MOBILE route only.
   2026-07-27 (a): it opened correctly but was UNUSABLE — at the 48px subheading
   preset ten items overflowed the viewport (Blog half cut off, Contact below the
   fold), so the type now scales with viewport height and the container scrolls.
   2026-07-27 (b): G's call — white panel, links RIGHT-aligned and blue. The nav's
   own `justifyContent:"right"` already wants right, so the previous centring
   overrides are gone rather than re-fought; only the <li> needs restating because
   it is a COLUMN flex (horizontal placement there is align-items, not
   justify-content). Desktop no longer uses this overlay at all — the MENU pill
   expands the pill bar inline instead (see .sa-pillbar.is-expanded below). */
/* Core ships the open panel at `padding: 16px 48px` and its stylesheet is printed
   AFTER skin.css, so that 48px cannot be overridden from here without !important —
   verified: a `padding-inline` rule on the same selector resolved to core's 48px and
   was dropped again. The 48px is fine on its own; the problem was the content's own
   inline padding STACKING on it (70px right inset on a 375px phone, four labels
   forced onto two lines, last item 15px from the bottom edge). Zeroing the content's
   inline padding is the whole fix: 48px inset, 37px clearance, one less wrapped label. */
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content {
	align-items: flex-end;
	justify-content: center;
	text-align: right;
	width: 100%;
	max-height: 100dvh;
	overflow-y: auto;
	padding: clamp(3.25rem, 9vh, 5rem) 0 clamp(1rem, 3vh, 2rem);
}
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container {
	align-items: flex-end;
	justify-content: center;
	width: 100%;
	gap: clamp(0.1rem, 0.6vh, 0.55rem);
}
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item {
	width: 100%;
	align-items: flex-end !important;
	text-align: right;
}
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
	font-size: clamp(1rem, 4vh, 2.25rem);
	line-height: 1.12;
	padding-block: 0.06em;
}
.sa-nav a { text-decoration: none; }
/* 2026-11-10: motion.js animates the overlay open/close itself (circle reveal + staggered
   links) when JS runs without reduced motion; core's own 0.1s fade would double it. */
html.sa-menu-motion .wp-block-navigation__responsive-container.is-menu-open { animation: none; }

/* ---- Pill-nav strip: FIXED bottom bar, persists through scroll (matches live) ---- */
.sa-pillbar {
	position: fixed;
	bottom: 16px;
	left: 50%;
	transform: translateX(-50%);
	z-index: 90;
	width: max-content;
	max-width: calc(100vw - 24px);
	/* WP block-gap gives this group a 19.2px top margin as a flow sibling. Harmless
	   while the bar was bottom-anchored (margin-top does not move a box positioned
	   from `bottom`), but the dvh rule below anchors from `top`, where that margin
	   pushes the bar 19.2px DOWN — measured 3px past the viewport bottom. */
	margin-block: 0;
	background: rgba(241, 241, 241, 0.82);
	backdrop-filter: blur(8px);
	-webkit-backdrop-filter: blur(8px);
	border-radius: 100px;
	padding: 0.45rem 0.9rem;
	gap: 0.2rem 0.95rem;
	box-shadow: 0 6px 24px rgba(23, 23, 23, 0.08);
}
.sa-pill { margin: 0; font-size: 0.72rem; white-space: nowrap; }
.sa-pill a { color: var(--wp--preset--color--dark-2); text-decoration: none; display: inline-flex; align-items: center; gap: 0.45em; }
.sa-pill:not(.sa-pill-contact):not(.sa-pill-home) a::before {
	content: "";
	width: 8px; height: 8px;
	border: 1px solid currentColor;
	border-radius: 50%;
	display: inline-block;
	flex: none;
}
.sa-pill-contact a, .sa-pill-home a { border-radius: 100px; padding: 0.45em 1.1em; }
.sa-pill-contact a { background: var(--wp--preset--color--pale-2); color: var(--wp--preset--color--brand); }
.sa-pill-home a { background: var(--wp--preset--color--brand); color: var(--wp--preset--color--white); }
.sa-pill-menu a { cursor: pointer; }
/* iOS Safari anchors `position:fixed` to the LAYOUT viewport, which extends behind
   the collapsible bottom toolbar. At scroll 0 on an iPhone the whole bar therefore
   painted UNDERNEATH Safari's toolbar — G reported "there is no menu, at all" on
   mobile, and the maths confirms it: bar top = 812 - 16 - 43 = 753, while only
   ~752px of that layout viewport is actually on screen. Chrome's device emulation
   does NOT reproduce this (the bar probes as visible at 375x812), which is why it
   was never caught. `dvh` tracks the VISUAL viewport, so the bar now rides above
   the toolbar and follows it as it collapses on scroll. */
@supports (height: 100dvh) {
	.sa-pillbar {
		top: calc(100dvh - 16px);
		bottom: auto;
		transform: translate(-50%, -100%);
	}
}
/* Collapsed bar: Contact · home · MENU. Written as :not(.is-expanded) rather than a
   higher-specificity unhide rule so the expanded state below needs no specificity
   games and no !important. */
.sa-pillbar:not(.is-expanded) .sa-pill:not(.sa-pill-contact):not(.sa-pill-menu):not(.sa-pill-home) { display: none; }
.sa-pillbar { padding: 0.45rem 0.55rem; gap: 0.15rem; }
.sa-pill-menu a, .sa-pill:not(.sa-pill-contact):not(.sa-pill-home) a { padding: 0.45em 1.1em; }
/* DESKTOP menu (G, 2026-07-27): clicking MENU expands the bar in place so every page
   pill sits side by side — no black full-screen overlay on desktop. The MENU pill
   itself drops out of the expanded bar (G's reference shows no MENU chip once open);
   Escape, an outside click or following a link collapses it again, wired in
   motion.js. Mobile keeps the overlay route: ten pills will not fit a phone bar. */
.sa-pillbar.is-expanded { gap: 0.15rem 0.35rem; }
.sa-pillbar.is-expanded .sa-pill-menu { display: none; }

/* ---- Audience bands + why-us cards + list pills ---- */
.sa-whyus-card { background: var(--wp--preset--color--white); }

/* ---- Home "The options" comparison (2026-11-10 positioning: "A full marketing team, in
   one or two seats"). Four cards, no prices: in-house hire · freelancers + fractional CMO ·
   traditional agency · Simple (brand blue). 4 across >=1200px, 2x2 from 782px, stacked below. */
.sa-compare-card {
	box-sizing: border-box; /* else the 32px padding sits on top of the 50% basis and forces a wrap */
	background: var(--wp--preset--color--white);
	border: 1px solid var(--wp--preset--color--hairline);
	border-radius: 24px;
	padding: var(--wp--preset--spacing--40);
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}
.sa-compare-card > * { margin-block: 0; }
.sa-compare-card.is-simple { background: var(--wp--preset--color--brand); border-color: var(--wp--preset--color--brand); color: var(--wp--preset--color--white); }
.sa-compare-kicker { text-transform: uppercase; letter-spacing: 0.14em; color: var(--wp--preset--color--muted); }
.sa-compare-card.is-simple .sa-compare-kicker { color: rgba(255, 255, 255, 0.72); }
.sa-compare-title { font-weight: 500; margin-bottom: 0.5rem !important; }
.sa-compare-card.is-simple .sa-compare-title { color: var(--wp--preset--color--white); }
.sa-compare-list { list-style: none; padding: 0; }
.sa-compare-list li {
	display: flex;
	gap: 12px;
	align-items: flex-start;
	padding: 11px 0;
	border-top: 1px solid rgba(23, 23, 23, 0.07);
	font-size: 0.92rem;
	line-height: 1.55;
	color: rgba(23, 23, 23, 0.72);
}
.sa-compare-list li::before { content: ""; width: 12px; height: 1px; background: rgba(23, 23, 23, 0.35); flex: none; margin-top: 0.8em; }
.sa-compare-card.is-simple .sa-compare-list li { border-top-color: rgba(255, 255, 255, 0.2); color: rgba(255, 255, 255, 0.94); }
.sa-compare-card.is-simple .sa-compare-list li::before { width: 6px; height: 6px; border-radius: 50%; background: var(--wp--preset--color--white); margin-top: 0.6em; }
@media (max-width: 781.98px) {
	/* phones: the section already sits inside 80px of side inset, so keep the card padding lean */
	.sa-compare-card { padding: 24px 20px; }
	.sa-compare-list li { padding: 9px 0; font-size: 0.9rem; }
}
@media (min-width: 782px) and (max-width: 1199.98px) {
	.wp-block-columns.sa-compare-grid { flex-wrap: wrap !important; }
	/* basis leaves room for the gap; grow fills the row, so it is always exactly two per row */
	.wp-block-columns.sa-compare-grid > .wp-block-column { flex: 1 1 calc(50% - var(--wp--preset--spacing--30)) !important; }
}
.sa-band { overflow: hidden; }
.sa-band-art { margin: 0; }
.sa-band-art img { width: 100%; height: auto; transform: rotate(72deg) scale(1.35); }
.sa-band-businesses .sa-band-art img { transform: rotate(-72deg) scale(1.35); }
/* List markers (from live mobile+desktop evidence): bands use a pale circled "+",
   why-us cards use a small brand square. No pill backgrounds. */
.sa-fitlist { list-style: none; padding-left: 0; }
.sa-fitlist li {
	display: flex;
	align-items: center;
	gap: 0.7em;
	margin-bottom: 0.85em;
	font-size: 0.9rem;
	font-weight: 500;
}
.sa-band .sa-fitlist li::before {
	content: "+";
	width: 1.5em; height: 1.5em;
	border-radius: 50%;
	background: var(--wp--preset--color--brand-4);
	color: var(--wp--preset--color--white);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 0.9em;
	line-height: 1;
	flex: none;
}
.sa-whyus-card .sa-fitlist li::before {
	content: "";
	width: 8px; height: 8px;
	background: var(--wp--preset--color--brand);
	flex: none;
}
.sa-whyus-card .sa-fitlist li { font-weight: 400; }

/* ---- Service pages ---- */
.sa-hero-inner .wp-block-cover__inner-container { width: 100%; max-width: 1360px; margin-inline: auto; padding-inline: var(--wp--preset--spacing--40); }
.sa-statsband .wp-block-columns { gap: 0; }
.sa-statsband .wp-block-column { padding-inline: var(--wp--preset--spacing--40); }
.sa-statsband .wp-block-column + .wp-block-column { border-left: 1px solid rgba(241,241,241,0.14); }
.sa-stat-num { font-size: clamp(3rem, 1.6rem + 5vw, 4.75rem); margin: 0 0 0.4em; }
.sa-source { opacity: 0.8; }
/* Equal-height service cards WITHOUT overflow: height:100% made every stacked card
   as tall as its whole column (2-3 cards => 200-300% => cards bled under the next
   section). Flex-grow shares the column height instead. */
.wp-block-column:has(> .sa-svc-card) { display: flex; flex-direction: column; }
.sa-svc-card { flex: 1 1 auto; }
.sa-svc-card:hover { border-color: var(--wp--preset--color--brand) !important; }
/* fit-check "not the right fit" list uses a muted dash marker instead of the brand dot */
.sa-fitlist-no li::before { background: transparent; width: 12px; height: 1px; border-radius: 0; align-self: center; background-color: var(--wp--preset--color--muted); }
.sa-fitlist-no li { background: var(--wp--preset--color--surface); }
.sa-band-dark .sa-fitlist li { background: rgba(255,255,255,0.08); }
@media (max-width: 781px) { .sa-statsband .wp-block-column + .wp-block-column { border-left: none; border-top: 1px solid rgba(241,241,241,0.14); padding-top: var(--wp--preset--spacing--30); margin-top: var(--wp--preset--spacing--30); } }

/* ---- Footer (light closer: stacked tagline + sphere orb panel, matches live) ---- */
.sa-footer a { color: inherit; text-decoration: none; opacity: 0.75; transition: opacity 0.2s var(--sa-ease-out); }
.sa-footer a:hover { opacity: 1; }
.sa-footer-orb {
	position: relative;
	height: clamp(220px, 29vw, 420px);
	overflow: hidden;
	display: flex;
	align-items: center;
	justify-content: center;
}
.sa-footer-orb-img { margin: 0; flex: none; }
.sa-footer-orb-img img { width: clamp(260px, 32vw, 465px); height: auto; display: block; }
.sa-footer-tagline { font-size: clamp(1.6rem, 1.1rem + 1.7vw, 2.5rem); }
.sa-footer-orb-wordmark {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	margin: 0;
	color: #FFFFFF;
	pointer-events: none;
}
/* White logo centred on the sphere: 214px at 1440, 151px at 390 (the old text's width). */
.sa-footer-orb-wordmark .sa-logo { width: clamp(140px, 8rem + 6vw, 240px); }

/* ---- Motion layer (class-driven; gated on html.sa-js so no-JS shows everything) ---- */
html.sa-js .sa-reveal {
	opacity: 0;
	transform: translateY(var(--sa-reveal-y));
	transition: opacity var(--sa-reveal-dur) var(--sa-ease-out),
	            transform var(--sa-reveal-dur) var(--sa-ease-out);
	will-change: opacity, transform;
}
html.sa-js .sa-reveal-left { transform: translateX(calc(-1 * var(--sa-reveal-y))); }
html.sa-js .sa-reveal-right { transform: translateX(var(--sa-reveal-y)); }
html.sa-js .sa-reveal.is-inview { opacity: 1; transform: none; }
/* stagger children via inline --sa-delay set by markup/JS */
html.sa-js .sa-reveal { transition-delay: var(--sa-delay, 0s); }

/* Marquee / logo ticker: JS duplicates the track for a seamless loop */
.sa-marquee { overflow: hidden; }
.sa-marquee__track { display: flex; width: max-content; animation: sa-marquee-scroll var(--sa-marquee-dur, 28s) linear infinite; }
@keyframes sa-marquee-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* Headline highlighter: brand-blue block draws in behind key words (matches live).
   Text goes white as the fill passes under it. */
.sa-highlight {
	position: relative;
	color: var(--wp--preset--color--white);
	padding: 0.02em 0.18em;
	border-radius: 6px;
	background-image: linear-gradient(var(--wp--preset--color--brand), var(--wp--preset--color--brand));
	background-repeat: no-repeat;
	background-position: left center;
	background-size: 0% 100%;
	transition: background-size 0.7s var(--sa-ease-out) 0.2s, color 0.35s linear 0.35s;
	-webkit-box-decoration-break: clone;
	box-decoration-break: clone;
}
html.sa-js .sa-highlight:not(.is-inview) { color: inherit; background-size: 0% 100%; }
html.sa-js .sa-highlight.is-inview { background-size: 100% 100%; }
html:not(.sa-js) .sa-highlight { background-size: 100% 100%; }

/* ---- Hover micro-interactions ---- */
.wp-block-button__link { transition: transform 0.25s var(--sa-ease-spring), filter 0.25s var(--sa-ease-out); }
.wp-block-button__link:hover { transform: translateY(-2px); filter: brightness(1.08); }
.sa-pill a { transition: opacity 0.2s var(--sa-ease-out); }
.sa-pill:not(.sa-pill-contact):not(.sa-pill-home):not(.sa-pill-menu) a:hover { opacity: 0.6; }
.sa-pill-contact a, .sa-pill-home a, .sa-pill-menu a { transition: transform 0.25s var(--sa-ease-spring), filter 0.2s; }
.sa-pill-contact a:hover, .sa-pill-home a:hover, .sa-pill-menu a:hover { transform: translateY(-1px); filter: brightness(1.06); }
.sa-portfolio-row figure { transition: transform 0.4s var(--sa-ease-out); }
.sa-portfolio-row .wp-block-column:hover figure { transform: translateY(-6px); }
.sa-blogthumb img { transition: transform 0.5s var(--sa-ease-out); }
.sa-blogthumb:hover img { transform: scale(1.03); }

/* ============================================================
   SERVICE-PAGE PATTERN v2 — true 1:1 with live (D2b 2026-07-16).
   Values measured from the live Framer CSS (crawl-live/html).
   Scoped so nothing here repaints the approved homepage.
   ============================================================ */

/* Hero: italic closing phrase, hairline-topped sub grid (live .bp-hero-sub) */
.sa-hero-inner h1 em { font-style: italic; color: rgba(255,255,255,0.65); }
.sa-hero-sub { border-top: 1px solid rgba(255,255,255,0.14) !important; padding-top: var(--wp--preset--spacing--40); max-width: 840px; margin-left: 0 !important; margin-right: auto !important; }
.sa-hero-sub p { font-size: 0.97rem; line-height: 1.72; color: rgba(255,255,255,0.92); } /* v2.1: G call — live's 0.6 too dim on the blue */
.sa-hero-sub p.sa-eyebrow { color: rgba(255,255,255,0.7); } /* v2.1: raised from 0.45 */

/* Keyword marquee strip between hero and stats (live .bp-marquee, bg #171717 h48) */
.sa-kwmarquee { background: var(--wp--preset--color--ink); height: 62px; display: flex; align-items: center; overflow: hidden; margin-block-start: 0; } /* v2.1: flush under the hero (live gap = 0; WP block-gap added 19px of white) · v2.2: 48px → 62px, G call (+30%) */
/* 2026-07-27: WP gives the first block inside .wp-site-blocks a block-gap top margin
   (1.2rem), and it COLLAPSES out through the wrapper — so the body background shows as a
   white stripe above the hero. The v2.2 rule fixed inner pages but explicitly excluded
   `.home`, which is exactly where G saw the stripe. Now applied to EVERY template so no
   future template can miss it. */
main.wp-block-group { margin-block-start: 0; }
.sa-kwmarquee .sa-marquee__track { display: flex; flex-wrap: nowrap; width: max-content; animation: sa-marquee-scroll 40s linear infinite; } /* v2.2: 20s → 40s, G call (~50% speed) */
.sa-kwmarquee p { font-family: var(--wp--preset--font-family--mono); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(255,255,255,0.55); margin: 0; padding: 0 0 0 32px; display: flex; align-items: center; gap: 32px; white-space: nowrap; } /* v2.1: 0.3 → 0.55, G contrast call */
.sa-kwmarquee p::after { content: '·'; color: var(--wp--preset--color--sky); font-size: 16px; }

/* Light stats band (live .bp-stats: page bg, brand-blue numbers, hairline dividers) */
.sa-statsband-light .sa-stat-num { color: var(--wp--preset--color--brand); font-size: clamp(3rem, 5.5vw, 4.5rem); letter-spacing: -0.05em; line-height: 1; margin-bottom: 16px; }
.sa-statsband-light .wp-block-column { border-right: 1px solid rgba(23,23,23,0.09); padding-right: 48px; }
.sa-statsband-light .wp-block-column:last-child { border-right: none; }
.sa-statsband-light .sa-stat-label { font-size: 0.85rem; line-height: 1.55; color: rgba(23,23,23,0.62); max-width: 240px; }
.sa-statsband-light .sa-source { color: rgba(23,23,23,0.62); }
@media (max-width: 781px) {
	.sa-statsband-light .wp-block-column { border-right: none; border-bottom: 1px solid rgba(23,23,23,0.09); padding: 0 0 24px; margin-bottom: 24px; }
	.sa-statsband-light .wp-block-column:last-child { border-bottom: none; margin-bottom: 0; }
}

/* Numbered service rows with hover-reveal detail (live .bp-svc-row) */
.sa-svc-rows { border-top: 1px solid rgba(23,23,23,0.09); }
.sa-svc-row { display: grid; grid-template-columns: 52px 1fr auto; gap: 32px; align-items: center; padding: 24px 0; border-bottom: 1px solid rgba(23,23,23,0.09); transition: padding 0.3s cubic-bezier(0.16,1,0.3,1); }
.sa-svc-rows > .sa-svc-row { margin: 0; }
.sa-svc-row > * { margin: 0; }
.sa-svc-row:hover { padding-top: 32px; padding-bottom: 32px; }
.sa-svc-num { font-family: var(--wp--preset--font-family--mono); font-size: 10.5px; letter-spacing: 0.08em; color: rgba(23,23,23,0.62); }
.sa-svc-title { font-family: var(--wp--preset--font-family--display); font-weight: 400; font-size: clamp(18px, 2.5vw, 22px); letter-spacing: -0.02em; line-height: 1.3; }
.sa-svc-detail { font-size: 14px; line-height: 1.65; color: rgba(23,23,23,0.62); max-height: 0; opacity: 0; overflow: hidden; margin-top: 6px; transition: max-height 0.35s cubic-bezier(0.16,1,0.3,1), opacity 0.25s; }
.sa-svc-row:hover .sa-svc-detail { max-height: 140px; opacity: 1; margin-top: 6px; }
.sa-svc-row::after { content: '→'; grid-column: 3; color: rgba(23,23,23,0.35); font-size: 20px; line-height: 1; transition: transform 0.3s, opacity 0.3s; }
.sa-svc-row:hover::after { transform: translateX(4px); opacity: 0.7; }
@media (hover: none) { .sa-svc-detail { max-height: none; opacity: 1; } } /* touch: details always visible */

/* Italic pull-quote beside section headings (live .bp-quote) */
.sa-pullquote { font-style: italic; font-size: 0.94rem; line-height: 1.75; color: rgba(23,23,23,0.62); border-left: 1px solid rgba(23,23,23,0.18); padding-left: 24px; }

/* Dark-band checklist: transparent rows, blue dot, hairlines (live .bp-check / .fr-fl) */
.sa-checklist { list-style: none; padding: 0; margin: 0; }
.sa-checklist li { display: flex; gap: 12px; align-items: flex-start; padding: 12px 0; border-bottom: 1px solid rgba(255,255,255,0.1); font-size: 0.94rem; line-height: 1.6; color: rgba(255,255,255,0.8); }
.sa-checklist li:last-child { border-bottom: none; }
.sa-checklist li::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--wp--preset--color--sky); flex: none; margin-top: 0.55em; }

/* Climax: full-bleed near-black band with ghost outline numeral (live .bp-climax #0A0A0A) */
.sa-climax { position: relative; overflow: hidden; }
.sa-eyebrow.has-text-align-center { display: flex; width: 100%; justify-content: center; } /* v2.1: inline-flex shrink-wrapped at the column's left edge (351px off band center at 1280) — block-level flex spans the band so the kicker truly centers, matching live */
.sa-climax hr { width: 480px; max-width: 60%; margin-left: auto; margin-right: auto; } /* live: short centered hairline */
.sa-climax-ghost { position: absolute; right: -2vw; top: 50%; transform: translateY(-50%); font-family: var(--wp--preset--font-family--display); font-weight: 700; font-size: clamp(120px, 22vw, 320px); letter-spacing: -0.06em; line-height: 1; color: transparent; -webkit-text-stroke: 1px rgba(255,255,255,0.07); pointer-events: none; user-select: none; margin: 0; }

/* Practice row: stone rounded band, copy left / arrow link right (live .bp-practice-row) */
.sa-practice-row { border-radius: 24px; }
.sa-work-link a { color: var(--wp--preset--color--brand); font-weight: 500; text-decoration: none; white-space: nowrap; }
.sa-work-link a::after { content: ' →'; transition: transform 0.3s; display: inline-block; }
.sa-work-link a:hover::after { transform: translateX(3px); }

/* Fit-check v2: eyebrow-led cards, flat hairline items, dot/dash markers (live .bp-fit-*) */
.sa-fitcheck .wp-block-column .sa-eyebrow::before { display: none; } /* live fit labels have no leading rule */
.sa-fitcheck .sa-fitlist { list-style: none; padding: 0; margin: 0; }
.sa-fitcheck .sa-fitlist li { background: transparent; border-radius: 0; display: flex; gap: 12px; align-items: flex-start; padding: 11px 0; border-bottom: 1px solid rgba(0,0,0,0.07); font-size: 0.94rem; line-height: 1.62; color: rgba(23,23,23,0.72); }
.sa-fitcheck .sa-fitlist li:last-child { border-bottom: none; padding-bottom: 0; }
.sa-fitcheck .sa-fitlist-yes li::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--wp--preset--color--brand); flex: none; margin-top: 0.55em; }
.sa-fitcheck .sa-fitlist-no li::before { content: ''; width: 12px; height: 1px; background: rgba(23,23,23,0.35); border-radius: 0; flex: none; margin-top: 0.8em; }

/* CTA v2: content left / pill button right, sky button (live .bp-cta / .bp-btn) */
.sa-cta .wp-block-columns { align-items: center; }
.sa-cta .wp-block-button__link { background: var(--wp--preset--color--brand); color: var(--wp--preset--color--white); border-radius: 100px; padding: 18px 32px; font-size: 0.9rem; font-weight: 500; }
.sa-cta .wp-block-button__link:hover { background: var(--wp--preset--color--brand-2); box-shadow: 0 8px 32px rgba(51,136,255,0.38); }

/* Inner-page pill bar: full service nav w/ radio dots + active state (live service
   pages). This bar is ALREADY in G's "expanded" shape on desktop — every page pill
   side by side — so it needs no MENU chip there; the chip is unhidden only on
   mobile, where the eight service pills collapse away.
   NOTE the leading `.sa-pillbar-nav.sa-pillbar`: the collapsed-state rule above is
   (0,6,0) because of its :not(.is-expanded), so this unhide has to carry one more
   class to outrank it. Losing that race would silently collapse every inner page. */
.sa-pillbar-nav.sa-pillbar:not(.is-expanded) .sa-pill:not(.sa-pill-contact):not(.sa-pill-menu):not(.sa-pill-home) { display: block; }
.sa-pillbar-nav .sa-pill-menu { display: none; }
.sa-pill.is-active a { background: var(--wp--preset--color--white); border-radius: 100px; }
.sa-pill.is-active a::before { background: var(--wp--preset--color--brand); border-color: var(--wp--preset--color--brand); }
@media (max-width: 781px) {
	/* same :not chain as the desktop unhide rule — equal specificity, later wins */
	.sa-pillbar-nav.sa-pillbar:not(.is-expanded) .sa-pill:not(.sa-pill-contact):not(.sa-pill-menu):not(.sa-pill-home) { display: none; }
	.sa-pillbar-nav .sa-pill-menu { display: block; }
}

/* ============================================================
   MOTION v2 — LOUD (G directive 2026-07-16). Scoped to inner
   pages (body.page) + service-page classes; approved home keeps
   its original motion. All effects transform/opacity/filter only.
   ============================================================ */

/* Bigger travel + longer springs on inner pages */
body.page, body.blog, body.single { --sa-reveal-y: 56px; --sa-reveal-dur: 0.95s; } /* D4: blog family joins the loud layer */

/* Hero entrance choreography (hero is above the fold: plays on load) */
body.page .sa-hero .sa-eyebrow.sa-reveal { transition-delay: 0.05s; }
body.page .sa-hero .sa-hero-sub.sa-reveal { transition-delay: 0.6s; }

/* Word-rise headlines: motion.js splits heading text into .sa-w spans.
   The split heading opts out of its block-level reveal; words carry it. */
html.sa-js .sa-split.sa-reveal { opacity: 1; transform: none; transition: none; }
html.sa-js .sa-split .sa-w { display: inline-block; opacity: 0; transform: translateY(0.65em); transition: opacity 0.55s var(--sa-ease-out), transform 0.85s var(--sa-ease-out); transition-delay: var(--sa-w-delay, 0s); }
html.sa-js .sa-split.is-inview .sa-w { opacity: 1; transform: none; }
body.page .sa-hero .sa-split .sa-w { transition-delay: calc(0.2s + var(--sa-w-delay, 0s)); } /* H1 rises just after the eyebrow */

/* Stat numbers pop in with blur+scale (counter roll-up in motion.js) */
html.sa-js .sa-statsband-light .sa-reveal .sa-stat-num,
html.sa-js .sa-climax .sa-stat-num.sa-reveal {
	opacity: 0; transform: scale(0.8); filter: blur(10px);
	transition: opacity 0.7s var(--sa-ease-out), transform 0.9s var(--sa-ease-out), filter 0.9s var(--sa-ease-out);
	transition-delay: var(--sa-delay, 0s);
}
html.sa-js .sa-statsband-light .sa-reveal.is-inview .sa-stat-num,
html.sa-js .sa-climax .sa-stat-num.sa-reveal.is-inview { opacity: 1; transform: none; filter: none; }

/* Service rows cascade in from the left (container opts out; rows carry it) */
html.sa-js .sa-svc-rows.sa-reveal { opacity: 1; transform: none; transition: none; }
html.sa-js .sa-svc-rows .sa-svc-row { opacity: 0; transform: translateX(-56px); transition: opacity 0.6s var(--sa-ease-out), transform 0.8s var(--sa-ease-out); transition-delay: var(--sa-delay, 0s); }
html.sa-js .sa-svc-rows.is-inview .sa-svc-row { opacity: 1; transform: none; }

/* Dark-band checklist items cascade */
html.sa-js .sa-band-dark.sa-reveal .sa-checklist li { opacity: 0; transform: translateX(-32px); transition: opacity 0.55s var(--sa-ease-out), transform 0.7s var(--sa-ease-out); transition-delay: var(--sa-delay, 0s); }
html.sa-js .sa-band-dark.sa-reveal.is-inview .sa-checklist li { opacity: 1; transform: none; }

/* Fit-check: cards converge from opposite sides, items rise after */
html.sa-js .sa-fitcheck .wp-block-columns.sa-reveal { opacity: 1; transform: none; transition: none; }
html.sa-js .sa-fitcheck .wp-block-columns.sa-reveal .wp-block-column { opacity: 0; transition: opacity 0.7s var(--sa-ease-out), transform 0.9s var(--sa-ease-out); }
html.sa-js .sa-fitcheck .wp-block-columns.sa-reveal .wp-block-column:first-child { transform: translateX(-64px); }
html.sa-js .sa-fitcheck .wp-block-columns.sa-reveal .wp-block-column:last-child { transform: translateX(64px); }
html.sa-js .sa-fitcheck .wp-block-columns.sa-reveal.is-inview .wp-block-column { opacity: 1; transform: none; }
html.sa-js .sa-fitcheck .sa-fitlist li { opacity: 0; transform: translateY(16px); transition: opacity 0.5s var(--sa-ease-out), transform 0.6s var(--sa-ease-out); transition-delay: calc(0.25s + var(--sa-delay, 0s)); }
html.sa-js .sa-fitcheck .wp-block-columns.sa-reveal.is-inview .sa-fitlist li { opacity: 1; transform: none; }

/* Pull-quote: hairline draws down as it enters */
html.sa-js .sa-pullquote { border-left-color: transparent; background-image: linear-gradient(rgba(23,23,23,0.18), rgba(23,23,23,0.18)); background-repeat: no-repeat; background-position: left top; background-size: 1px 0%; transition: background-size 0.9s var(--sa-ease-out) 0.35s; }
html.sa-js .wp-block-columns.sa-reveal.is-inview .sa-pullquote { background-size: 1px 100%; }

/* Climax ghost numeral drifts slowly */
.sa-climax-ghost { animation: sa-ghost-drift 16s ease-in-out infinite alternate; }
@keyframes sa-ghost-drift { from { transform: translateY(-56%); } to { transform: translateY(-44%); } }

/* See-our-work arrow nudges forever */
.sa-work-link a::after { animation: sa-nudge 1.8s var(--sa-ease-out) infinite; }
@keyframes sa-nudge { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(6px); } }

/* Louder CTA button hover */
.sa-cta .wp-block-button__link:hover { transform: translateY(-3px) scale(1.03); }

/* Respect user motion preference: no transforms, everything visible immediately */
@media (prefers-reduced-motion: reduce) {
	html.sa-js .sa-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
	.sa-marquee__track { animation: none !important; }
	.sa-highlight { transition: none !important; background-size: 100% 100% !important; color: var(--wp--preset--color--white) !important; }
	.wp-block-button__link:hover, .sa-portfolio-row .wp-block-column:hover figure, .sa-blogthumb:hover img { transform: none !important; }
	.sa-svc-row, .sa-svc-detail, .sa-svc-row::after, .sa-work-link a::after { transition: none !important; }
	.sa-svc-detail { max-height: none !important; opacity: 1 !important; } /* no hover animation: show details */
	/* motion v2 states: fully visible, no animation (JS also skips splitting/counters) */
	html.sa-js .sa-split .sa-w,
	html.sa-js .sa-svc-rows .sa-svc-row,
	html.sa-js .sa-band-dark.sa-reveal .sa-checklist li,
	html.sa-js .sa-fitcheck .wp-block-columns.sa-reveal .wp-block-column,
	html.sa-js .sa-fitcheck .sa-fitlist li,
	html.sa-js .sa-statsband-light .sa-reveal .sa-stat-num,
	html.sa-js .sa-climax .sa-stat-num.sa-reveal { opacity: 1 !important; transform: none !important; filter: none !important; transition: none !important; }
	.sa-climax-ghost, .sa-work-link a::after { animation: none !important; }
	html.sa-js .sa-pullquote { border-left-color: rgba(23,23,23,0.18) !important; background-image: none !important; }
}

/* ============================================================
   BLOG (D4, 2026-07-23) — index (.sa-bloghub, home.html) +
   single post (.sa-post, single.html) + GET STARTED band.
   Values measured on live via getComputedStyle (blog + post pages).
   Scoped to blog classes/templates — nothing here repaints
   approved pages.
   ============================================================ */
/* (blog/single covered by the universal `main.wp-block-group` rule above) */

/* Index */
/* S3: level-agnostic + outranks `:root :where(h1)` (0,1,0), which would otherwise tie
   on specificity and be decided by stylesheet order. line-height pinned because
   theme.json's h1 sets 1.02 while every other level inherits 1.06 from `heading`. */
.sa-bloghub .sa-blogpagetitle { font-size: 50px; font-weight: 300; letter-spacing: -0.02em; line-height: 1.06; }
.sa-blogsearch { max-width: 390px; margin: 28px auto 0; }
.sa-blogsearch .wp-block-search__inside-wrapper { border: 1px solid rgba(23,23,23,0.22); border-radius: 10px; background: #fff; padding: 4px 6px; }
.sa-blogsearch input.wp-block-search__input { border: none; background: transparent; font-size: 14px; padding: 8px 10px; outline: none; }
.sa-blogsearch button.wp-block-search__button { background: transparent; border: none; color: rgba(23,23,23,0.62); padding: 0 6px; }
.sa-bloghub .sa-blogintro { font-size: clamp(1.4rem, 2.3vw, 32px); line-height: 1.2; font-weight: 300; margin-top: 56px; letter-spacing: -0.01em; }
.sa-bloghub .sa-bloggrid { margin-top: 56px; }

/* Card grid (index + Other posts) */
.sa-bloggrid .wp-block-post-template { gap: 40px 32px; }
.sa-bloggrid .sa-blogthumb img { width: 100%; border-radius: 8px; } /* width:100% = sizes=auto gotcha */
.sa-bloggrid .sa-blogdate { font-family: var(--wp--preset--font-family--body); font-size: 14px; color: #4d4d4d; margin: 14px 0 6px; }
/* S3: was `h6.sa-blogtitle`. The element qualifier had to go — the cards are now h2 on
   the index and h3 under "Other posts", and an element-qualified rule would have
   dropped every one of these values on the floor. All four typography properties are
   pinned here precisely so the card renders identically at any heading level. */
.sa-bloggrid .sa-blogtitle { font-size: 24px; font-weight: 500; line-height: 1.5; letter-spacing: -0.01em; margin: 0; text-transform: none; } /* parent theme uppercases h6 — live is sentence case */
.sa-bloggrid .sa-blogtitle a { text-decoration: none; color: inherit; }
.sa-bloggrid .sa-blogtitle a:hover { color: var(--wp--preset--color--brand); }

/* Single post */
.sa-post .sa-post-title { font-size: clamp(2rem, 3.4vw, 48px); font-weight: 500; line-height: 1.05; letter-spacing: -0.02em; }
.sa-post .sa-postdate { font-family: var(--wp--preset--font-family--body); font-size: 14px; color: #6B6B6B; margin-top: 14px; }
.sa-post .sa-post-hero { margin-top: 32px; }
.sa-post .sa-post-hero img { width: 100%; height: 320px; object-fit: cover; border-radius: 8px; }
.sa-post .wp-block-post-content p { font-size: 18px; line-height: 1.6; color: #666666; }
.sa-post .wp-block-post-content h2 { font-size: clamp(1.8rem, 3.5vw, 50px); font-weight: 300; letter-spacing: -0.02em; margin-top: 48px; margin-bottom: 16px; }
/* S3: letter-spacing pinned. This was an h3 (inheriting -0.02em from theme.json's
   `heading`) and is now an h2, whose element style sets -0.025em. */
.sa-otherposts .sa-otherposts-title { font-size: 24px; font-weight: 500; letter-spacing: -0.02em; margin-bottom: 28px; }

/* GET STARTED band (blog templates) */
.sa-getstarted { position: relative; overflow: hidden; background: var(--wp--preset--color--brand); border-radius: 24px; margin-left: 24px; margin-right: 24px; min-height: 300px; display: flex; flex-direction: column; justify-content: center; }
.sa-getstarted .sa-gs-ball { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -42%); width: min(640px, 80vw); margin: 0; pointer-events: none; }
.sa-getstarted .sa-gs-ball img { width: 100%; height: auto; }
.sa-getstarted .sa-gs-title, .sa-getstarted .sa-gs-sub { position: relative; z-index: 1; color: #fff; margin: 0; }
.sa-getstarted .sa-gs-title { font-size: 28px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em; }
.sa-getstarted .sa-gs-sub { font-size: 16px; margin-top: 6px; }

/* S3: visually hidden, still read by screen readers and parsed by crawlers. Used for
   the /linktree H1 — that page is deliberately chrome-less and has no visible text
   heading at all, and G signed off its exact measured proportions at U6, so the
   structural H1 must add zero pixels. */
.sa-sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 781px) {
  .sa-bloghub .sa-blogpagetitle { font-size: 36px; }
  .sa-bloggrid .wp-block-post-template { grid-template-columns: 1fr; }
  .sa-post .sa-post-hero img { height: 220px; }
  .sa-getstarted { margin-left: 10px; margin-right: 10px; }
}

/* ============================================================
   CONTACT (D5, 2026-07-23) — form card + get-in-touch column.
   Restyles Fluent Forms (form id 3) to the live card design.
   Scoped under .sa-contact / .sa-form-card only.
   ============================================================ */
.sa-form-card .fluentform { margin-top: 28px; }
.sa-form-card form { display: block; }
.sa-form-card form > fieldset { display: grid; grid-template-columns: 1fr 1fr; gap: 22px 24px; border: 0; padding: 0; margin: 0; min-width: 0; } /* FF nests every field in ONE fieldset — grid lives there */
.sa-form-card .ff-el-group { margin-bottom: 0; }
/* P4 (2026-07-26): enabling FF's honeypot injects a hidden `.ff-hpsf-container` as the
   FIRST child of the fieldset, which shifted the old `:nth-of-type(n+3)` counting and
   pushed Email to full width, breaking the desktop 2-column pair. Target the fields
   themselves instead of their index, so any hidden field FF adds cannot move the layout. */
.sa-form-card form > fieldset > .ff-el-group, .sa-form-card .ff_submit_btn_wrapper { grid-column: 1 / -1; }
.sa-form-card form > fieldset > .ff-el-group:has(input[name="full_name"]),
.sa-form-card form > fieldset > .ff-el-group:has(input[name="email"]) { grid-column: auto; }
.sa-form-card form > fieldset > .ff-hpsf-container { display: none !important; }
.sa-form-card .ff-el-input--label label { font-family: var(--wp--preset--font-family--mono); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(23,23,23,0.62); font-weight: 400; }
.sa-form-card .ff-el-form-control { background: #F1F1F1; border: 1px solid transparent; border-radius: 10px; padding: 13px 16px; font-size: 15px; color: var(--wp--preset--color--ink); width: 100%; box-shadow: none; }
.sa-form-card .ff-el-form-control:focus { border-color: var(--wp--preset--color--sky); background: #fff; outline: none; box-shadow: none; }
.sa-form-card select.ff-el-form-control { appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23171717' stroke-width='1.5'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 16px center; }
.sa-form-card textarea.ff-el-form-control { min-height: 140px; resize: vertical; }
.sa-form-card .ff-btn-submit { width: 100%; background: var(--wp--preset--color--brand) !important; color: #fff !important; border: none; border-radius: 12px; padding: 15px 24px; font-size: 15px; font-weight: 500; cursor: pointer; transition: background 0.25s var(--sa-ease-out), transform 0.25s var(--sa-ease-out); }
.sa-form-card .ff-btn-submit::after { content: " →"; }
.sa-form-card .ff-btn-submit:hover { background: var(--wp--preset--color--brand-2) !important; transform: translateY(-1px); }
.sa-form-card .ff-message-success { font-size: 15px; border: none; box-shadow: none; padding: 14px 0 0; color: var(--wp--preset--color--brand); }

.sa-contact .sa-ct-row { border-bottom: 1px solid rgba(23,23,23,0.09); padding-bottom: 18px; margin-bottom: 18px; }
.sa-contact .sa-ct-label { font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(23,23,23,0.62); margin: 0 0 6px; }
.sa-contact .sa-ct-value { font-size: 17px; margin: 0; }
.sa-contact .sa-ct-value a { color: inherit; text-decoration: none; }
.sa-contact .sa-ct-value a:hover { color: var(--wp--preset--color--brand); }
.sa-contact .sa-expect-card .sa-checklist { margin-top: 18px; }

/* U2 (2026-07-24): required fields need a VISIBLE indicator. Fluent Forms marks the
   label wrapper `.ff-el-is-required` but our restyle left no asterisk rendering, so
   4 required fields looked optional. */
.sa-form-card .ff-el-input--label.ff-el-is-required > label::after {
  content: "*"; color: var(--wp--preset--color--sky); margin-left: 4px; font-weight: 600;
}

@media (max-width: 781px) {
  /* U2 (2026-07-24): the grid lives on `form > fieldset` (FF nests every field in ONE
     fieldset), NOT on `form` — the old rule targeted `form` so it never applied and
     name/email stayed in two 113px columns that clipped their placeholders. */
  .sa-form-card form > fieldset { grid-template-columns: 1fr; }
  .sa-form-card { padding: 28px 22px !important; }
}

/* ============================================================
   CASE STUDIES (D6, 2026-07-23) — .sa-case page skeleton +
   .sa-carousel module (shared later by home D7 + /lp).
   ============================================================ */
.sa-case .wp-block-image img, .sa-case .wp-block-gallery img { width: 100%; height: auto; display: block; } /* sizes=auto gotcha */
/* S3b 2026-07-26: this class now lands on BOTH a <p> (the client/Our Work/timeline
   META labels, which are field labels paired with a value, not document sections) and
   an <h2> (the real section headings, which carry sa-reveal). Two consequences:
   (1) the doubled class raises specificity to (0,2,0) — as a single class it TIED with
       `:root :where(h2)` (0,1,0), so theme.json's heading font-size (up to 4rem) could
       have beaten 11px purely on stylesheet order;
   (2) line-height is pinned, because a heading inherits 1.06 from theme.json's
       `heading` element while a <p> falls back to the body line-height.
   Both verified by computed-style diff, not assumed. */
.sa-case-label.sa-case-label { font-family: var(--wp--preset--font-family--mono); font-size: 11px; line-height: 1.06; letter-spacing: 0.16em; text-transform: uppercase; color: rgba(23,23,23,0.62); font-weight: 400; margin: 0; } /* U3/S6 2026-07-25: 0.55 composited to #797979 on #f1f1f1 = 3.85:1, failing WCAG AA for 11px text. 0.62 -> ~#6B6B6B = 4.7:1. Reduced-motion users always saw this; the 3D route merely masked it from the audit. */
/* S3b: margin-bottom was `10px` here, but it had NEVER applied to the section headings —
   a WP heading margin reset outranked the old single-class selector. Raising specificity
   (above) let the 10px through for the first time and grew every case page by 10-50px,
   caught by the computed-style diff. The observed value is 0, so 0 is what is written. */
.sa-case-meta { border-top: 1px solid rgba(23,23,23,0.12); border-bottom: 1px solid rgba(23,23,23,0.12); padding: 22px 0; }
.sa-case-meta .sa-case-label.sa-case-label { margin-bottom: 6px; } /* (0,3,0) so it beats the doubled base rule by specificity, not by source order */
.sa-case-meta p:not(.sa-case-label) { margin: 0; font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.02em; }
.sa-case-intro h1 { font-size: clamp(1.6rem, 2.6vw, 34px); font-weight: 500; line-height: 1.25; letter-spacing: -0.01em; text-transform: uppercase; }

/* Carousel module: group.sa-carousel > gallery.sa-carousel__track (+ JS nav) */
.sa-carousel { position: relative; }
.sa-carousel .sa-carousel__track { display: flex !important; flex-wrap: nowrap !important; overflow-x: auto; scroll-snap-type: x mandatory; gap: 16px !important; margin: 0; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.sa-carousel .sa-carousel__track::-webkit-scrollbar { display: none; }
.sa-carousel .sa-carousel__track > figure, .sa-carousel .sa-carousel__track > .wp-block-image, .sa-carousel .sa-carousel__track > .sa-pcard { flex: 0 0 auto; width: calc((100% - 32px) / 3); scroll-snap-align: start; margin: 0 !important; }
.sa-carousel img { border-radius: 8px; }
.sa-carousel__nav { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; }
.sa-carousel__btn { width: 46px; height: 36px; border: 1px solid rgba(23,23,23,0.25); border-radius: 100px; background: transparent; color: var(--wp--preset--color--ink); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; transition: background 0.2s var(--sa-ease-out), color 0.2s var(--sa-ease-out); }
.sa-carousel__btn:hover:not(:disabled) { background: var(--wp--preset--color--ink); color: #fff; }
.sa-carousel__btn:disabled { opacity: 0.3; cursor: default; }
.sa-carousel:focus-visible { outline: 2px solid var(--wp--preset--color--brand); outline-offset: 4px; }

@media (max-width: 781px) {
  .sa-carousel .sa-carousel__track > figure, .sa-carousel .sa-carousel__track > .wp-block-image, .sa-carousel .sa-carousel__track > .sa-pcard { width: 78%; }
}

/* v2.3 (D7, 2026-07-24): home portfolio now uses the shared .sa-carousel module.
   Slides are image+label card groups (.sa-pcard), not bare figures — keep the
   sizes=auto width fix and the image→label gap the static grid had. The 16px
   inline border-radius on each card image overrides the module's 8px default. */
.sa-portfolio .sa-pcard img { width: 100%; height: auto; }
.sa-portfolio .sa-pcard figure { margin: 0 0 0.75rem; }

/* ============================================================
   LINKTREE (D9, 2026-07-24) — chrome-less link hub (template
   page-linktree.html). Live design: light grey stage, brand
   wordmark + ball, icon row, 3 big blue card buttons.
   ============================================================ */
/* U6 (2026-07-25, G-approved): the live page carries a large soft organic swirl behind
   everything. It was skipped at D9 because the source is SVG and project policy is
   convert-to-PNG/WebP, not SVG upload. Harvested from the frozen reference, rendered at 2x
   with transparency and shipped as WebP (media 290). Decorative only -> painted here as a
   background layer rather than added to the block content, so it stays out of G's editor. */
body.page-linktree, .sa-linktree-main { background: #ECECEC; }
/* NOTE: there is NO `page-linktree` body class — WP emits `page-id-269`. The template
   wrapper `.sa-linktree-main` is the reliable hook, so the artwork hangs off that. */
.sa-linktree-main { position: relative; }
.sa-linktree-main::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image: url("/wp-content/uploads/2026/07/lt-organic@2x.webp");
  background-repeat: no-repeat; background-position: center 45%; background-size: 190% auto;
}
.sa-linktree-main > * { position: relative; z-index: 1; }
.sa-lt { max-width: 900px; margin-inline: auto; padding: 64px 24px 96px; }
.sa-lt-wordmark { --sa-lt-logo: min(697px, 56vw); display: flex; align-items: center; justify-content: center; gap: calc(var(--sa-lt-logo) * 0.026); margin: 0; color: var(--wp--preset--color--brand); }
/* 2026-11-10: real logo + sphere, sized like Framer's /linktree (logo 697px wide at 1440,
   sphere = 0.178 of the logo width, 18px gap). */
.sa-lt-wordmark .sa-logo { width: var(--sa-lt-logo); flex: none; }
/* U6 sizing (2026-07-26, G: "match the live sizes"). Measured on live @1440:
   wordmark ball 150x122, button art 540x439 (NOT square), button min-height 300,
   chat icon 65x66. @390: ball 186x151, wordmark ball 59x48. */
.sa-lt-wordmark img { width: calc(var(--sa-lt-logo) * 0.178); height: auto; flex: none; }
.sa-lt-icons { display: flex; justify-content: center; gap: 22px; margin-top: 28px; }
/* U6 (2026-07-25, G): live's social icons are BRAND BLUE, not ink. Sampled from the live
   render as rgb(0,33,255) — the exact same value as the button fill. */
.sa-lt-icons a, .sa-lt-icons span { color: var(--wp--preset--color--brand); display: inline-flex; width: 22px; height: 22px; }
.sa-lt-icons svg { width: 100%; height: 100%; }
.sa-lt-icons a:hover { color: var(--wp--preset--color--ink); }
/* U6: the middle button carries the S-ribbon on live (media 289, same harvest/convert
   route as the background). Decorative -> CSS layer, keeps the block content clean. */
.sa-lt-btn.sa-lt-ribbon::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: url("/wp-content/uploads/2026/07/lt-ribbon-crop@2x.webp");
  background-repeat: no-repeat; background-position: left 2% center; background-size: auto 256%; opacity: 0.85;
}
.sa-lt-btn { position: relative; display: flex; align-items: center; min-height: 300px; border-radius: 28px; background: var(--wp--preset--color--brand); overflow: hidden; margin-top: 40px; text-decoration: none; padding: 32px 48px; transition: transform 0.3s var(--sa-ease-out), box-shadow 0.3s var(--sa-ease-out); }
.sa-lt-btn:hover { transform: translateY(-4px); box-shadow: 0 18px 48px rgba(0,33,255,0.28); }
.sa-lt-btn .sa-lt-label { position: relative; z-index: 1; color: #fff; font-family: var(--wp--preset--font-family--display); font-weight: 600; font-size: clamp(2rem, 5vw, 64px); margin: 0; display: flex; align-items: center; gap: 0.35em; }
.sa-lt-btn.sa-lt-right { justify-content: flex-end; }
.sa-lt-btn.sa-lt-center { justify-content: center; }
.sa-lt-btn .sa-lt-art { position: absolute; margin: 0; pointer-events: none; }
.sa-lt-btn .sa-lt-art img { width: 540px; height: 439px; object-fit: contain; }
.sa-lt-art--right { right: -40px; top: 50%; transform: translateY(-38%); }
.sa-lt-art--left { left: -50px; bottom: -90px; }
.sa-lt-label img { width: 1.02em; height: 1.03em; object-fit: contain; }
@media (max-width: 781px) {
  .sa-lt { padding-top: 40px; }
  .sa-lt-btn { min-height: 186px; padding: 24px 28px; }
  .sa-lt-btn .sa-lt-art img { width: 186px; height: 151px; }
}
/* whole-card click without wp:html buttons: the label's anchor stretches over the card */
.sa-lt-btn .sa-lt-label a { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 0.35em; position: static; }
.sa-lt-btn .sa-lt-label a::after { content: ""; position: absolute; inset: 0; }
/* constrained layout centres the label paragraphs — kill that so the card's
   flex justification (left / right / center per live) wins */
.sa-lt-btn > .sa-lt-label, .sa-lt-btn > .sa-lt-art { margin-inline: 0 !important; }

/* ---- Hero sound toggle (2026-07-27) ---- */
.sa-hero { position: relative; }

/* Mobile hero height — G, 2026-07-27: "the video is cutting off part of the subtitle".
   The cover is `min-height:78vh` INLINE on the block, and object-fit:cover crops the
   axis the container over-fills. On a 390x812 phone that is 390/597 = 0.65, and with a
   16:9 source only (0.65 x 0.5625) = 37% of the frame width survives.
   Measured against the actual file (44 frames sampled at 5fps, three subtitle lines:
   "Tired of managing", "your marketing alone?", "make it simple."), the widest line
   spans x 27.5%-72.5% of the frame — it needs ~48% of the width, so 37% guaranteed
   both ends were sliced off. Visible width = (W/H) x 0.5625, so W/H must be >= 0.85;
   a near-square hero gives 58.6% and leaves the subtitle ~35px of clear air each side.
   `min()` keeps it sane on tall/short devices. !important is unavoidable: the 78vh is
   an inline style and covers have no responsive min-height in the block editor. */
@media (max-width: 781px) {
	body.home .sa-hero:not(.sa-hero--ball) { min-height: min(96vw, 62vh) !important; }
}
/* Desktop hero height — G, 2026-07-29: same bug, opposite axis. When the hero is WIDER
   than 16:9 the cover crops the HEIGHT instead, and the subtitle sits low in the frame
   (measured y 83.4%-88.7%), so it is the first thing the bottom crop eats. On G's
   1994x1056 window the hero was 1979x824 = 2.40:1, leaving the band y 13%-87% — the
   subtitle clipped by 1.7% of frame height, exactly the reported half-cut line.
   Visible height = (H/W) x 16/9, so keeping the band's lower edge past 88.7% needs
   H >= 0.4724 x W; 47.5vw is that with a little air. `min(..., 92vh)` stops the hero
   ever exceeding the viewport, and the outer `max(78vh, ...)` means this only ever
   RAISES the height — below ~1.64:1 (so 1440x900, 1280x800) 78vh still wins and the
   rendering is byte-for-byte what it was. Scoped >=782px so it cannot reach the mobile
   rule above, where the crop is horizontal and this axis is not cropped at all.
   KNOWN LIMIT: on an ultrawide-and-short window (2560x1000, 3440x1180) no `cover`
   value can work — the required height exceeds the viewport itself. Fixing that would
   mean dropping the hero out of full-bleed, which is a design change, not a fix. */
@media (min-width: 782px) {
	body.home .sa-hero:not(.sa-hero--ball) { min-height: max(78vh, min(47.5vw, 92vh)) !important; }
}
/* Ball hero — G, 2026-11-09: home hero restored to the Framer original (blue 3D ball
   video, two-line headline). The two rules above size the SUBTITLED character video and
   stay with it (it lives on /home-2 now), so they skip `.sa-hero--ball`. Measured on
   live Framer: the hero is 100vh at >=810px wide (inline min-height on the block) and a
   fixed 844px below that; background #3388FF shows until the video's first frame. */
.sa-hero--ball { background-color: #3388ff; }
@media (max-width: 809.98px) {
	.sa-hero--ball { min-height: 844px !important; }
}
/* Side margins as Framer: 20px on phones (inline on the block), 60px on tablets, 130px
   on desktop — they decide where the headline wraps (e.g. two lines at 1024px). */
@media (min-width: 810px) and (max-width: 1199.98px) {
	.sa-hero--ball { padding-left: 60px !important; padding-right: 60px !important; }
}
@media (min-width: 1200px) {
	.sa-hero--ball { padding-left: 130px !important; padding-right: 130px !important; }
}
.sa-sound {
	position: absolute;
	right: clamp(16px, 3vw, 40px);
	bottom: clamp(16px, 3vw, 40px);
	z-index: 5;
	display: inline-flex;
	align-items: center;
	gap: 0.55em;
	padding: 0.55em 1.05em;
	border: 1px solid rgba(255, 255, 255, 0.5);
	border-radius: 100px;
	background: rgba(23, 23, 23, 0.28);
	-webkit-backdrop-filter: blur(6px);
	backdrop-filter: blur(6px);
	color: #fff;
	font-family: var(--wp--preset--font-family--mono);
	font-size: 11px;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	cursor: pointer;
	transition: background 0.25s var(--sa-ease-out), border-color 0.25s var(--sa-ease-out);
}
.sa-sound:hover { background: rgba(23, 23, 23, 0.45); border-color: #fff; }
.sa-sound:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
/* speaker glyph, drawn in CSS so no icon file or inline SVG is needed */
.sa-sound-ico {
	width: 9px; height: 9px;
	border-left: 3px solid currentColor;
	border-top: 3px solid transparent;
	border-bottom: 3px solid transparent;
	position: relative;
	flex: none;
}
.sa-sound-ico::after {
	content: "";
	position: absolute;
	left: 3px; top: 50%;
	width: 7px; height: 1px;
	background: currentColor;
	transform: translateY(-50%) rotate(-42deg);
	transform-origin: left center;
	transition: opacity 0.2s;
}
.sa-sound.is-on .sa-sound-ico::after { opacity: 0; }
.sa-sound.is-on { background: var(--wp--preset--color--brand); border-color: var(--wp--preset--color--brand); }


/* ---- Cookie consent banner (2026-09-24, G: "Apollo tracker: Keep it") ----
   Sits ABOVE the fixed pill bar (bar = 16px gap + ~43px tall), never over it: the
   expanded desktop bar spans most of the width, so a bottom-anchored card would cover
   pills. Accept and Decline carry equal weight (ICO: rejecting must be as easy as
   accepting). Injected by assets/consent.js; hidden via [hidden]. */
.sa-consent {
	position: fixed;
	z-index: 95;
	left: 16px;
	bottom: 76px;
	max-width: 340px;
	padding: 16px 18px;
	border-radius: 18px;
	background: rgba(255, 255, 255, 0.94);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	box-shadow: 0 8px 28px rgba(23, 23, 23, 0.14);
	color: var(--wp--preset--color--ink);
	font-size: 0.8rem;
	line-height: 1.5;
}
.sa-consent[hidden] { display: none; }
.sa-consent__text { margin: 0 0 12px; }
.sa-consent__text a { color: var(--wp--preset--color--brand); }
.sa-consent__actions { display: flex; gap: 8px; }
.sa-consent__btn {
	flex: 1 1 0;
	font: inherit;
	font-weight: 500;
	padding: 0.55em 1.1em;
	border-radius: 100px;
	border: 1px solid var(--wp--preset--color--brand);
	background: transparent;
	color: var(--wp--preset--color--brand);
	cursor: pointer;
}
.sa-consent__btn--accept { background: var(--wp--preset--color--brand); color: var(--wp--preset--color--white); }
.sa-consent__btn:focus-visible { outline: 2px solid var(--wp--preset--color--brand); outline-offset: 2px; }
@media (max-width: 781px) {
	.sa-consent { left: 12px; right: 12px; max-width: none; }
}
/* Same anchor as the pill bar: iOS Safari measures `bottom` from the LAYOUT viewport,
   which runs behind its toolbar, while the bar rides the VISUAL viewport via dvh. Left
   on `bottom`, the banner would slide into the bar on a real iPhone. */
@supports (height: 100dvh) {
	.sa-consent { top: calc(100dvh - 76px); bottom: auto; transform: translateY(-100%); }
}
