/* Secure E-BIM Engineering — "Fieldwork" visual toolkit, v4.
   Reset after two rounds of feedback ("too busy", "still feels like a
   template", "colours feel generic"). Direction: a deliberate mix of just
   two references — axiom-power (dramatic real photography, bold single
   accent, full-bleed colour blocks) and m3-pg.de (huge mixed-weight
   editorial type, lots of quiet whitespace, almost no ornament). Dropped
   entirely from the previous version: the gradient-mesh/grain hero, the
   generated wireframe graphic, the monospace bracket tags, the corner
   brackets, the ticker line, the floating pill header, the scroll-progress
   rail. Warm ink/paper/amber palette instead of navy/cyan (brand_tokens.json).
   Sits on top of Ollie's colour + type tokens. */

:root {
	--sb-primary: var(--wp--preset--color--primary);
	--sb-ink: var(--wp--preset--color--primary-alt);
	--sb-ink-text: var(--wp--preset--color--primary-alt-accent);
	--sb-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---------------------------------------------------------------------
   Page load-in — every page fade-in-on-load, not just the homepage's
   once-per-session curtain (that one's a deliberate one-time first-
   visit moment; this is the plain "the page didn't just pop in" touch
   asked for on every navigation, including revisits). Opacity only, no
   transform — a transform here would create a new containing block for
   the whole page including the homepage's pinned scroll-hero, and that
   hero's sticky-positioning timing has broken from far smaller changes
   than that before. securebim-reveal.js adds .sb-nav-in once the DOM is
   ready; the <noscript> rule in bootstrap.php (sb_page_load_in_noscript)
   is the safety net if JS never runs. */
body { opacity: 0; transition: opacity 0.5s var(--sb-ease); }
html.sb-nav-in body { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
	body { opacity: 1; transition: none; }
}

/* ---------------------------------------------------------------------
   Header — plain flat bar. No pill, no blur, no gimmicks.
   --------------------------------------------------------------------- */
/* The sticky positioning has to live on Ollie's own outer template-part
   wrapper (<header class="site-header wp-block-template-part">), not on
   our inner .sb-header group: that outer wrapper is exactly as tall as
   its content, so putting position:sticky on the inner element leaves it
   zero room to actually stick — it just scrolls away immediately. */
header.wp-block-template-part {
	position: sticky;
	top: 0;
	z-index: 500;
}
.sb-header {
	background: var(--wp--preset--color--base);
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sb-header .wp-block-navigation-item a {
	font-weight: 600;
	font-size: 1rem;
	letter-spacing: 0.01em;
	text-decoration: none;
}
.sb-header .wp-block-navigation-item a:hover,
.sb-header .wp-block-navigation-item a:focus {
	color: var(--sb-primary);
	text-decoration: none;
}
.sb-header .wp-block-navigation__container { gap: 2.1rem !important; }
/* Custom crop of the source logo (in/images/logo-text-transparent.png):
   the arch/people icon above "SECURE" is cropped off, background
   stripped to transparent, text (both "SECURE" and the small "BIM
   ENGINEERING" line beneath it) untouched, single combined image.
   Sharp alpha cutoff + a bbox computed on a binarized mask (not raw
   alpha) so compression haze from the JPEG source can't drag the crop's
   bounding box past the visible letterforms. Header padding stays
   near-zero (0.35rem, from the earlier "don't grow the header"
   request). A later attempt to enlarge just the "BIM ENGINEERING" line
   (as its own upscaled raster, then as separate live CSS text) caused
   more problems than it solved across several rounds of feedback —
   reverted back to this single small-subtitle image per direct request. */
.sb-header img.custom-logo { max-height: 60px; width: auto; }
/* WordPress's nav-block "mobile" overlay only switches to the hamburger
   below a fixed ~600px viewport width — it isn't aware of how much room
   the logo actually takes up next to it. Between 600px and ~900px the
   full 5-link nav doesn't fit next to the logo, and the header's own
   flex-wrap (kept for graceful degradation) silently drops the nav onto
   its own full-width second row instead of erroring — a broken-looking
   stacked header, not an intentional design. Rather than touch the
   desktop sizing, shrink just the nav footprint in that one dead zone
   so everything still fits on a single row. */
@media (min-width: 600px) and (max-width: 900px) {
	.sb-header .wp-block-navigation__container { gap: 1.1rem !important; }
	.sb-header .wp-block-navigation-item a { font-size: 0.85rem; }
	.sb-header img.custom-logo { max-height: 44px; }
}

/* ---------------------------------------------------------------------
   Eyebrow — plain small-caps label, no brackets.
   --------------------------------------------------------------------- */
.sb-eyebrow {
	display: inline-block;
	font-size: 0.8rem;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--sb-primary);
}

/* Ollie's own heading styles set `hyphens: auto` on h1–h6. Harmless at
   wide widths, but on a narrow mobile column with a big display font it
   was cutting words mid-syllable — "Engineering" -> "Engine-/ring",
   "modellering" -> "modelle-/ring" — reading as broken, not stylistic.
   Turned off site-wide for headings; overflow-wrap stays as the safety
   net for a genuinely unbreakable single word. */
h1, h2, h3, h4, h5, h6 { hyphens: none; }

/* ---------------------------------------------------------------------
   Mixed-weight display heading: <strong> full ink/white, plain text muted.
   --------------------------------------------------------------------- */
.sb-display {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 500;
	line-height: 1.02;
	letter-spacing: -0.01em;
}
.sb-display strong { font-weight: 700; }

/* Word-mask reveal: each word rises out of a hidden "slot" with a slight
   blur-to-sharp settle, staggered via the inline animation-delay set per
   word in sb_display(). Extra bottom padding + matching negative margin
   on the mask stops descenders (g, j, p, q, y) getting clipped by the
   overflow:hidden box. */
.sb-wordmask {
	display: inline-block;
	overflow: hidden;
	vertical-align: top;
	padding-bottom: 0.14em;
	margin-bottom: -0.14em;
}
.sb-word {
	display: inline-block;
	opacity: 0;
	transform: translateY(115%);
	filter: blur(5px);
}
/* The animation itself is only attached once .sb-ready lands on <html>
   (securebim-hero.js — right away if there's no curtain or it's already
   been seen this session, otherwise once the curtain finishes lifting).
   Tried gating this with animation-play-state: paused/running instead
   (simpler-looking) first, but measured it not reliably honouring the
   per-word animation-delay after resuming — words stayed stuck at their
   0% frame well past when they should have finished. Only actually
   creating the animation when .sb-ready appears sidesteps that: each
   word's delay then counts from a fresh start. */
html.sb-ready .sb-word { animation: sb-word-in 0.9s var(--sb-ease) both; }
@keyframes sb-word-in {
	to { opacity: 1; transform: translateY(0); filter: blur(0); }
}
/* The heading now animates word-by-word — turn off the whole-block
   fade/rise/blur it would otherwise inherit from .sb-load-in > *, so the
   two effects don't stack. */
.sb-load-in > .sb-display,
html.sb-ready .sb-load-in > .sb-display { animation: none; opacity: 1; transform: none; filter: none; }
.sb-display--light { color: rgba(245, 241, 234, 0.7); }
.sb-display--light strong { color: var(--sb-ink-text); }
.sb-display--dark { color: rgba(22, 21, 18, 0.72); }
.sb-display--dark strong { color: var(--wp--preset--color--main); }

/* Typewriter reveal — text materialises character by character over a
   few seconds. JS-scheduled (securebim-hero.js), not CSS animation-delay
   — see the long comment on sb_typewriter() in page-builder.php for why.
   Characters are visible by default (no opacity rule here) so the text
   reads fine with JS off; the JS itself adds .sb-typewriter--active
   (hiding everything) right before it starts revealing, only once it's
   actually going to run the sequence. */
.sb-typewriter--active .sb-char { opacity: 0; }
.sb-typewriter--active .sb-char.sb-char--in { opacity: 1; }


/* The paragraph now reveals itself character by character — same reason
   as the heading above, turn off the whole-block fade/rise it would
   otherwise inherit from .sb-load-in > *. */
.sb-load-in > .sb-has-typewriter,
html.sb-ready .sb-load-in > .sb-has-typewriter { animation: none; opacity: 1; transform: none; filter: none; }

/* ---------------------------------------------------------------------
   Photo hero — full-bleed real photo, dark overlay, content anchored to
   the bottom. Nod to axiom-power.
   --------------------------------------------------------------------- */
.sb-photo-hero {
	position: relative;
	min-height: min(84vh, 760px);
	display: flex;
	align-items: flex-end;
	overflow: hidden;
}
.sb-photo-hero__img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.sb-photo-hero__overlay {
	position: absolute;
	inset: 0;
	background: linear-gradient(0deg, rgba(20, 19, 17, 0.92) 0%, rgba(20, 19, 17, 0.55) 42%, rgba(20, 19, 17, 0.15) 75%);
}
.sb-photo-hero__content {
	position: relative;
	z-index: 1;
	width: 100%;
	padding: 3rem 2rem clamp(3rem, 7vw, 5rem);
}
.sb-photo-hero__inner { max-width: 1320px; margin: 0 auto; }
.sb-photo-hero .sb-display { font-size: clamp(2.4rem, 6vw, 4.5rem); }
.sb-photo-hero__sub {
	color: rgba(245, 241, 234, 0.85);
	font-size: 1.1rem;
	max-width: 640px;
	margin-top: 1.25rem;
}

/* ---------------------------------------------------------------------
   Pinned split hero — two panels side by side, pinned while the reader
   scrolls: the light panel (our paper) shrinks from ~70% to 0% width,
   the dark panel (ink) grows from ~30% to 100% and ends up filling the
   screen, then normal page scroll continues. Nod to mistral.ai's hero
   transition; original code, Secure's own copy/colours — light gives way
   to dark instead of colour-mosaic taking over. Driven by
   assets/js/securebim-hero.js; degrades to a static stacked two-part hero
   under reduced-motion or on narrow screens (the horizontal split doesn't
   make sense once panels are only ~120px wide).
   --------------------------------------------------------------------- */
.sb-mstack { position: relative; }
.sb-mstack__stage {
	/* Sticky offset matched to the real header height (set on <html> by
	   securebim-hero.js) instead of 0 — otherwise the stage's natural
	   resting position (right below the sticky header) doesn't match its
	   own sticky threshold, so the first scroll has to close that gap as
	   plain unpinned scrolling before sticky pinning (and the split
	   animation) actually engages.
	   Height MUST shrink by exactly the header height, not stay a full
	   vh100 — a sticky element can't stick past where its own containing
	   block (.sb-mstack) ends, i.e. it needs `top + height` worth of
	   clearance from the parent's bottom edge. With top raised to the
	   header height, keeping height at a full vh100 pushes that required
	   clearance to `header height + vh100`, which is `header height` more
	   than the .sb-mstack/.sb-cover-sheet timing math (calibrated for
	   exactly one vh100 of clearance) actually provides — measured this
	   directly: the stage's stuck position held at `top: header-height`
	   right up to scroll≈2700, then *jumped* straight to `top: 0` instead
	   of continuing smoothly to the intended release point at scroll≈2772,
	   opening a gap where the cover-sheet hadn't arrived yet but the hero
	   was no longer properly pinned. Shrinking the height back down by the
	   header height restores `top + height = vh100` exactly, which is what
	   keeps the stuck position — and therefore the release point — exactly
	   in sync with the cover-sheet's own arrival. */
	position: sticky;
	top: var(--sb-header-h, 0px);
	height: calc(var(--sb-vh100, 100vh) - var(--sb-header-h, 0px));
	display: flex;
	overflow: hidden;
	/* Perf: scopes layout/paint recalculation to this subtree. The panel
	   split (below) changes flex-basis every scroll frame, which forces a
	   layout pass no matter what — `contain` at least stops the browser
	   from having to consider whether that change could affect anything
	   OUTSIDE this box (it can't: fixed height, sticky, overflow hidden),
	   keeping each frame's recalculation cheap and local. */
	contain: layout paint;
}
.sb-mstack__left,
.sb-mstack__right {
	box-sizing: border-box;
	overflow: hidden;
	display: flex;
	align-items: center;
	position: relative;
	min-width: 0;
}
.sb-mstack__left {
	background: var(--wp--preset--color--base);
	flex: 0 0 var(--sb-split-left, 68%);
	max-width: var(--sb-split-left, 68%);
}
.sb-mstack__right {
	background: var(--sb-ink);
	flex: 0 0 var(--sb-split-right, 32%);
	max-width: var(--sb-split-right, 32%);
	flex-direction: column;
	justify-content: center;
	text-align: center;
}
/* Small corner indicator instead of a giant background numeral (three
   "boring" rounds — a breathing glow, then a breathing giant number, then
   the giant number counting on its own loop — all still read as passive
   decoration at that scale). Now a compact "ONZE AANPAK — 0 / 475+" bar
   pinned to the top of the dark panel, echo of the reference's "01 / 04"
   corner tag. The counting part (data-sb-ghost-num) still counts from 0
   up to the real "475+" in lockstep with the reader's own scroll through
   the pinned hero (updateStack() in securebim-hero.js, reusing the same
   splitProgress already driving the panel split — no separate scroll
   math). Static "0 / 475+" by default (progressive enhancement: correct
   final value shown even if JS never runs) — JS resets the counted part
   to 0 and takes over only once it confirms the scroll-driven split
   hero is actually active. */
.sb-mstack__right { isolation: isolate; }
/* Back to pinned at the very top of the panel, per direct request — a
   separate large counter (.sb-ghost-num--lg below) now sits right
   above the paragraph instead, so this small corner one is free to
   live at the top again without also being "the text's counter". */
.sb-mstack__right-top {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 1.6rem 2rem;
	font-size: clamp(0.72rem, min(0.85vw, 1.15vh), 1rem);
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
}
.sb-mstack__right-top-label { color: rgba(245, 241, 234, 0.55); }
.sb-ghost-num {
	color: var(--sb-primary);
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
}
/* Big scroll-scrubbed counter, back above the paragraph text — the
   original "counts from 0 up to 475+ in lockstep with scroll" numeral
   from earlier in the project, reintroduced here, centered to match
   the rest of the (now centered) right-panel content. cqw (see the
   comment on .sb-mstack__right-inner) rather than vw/vh — same
   "column, not viewport" reasoning as the paragraph below it. */
.sb-ghost-num--lg {
	display: block;
	width: 100%;
	text-align: center;
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 700;
	font-size: clamp(3rem, 15cqw, 6.6rem);
	line-height: 1;
	letter-spacing: -0.02em;
	color: var(--sb-primary);
	margin-bottom: 0.6rem;
	font-variant-numeric: tabular-nums;
}
.sb-mstack__right-inner { position: relative; z-index: 1; }
/* Padding lives on the inner wrapper, not the shrinking flex item itself —
   a flex child can never shrink narrower than its own padding, which was
   leaving a permanent ~64px strip even at 0% width. */
/* Fixed layout width (not a % of the shrinking panel): the panel's own
   box still shrinks via --sb-split-left as before, but the heading
   inside it keeps the exact line-wrapping it had at full width the
   whole time — updateStack() in securebim-hero.js scales this block
   down as one rigid unit (transform, paint-time only) instead of
   letting it reflow. Reflowing at a fixed (viewport-relative) font-size
   as the box narrows was forcing the heading onto more and more lines —
   by the time the panel was ~half-width, every word had cascaded onto
   its own line, looking broken while still clearly visible. */
/* will-change hints the browser to promote this to its own compositor
   layer up front — it gets opacity + transform written to it every
   scroll frame by securebim-hero.js, so without this the browser would
   otherwise discover that and promote it mid-scroll (a one-time but
   real hitch right as the reader starts scrolling through the hero). */
.sb-mstack__left-inner { width: min(920px, calc(68vw - 4rem)); flex-shrink: 0; margin: 0 auto; padding: 2rem; box-sizing: border-box; transform-origin: left center; will-change: transform, opacity; }
.sb-mstack__right-inner { padding: 2rem; box-sizing: border-box; }
/* Scaled off the SMALLER of viewport width/height (min()), not just
   width — a fixed vw-based size looked fine on a normal-height screen
   but overlapped the photo on a short/wide "laptop" viewport (lots of
   horizontal room, not much vertical) and read as too small on a tall
   "widescreen" monitor with room to spare in both directions. Tying it
   to whichever dimension is actually tightest keeps it from outgrowing
   the panel's available height on short screens while still letting it
   grow properly on generous ones. */
.sb-mstack__left .sb-eyebrow { margin-bottom: 0.6rem; font-size: clamp(1.5rem, min(1.9vw, 2.8vh), 2.4rem); min-height: 1.3em; }
/* "photo" left panel: content anchored top-left (not centered), real
   BIM wireframe render as the panel's own background — the client's
   actual reference photo (assets/img/hero-building.jpg), not a drawn
   stand-in. `cover` (revision 43) read as too zoomed-in/large per
   direct feedback — sized down instead (a fixed % of the panel's own
   width, not `cover`, so it scales down rather than crop-to-fill).
   Anchored `right BOTTOM`, not top: with a fixed width and `auto`
   height, top-anchoring left a huge dead gap between the image and the
   "ontdek wat we doen" button on any viewport taller than the one this
   was tuned on — screenshotted at the client's own (taller) viewport
   and the image was floating disconnected near the header with empty
   space below it all the way to the button. Bottom-anchoring keeps it
   grounded next to that button regardless of viewport height; any
   extra vertical room now opens up ABOVE the image instead, between it
   and the heading, which reads fine. */
.sb-mstack__left--photo {
	align-items: stretch;
	background-image: url('../img/hero-building.jpg');
	background-repeat: no-repeat;
	background-position: right bottom;
	/* Same min(vw,vh) idea as the text below: a flat 85% of panel WIDTH
	   grew unboundedly tall on a short/wide "laptop" viewport (nothing
	   capping its height), which is exactly what was crowding the
	   heading. Capping it against vh as well keeps it in proportion to
	   whichever dimension is tighter. */
	/* Perf: was `min(85%, 74vh)` — that 85% is relative to THIS element's
	   own box, which is exactly what the split-hero animation resizes
	   every scroll frame (flex-basis, driven by --sb-split-left). A
	   percentage background-size tied to a box that's resizing every
	   frame forces the browser to re-rasterize the image at a new size
	   on every frame too — one of the main sources of the scroll being
	   "choppy/laggy" per direct feedback. Swapped for the fixed-vw
	   equivalent of that same 85% *at the panel's resting (pre-scroll)
	   width* — 85% of 68vw ≈ 58vw — so the image renders at one stable
	   size and the browser can just clip/composite it as the box
	   narrows, instead of re-rastering. Same visual size at rest; during
	   the brief scroll-through it now clips at the panel edge rather
	   than continuously rescaling, which isn't noticeable in practice
	   since the panel (and this image with it) is fading out and
	   shrinking to nothing over that same short stretch anyway. */
	background-size: min(58vw, 74vh) auto;
}
.sb-mstack__left--photo .sb-mstack__left-inner {
	margin: 0;
	height: 100%;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: flex-start;
	text-align: left;
	/* A bit more breathing room than the base 2rem — sitting right in
	   the top-left corner read as cramped per direct feedback. */
	padding: 3.75rem 2.5rem 2rem 2.75rem;
}
/* Smaller than the plain (non-photo) hero's display size — that size
   was tuned for a wide, unconstrained column; here the column is
   narrowed to keep clear of the photo's structure on the right, and
   this hero's copy ("Secure e-BIM Engineering & innovation.") is long
   enough that the bigger size pushed the last line below the fold of
   the pinned panel. Bumped up slightly from the first pass per direct
   feedback ("text a little bigger"). */
.sb-mstack__left.sb-mstack__left--photo .sb-display { max-width: 70%; font-size: clamp(3.5rem, min(6.9vw, 9vh), 6.3rem); }
/* First line ("Secure E-BIM", bold/ink) bigger, second line
   ("Engineering & Innovation", muted) smaller — per direct feedback.
   min(vw,vh): raised the top end for a big widescreen monitor (was
   capping out too soon and reading small there), while the vh half of
   that min() keeps a short/wide "laptop" viewport from blowing past
   its available vertical room and running into the photo — screenshot
   the client sent showed exactly that overlap on their laptop. */
.sb-mstack__left.sb-mstack__left--photo .sb-hero-line:first-child { font-size: clamp(3.4rem, min(7.9vw, 10vh), 8.6rem); }
.sb-mstack__left.sb-mstack__left--photo .sb-hero-line + .sb-hero-line { font-size: clamp(2.4rem, min(5.5vw, 7vh), 6rem); }
/* Ollie's own theme.json gives every heading a default top/bottom
   margin — harmless for a single heading, but here each line is its
   own separate <h1> (see sb_hero_stack()'s per-segment sb_display()
   calls), so those two default margins stacked between them and read
   as a big, disconnected gap ("very separated and ugly" per direct
   feedback). Zeroed out and replaced with one small, deliberate gap. */
.sb-hero-line { display: block; margin: 0; }
.sb-hero-line + .sb-hero-line { margin-top: 0.12em; }
/* Down-arrow "scroll cue" button, anchored to the bottom of the photo
   panel (margin-top: auto pushes it down within the column above). */
.sb-scroll-cue {
	margin-top: auto;
	display: inline-flex;
	align-items: center;
	gap: 0.85rem;
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
	font-family: inherit;
	font-size: 0.85rem;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--main);
}
/* Sitting directly on the busy photo (columns, pipe runs) left the
   label unreadable and the circle nearly invisible — a light scrim
   restores contrast without needing a full dark overlay across the
   whole image. */
.sb-mstack__left--photo .sb-scroll-cue {
	background: rgba(250, 248, 244, 0.88);
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
	padding: 0.5rem 1.1rem 0.5rem 0.5rem;
	border-radius: 999px;
	box-shadow: 0 8px 24px -12px rgba(20, 19, 17, 0.35);
}
.sb-scroll-cue__circle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.6rem;
	height: 2.6rem;
	flex-shrink: 0;
	border-radius: 50%;
	border: 1.5px solid var(--wp--preset--color--border-light);
	color: var(--sb-primary);
	transition: transform 0.25s var(--sb-ease), background 0.25s var(--sb-ease), border-color 0.25s var(--sb-ease);
}
.sb-scroll-cue__circle svg { width: 1.15rem; height: 1.15rem; }
.sb-scroll-cue:hover .sb-scroll-cue__circle { background: var(--sb-primary); border-color: var(--sb-primary); color: var(--sb-ink); transform: translateY(3px); }
@media (prefers-reduced-motion: reduce) {
	.sb-scroll-cue:hover .sb-scroll-cue__circle { transform: none; }
}
/* Terminal-style block cursor for the type/delete loop — solid while
   actively typing/deleting, only blinking during the two paused beats
   (fully typed / fully empty), matching how a real terminal caret
   behaves. Distinct from (and not to be confused with) the thin-line
   cursor already removed from the other typewriter effect elsewhere on
   this site per direct request — this is a deliberately different,
   blockier style, asked for specifically for this loop. */
.sb-type-loop__cursor {
	display: inline-block;
	width: 0.52em;
	height: 0.95em;
	margin-left: 0.06em;
	background: currentColor;
	vertical-align: -0.1em;
}
.sb-type-loop__cursor.sb-blink { animation: sb-cursor-blink 0.9s steps(1) infinite; }
@keyframes sb-cursor-blink { 50% { opacity: 0; } }
/* startTypeLoops() (securebim-hero.js) now skips ~900px and below (the
   mobile eyebrow-wrap layout-shift fix) — without this, the cursor sat
   there as a static, never-blinking solid block forever, since nothing
   ever adds/removes .sb-blink for it once the loop itself never starts. */
@media (max-width: 900px) {
	.sb-type-loop__cursor { display: none; }
}
@media (prefers-reduced-motion: reduce) {
	.sb-type-loop__cursor { display: none; }
}
.sb-mstack__left .sb-display { font-size: clamp(3.6rem, 8.6vw, 7rem); white-space: normal; }
/* Centered, not left-aligned — per direct feedback ("alles in het
   midden"), so the paragraph, buttons and caption read as one tidy
   centered column under "Secure" instead of a left-ragged block. */
.sb-mstack__right { text-align: center; }
/* Was a flat 540px cap — on a wide monitor the right panel itself is
   much wider than that, so the paragraph sat in a narrow column with a
   lot of unused space either side, reading small/lost. Letting it use
   more of the panel (up to a still-readable 760px) on wide screens. */
/* width:100% is required here, not just decorative — container-type
   (below) implies size containment, which makes this flex item's
   intrinsic (content-based) width inert. Without an explicit width it
   collapsed toward zero instead of filling out to max-width, which
   broke the whole column down to one word per line. */
.sb-mstack__right-inner { width: 100%; max-width: min(92%, 760px); }
/* vw-based font-size (and vh before that) both turned out to be the
   wrong unit here: they scale off the FULL viewport width, but this
   column is only ~32% of it (the dark panel). vw alone fixed the
   widescreen case but then broke the opposite way on an in-between
   "laptop" width — the panel there is much narrower in absolute px
   than on a wide monitor, so the same vw-derived font size was now too
   big for THAT narrower column, wrapping into the same kind of ragged
   short lines, just at a different screen size (confirmed via
   screenshot: broken on ~1150–1350px-wide screens even though both the
   true-widescreen and true-laptop cases looked fine). A container
   query fixes the actual problem instead of chasing it: cqw is a
   percentage of this element's OWN rendered width (already capped at
   760px above), so font size tracks the column itself, not the
   viewport it happens to sit in — correct at any width/height combo. */
.sb-mstack__right-inner { container-type: inline-size; }
.sb-mstack__right p { color: rgba(245, 241, 234, 0.94); font-size: clamp(1.2rem, 5cqw, 1.9rem); font-weight: 400; line-height: 1.5; }
/* Buttons stacked vertically (reference: two full-width rows, arrow
   icon at the end of each), not the earlier side-by-side pair. Tighter
   gap to the paragraph above (was 1.75rem) so the whole group reads as
   one connected block instead of scattered pieces. */
.sb-mstack__right .sb-mstack__buttons { display: flex; flex-direction: column; gap: 0.75rem; margin-top: 1.25rem; }
.sb-mstack__right .sb-btn { font-size: clamp(0.82rem, 2.3cqw, 1.05rem); padding: 0.95rem 1.7rem; }
.sb-btn--arrow { display: flex; align-items: center; justify-content: space-between; gap: 1rem; width: 100%; box-sizing: border-box; }
.sb-btn--arrow svg { width: 1.1rem; height: 1.1rem; flex-shrink: 0; }
.sb-mstack__caption { margin-top: 1rem; font-size: clamp(0.8rem, 2.2cqw, 1.05rem); color: rgba(245, 241, 234, 0.55); }
@media (max-width: 900px), (prefers-reduced-motion: reduce) {
	.sb-mstack { height: auto !important; }
	.sb-mstack__stage { position: static !important; height: auto !important; flex-direction: column !important; }
	.sb-mstack__left, .sb-mstack__right { flex-basis: auto !important; max-width: none !important; width: 100% !important; padding: clamp(3.5rem, 10vw, 6rem) 1.5rem; opacity: 1 !important; }
	.sb-mstack__left .sb-display { font-size: clamp(2rem, 8vw, 2.8rem); }
	.sb-mstack__left--photo { background-image: none; }
	.sb-mstack__left--photo .sb-display { max-width: none; }
	.sb-mstack__left--photo .sb-mstack__left-inner { height: auto; padding: 0; }
	.sb-mstack__right-top { display: none; }
	.sb-scroll-cue { margin-top: 2rem; }
}

/* ---------------------------------------------------------------------
   Cover sheet — the section right after the pinned split hero. Pulled up
   by exactly one viewport (the hero's HOLD_VH) via a negative margin, so
   as the reader scrolls through that hold time its top edge glides from
   the bottom of the screen to the top, "sliding up over" the still-pinned
   dark hero — pure CSS (sticky release + negative margin, both sized to
   the same one-viewport distance), no scroll-linked JS needed for this
   part. Nod to the reveal pattern used on landobrand.com and similar.
   --------------------------------------------------------------------- */
.sb-cover-sheet {
	/* Needs *something* a full viewport tall to fully hide the hero at
	   the exact moment the pin releases — confirmed the hard way twice:
	   no min-height, and again at a smaller 60vh min-height, both let
	   Diensten's heading peek through (screenshotted, garbled overlap
	   with the still-pinned hero) before the release point. But making
	   the REAL "Over Secure" content section itself min-height:100vh
	   just relocated that same forced emptiness to before/after the
	   actual text — "Over Secure" is nowhere near a full screen tall, so
	   either placement left a very visible dead stretch.
	   Fix: decouple the covering from the content's own box height.
	   .sb-cover-sheet itself is back to its natural (short) content
	   height — nothing forces it tall. The full-viewport coverage comes
	   from the ::before pseudo-element below instead: it's absolutely
	   positioned and independently sized to one vh100, so it visually
	   extends past this section's own (shorter) box and keeps painting
	   over whatever comes next in the document (Diensten) until the
	   reader has actually scrolled past it — without adding a single
	   pixel to the page's real scrollable height. */
	position: relative;
	z-index: 2;
	background: var(--wp--preset--color--base);
	margin-top: calc(var(--sb-vh100, 100vh) * -1);
}
.sb-cover-sheet::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: var(--sb-vh100, 100vh);
	background: var(--wp--preset--color--base);
}
.sb-cover-sheet > .wp-block-columns,
.sb-cover-sheet > * { position: relative; width: 100%; }
@media (max-width: 900px), (prefers-reduced-motion: reduce) {
	.sb-cover-sheet { margin-top: 0 !important; }
	.sb-cover-sheet::before { content: none; }
}

/* ---------------------------------------------------------------------
   Plain (text-only) hero — dark or light, no photo, no ornament.
   --------------------------------------------------------------------- */
.sb-plain-hero {
	padding: clamp(5rem, 12vw, 8rem) 2rem clamp(3.5rem, 8vw, 5rem);
}
.sb-plain-hero--dark { background: var(--sb-ink); }
.sb-plain-hero--light { background: var(--wp--preset--color--base); }
.sb-plain-hero__inner { max-width: 1320px; margin: 0 auto; }
.sb-plain-hero .sb-display { font-size: clamp(2.2rem, 5.5vw, 4rem); margin-top: 1rem; }
.sb-plain-hero__sub {
	max-width: 640px;
	margin-top: 1.25rem;
	font-size: 1.1rem;
}
.sb-plain-hero--dark .sb-plain-hero__sub { color: rgba(245, 241, 234, 0.75); }
.sb-plain-hero--light .sb-plain-hero__sub { color: var(--wp--preset--color--secondary); }

/* Restrained glow variant — no photo, no tiled grid (a tiled-grid version
   of this was tried earlier in the project and read as flat/generic; a
   single soft radial glow reads as considered instead). */
.sb-plain-hero--glow { position: relative; overflow: hidden; }
.sb-plain-hero--glow::before {
	content: "";
	position: absolute;
	inset: 0;
	background: radial-gradient(60% 55% at 18% 12%, rgba(232, 89, 12, 0.16), transparent 65%);
	pointer-events: none;
}
.sb-plain-hero--glow .sb-plain-hero__inner { position: relative; z-index: 1; }

/* Counted-up project numbers, real content (6 projects, 475+ units — not
   decorative filler) instead of a photo. */
.sb-hero-stats {
	position: relative;
	z-index: 1;
	max-width: 1320px;
	margin: clamp(2.5rem, 6vw, 4rem) auto 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
	gap: 2rem;
	border-top: 1px solid rgba(245, 241, 234, 0.16);
	padding-top: clamp(1.75rem, 4vw, 2.5rem);
}
.sb-hero-stats__num {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 700;
	font-size: clamp(1.9rem, 4vw, 3rem);
	color: var(--sb-primary);
	line-height: 1;
	font-variant-numeric: tabular-nums;
}
.sb-hero-stats__num--iconed, .sb-stat-row__num--iconed { display: inline-flex; align-items: center; gap: 0.55rem; }
/* sb_morph_cube(): a genuinely continuous loop — a flat square ("2D")
   cross-fades into a flat isometric drawing of a cube ("3D") and back,
   forever, via opacity only — not a one-shot scroll reveal, and
   deliberately not real CSS 3D transforms (see the long comment on
   sb_morph_cube() in page-builder.php for why: nested preserve-3d at
   this icon size didn't reliably paint). currentColor throughout so it
   always matches whatever stat number it sits next to. */
.sb-morph-cube { display: inline-block; width: 22px; height: 22px; flex: none; overflow: visible; }
.sb-morph-cube__flat, .sb-morph-cube__cube { transform-origin: 16px 16px; transition: none; }
.sb-morph-cube__flat { animation: sb-cube-flat 4.2s ease-in-out infinite; }
.sb-morph-cube__cube { animation: sb-cube-solid 4.2s ease-in-out infinite; }
@keyframes sb-cube-flat {
	0%, 12% { opacity: 1; transform: scale(1); }
	45%, 75% { opacity: 0; transform: scale(0.85); }
	95%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes sb-cube-solid {
	0%, 12% { opacity: 0; transform: scale(0.85); }
	45%, 75% { opacity: 1; transform: scale(1.05); }
	95%, 100% { opacity: 0; transform: scale(0.85); }
}
@media (prefers-reduced-motion: reduce) {
	.sb-morph-cube__flat, .sb-morph-cube__cube { animation: none; }
	.sb-morph-cube__flat { opacity: 0; }
	.sb-morph-cube__cube { opacity: 1; }
}
.sb-hero-stats__label {
	margin-top: 0.5rem;
	font-size: 0.9rem;
	color: rgba(245, 241, 234, 0.65);
}

/* ---------------------------------------------------------------------
   Body rhythm.
   --------------------------------------------------------------------- */
.sb-band { padding-top: clamp(3.5rem, 7vw, 5.5rem); padding-bottom: clamp(3.5rem, 7vw, 5.5rem); }
/* Gets transform: translateY() written every scroll frame (the "creep"
   parallax, securebim-hero.js) — same will-change reasoning as
   .sb-mstack__left-inner above. */
[data-sb-parallax] { will-change: transform; }
.sb-band--tint { background: var(--wp--preset--color--tint); }

/* --- Chamfered card (single cut corner) --- */
.sb-card {
	--cut: 20px;
	background: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--border-light);
	clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%);
	padding: 1.75rem;
	transition: border-color 0.25s var(--sb-ease), transform 0.25s var(--sb-ease);
}
.sb-card:hover { border-color: var(--sb-primary); transform: translateY(-3px); }
/* Equal-height cards in a service row: a title that wraps to 2 lines
   (e.g. "Elektrotechnische BIM-modellering in Revit") was making that
   one card taller than its row-mates, which sat shorter with extra
   whitespace — per direct feedback, no card should be bigger just
   because it has more text. align-items:stretch on the row + height:
   100% on the card (not the column itself, since .sb-reveal's own
   transform/opacity load-in animation lives on .sb-card and needs to
   stay the thing being sized) evens them out; the shorter cards just
   gain a bit of empty padding at the bottom, which is the expected
   trade-off for a level grid. */
.sb-service-row { align-items: stretch; }
.sb-service-row .sb-card { height: 100%; box-sizing: border-box; }
.sb-card--dark { background: var(--sb-ink); color: var(--sb-ink-text); border-color: rgba(245, 241, 234, 0.14); }

/* Honeypot field (sb_contact_form()): visually hidden but still in the
   normal tab/DOM flow — real users never see or reach it, but naive
   spam bots that fill every field do, and get silently rejected. Not
   display:none, which some bots already know to skip. */
.sb-hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}
.sb-form-notice {
	padding: 0.9rem 1.1rem;
	margin: 0 0 1.25rem;
	font-size: 0.92rem;
	border-left: 3px solid;
}
.sb-form-notice--success { background: rgba(232, 89, 12, 0.08); border-color: var(--sb-primary); }
.sb-form-notice--error { background: rgba(20, 19, 17, 0.05); border-color: var(--sb-ink); }

/* --- Tag / badge --- */
.sb-tag {
	display: inline-block;
	font-size: 0.72rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--sb-primary);
	border: 1px solid var(--sb-primary);
	padding: 0.3em 0.75em;
}
.sb-tag--solid { background: var(--sb-primary); color: var(--sb-ink); border-color: var(--sb-primary); }

/* ---------------------------------------------------------------------
   Spec grid — plain bordered grid (icon + heading + desc), no code
   labels, no monospace. Nod to axiom-power's feature grid, quieted down.
   --------------------------------------------------------------------- */
.sb-spec-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	border-top: 1px solid var(--wp--preset--color--border-light);
	border-left: 1px solid var(--wp--preset--color--border-light);
}
.sb-spec-cell {
	border-right: 1px solid var(--wp--preset--color--border-light);
	border-bottom: 1px solid var(--wp--preset--color--border-light);
	padding: 2.25rem 1.85rem;
	transition: background-color 0.25s var(--sb-ease);
}
.sb-spec-cell:hover { background: var(--wp--preset--color--tint); }
.sb-spec-cell__icon { width: 30px; height: 30px; color: var(--sb-primary); margin-bottom: 1.5rem; }
.sb-spec-cell h3 { margin: 0 0 0.5rem; font-size: 1.1rem; }
.sb-spec-cell p { margin: 0; color: var(--wp--preset--color--secondary); font-size: 0.95rem; }
@media (max-width: 900px) { .sb-spec-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 599px) { .sb-spec-grid { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------------
   Manifesto — big-type editorial statement (Diensten page). Went
   through two full-bleed orange treatments (flat, then a two-tone
   pull-quote with a decorative mark) and both still read as "just a
   banner, boring" per direct feedback. Dropped the colour band
   entirely instead — plain page background, huge mixed-weight type,
   the orange now spent on the emphasised phrase itself rather than a
   box around everything, matching this project's own stated direction
   (huge mixed-weight editorial type, lots of quiet whitespace, almost
   no ornament — see the top of this file). */
.sb-manifesto p {
	font-family: var(--wp--preset--font-family--heading);
	font-size: clamp(2rem, 4.8vw, 3.6rem);
	font-weight: 700;
	line-height: 1.16;
	letter-spacing: -0.01em;
	margin: 0;
	max-width: 1000px;
	color: rgba(22, 21, 18, 0.5);
}
.sb-manifesto p strong { color: var(--sb-primary); font-weight: 700; }

/* ---------------------------------------------------------------------
   Inline stat row — plain, on the page's own background (no separate
   full-bleed dark band; that read as a slab of dead space, especially
   stacked right above the equally-dark footer).
   --------------------------------------------------------------------- */
.sb-stat-row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 2rem;
	max-width: 1320px;
	margin: 0 auto;
}
.sb-stat-row__num { font-family: var(--wp--preset--font-family--heading); font-size: clamp(1.8rem, 3.4vw, 2.5rem); font-weight: 700; color: var(--sb-primary); line-height: 1; }
.sb-stat-row__label { margin-top: 0.45rem; font-size: 0.85rem; color: var(--wp--preset--color--secondary); }

/* Floating info badge pinned to a hero corner — a compact stand-in for a
   whole stats section (nod to axiom-power's pinned corner cards). */
.sb-hero-badge {
	position: absolute;
	right: 2rem;
	bottom: clamp(1.5rem, 4vw, 2.5rem);
	z-index: 2;
	background: rgba(250, 248, 244, 0.95);
	color: var(--wp--preset--color--main);
	padding: 1.1rem 1.4rem;
	max-width: 230px;
	border-left: 3px solid var(--sb-primary);
}
.sb-hero-badge__num { font-family: var(--wp--preset--font-family--heading); font-size: 1.6rem; font-weight: 700; line-height: 1; }
.sb-hero-badge__label { font-size: 0.78rem; color: var(--wp--preset--color--secondary); margin-top: 0.3rem; }
@media (max-width: 700px) { .sb-hero-badge { display: none; } }

/* ---------------------------------------------------------------------
   Scroll hero — a pinned, full-viewport stage that cycles through real
   project photos as the reader scrolls past it, each one scaling up from
   a distance ("flying at you") and settling before the next takes over.
   Nod to floema.com's pinned image-sequence hero. Driven by
   assets/js/securebim-hero.js; degrades to a plain static first-slide
   hero if JS or scroll-based motion isn't available.
   --------------------------------------------------------------------- */
.sb-scrollhero { position: relative; }
.sb-scrollhero__stage {
	position: sticky;
	top: 0;
	height: var(--sb-vh100, 100vh);
	overflow: hidden;
	display: flex;
	align-items: flex-end;
}
.sb-scrollhero__slide {
	position: absolute;
	inset: 0;
	opacity: 0;
	will-change: opacity, transform;
}
.sb-scrollhero__slide:first-child { opacity: 1; }
.sb-scrollhero__slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sb-scrollhero__overlay {
	position: absolute;
	inset: 0;
	z-index: 1;
	background: linear-gradient(0deg, rgba(20, 19, 17, 0.92) 0%, rgba(20, 19, 17, 0.5) 45%, rgba(20, 19, 17, 0.12) 78%);
}
.sb-scrollhero__content { position: relative; z-index: 2; width: 100%; padding: 3rem 2rem clamp(3rem, 7vw, 5rem); }
.sb-scrollhero__inner { max-width: 1320px; margin: 0 auto; }
.sb-scrollhero .sb-display { font-size: clamp(2.4rem, 6vw, 4.5rem); }
.sb-scrollhero__sub { color: rgba(245, 241, 234, 0.85); font-size: 1.1rem; max-width: 640px; margin-top: 1.25rem; }
.sb-scrollhero__meta {
	position: absolute;
	left: 2rem;
	bottom: clamp(1.5rem, 4vw, 2.5rem);
	z-index: 2;
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
	color: rgba(245, 241, 234, 0.65);
	font-size: 0.85rem;
}
.sb-scrollhero__meta-index { font-family: var(--wp--preset--font-family--heading); font-weight: 700; color: var(--sb-ink-text); }
.sb-scrollhero__track {
	position: absolute;
	right: 2rem;
	bottom: clamp(1.5rem, 4vw, 2.5rem);
	z-index: 2;
	width: 140px;
	height: 2px;
	background: rgba(245, 241, 234, 0.25);
}
.sb-scrollhero__track-fill { height: 100%; width: 0%; background: var(--sb-primary); }
@media (max-width: 700px) {
	.sb-scrollhero__meta, .sb-scrollhero__track { display: none; }
}
@media (prefers-reduced-motion: reduce) {
	.sb-scrollhero__stage { position: static; height: auto; min-height: 84vh; }
	.sb-scrollhero__slide { position: absolute; }
	.sb-scrollhero__slide:first-child { position: relative; opacity: 1; }
}

/* ---------------------------------------------------------------------
   Curtain intro — a full-viewport panel that covers the page on first
   load, then rises away (nod to the "screen sliding up" reveal pattern).
   Plays once per browser session (assets/js/securebim-hero.js).
   --------------------------------------------------------------------- */
.sb-curtain {
	position: fixed;
	inset: 0;
	z-index: 999;
	background: var(--sb-ink);
	display: flex;
	align-items: center;
	justify-content: center;
	transition: transform 0.9s cubic-bezier(0.76, 0, 0.24, 1);
}
.sb-curtain__mark {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 700;
	font-size: clamp(1.4rem, 3.4vw, 2rem);
	color: var(--sb-ink-text);
	transition: opacity 0.4s ease;
}
.sb-curtain--up { transform: translateY(-100%); }
.sb-curtain--up .sb-curtain__mark { opacity: 0; }
.sb-curtain[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
	.sb-curtain { display: none; }
}

/* --- Project card --- */
.sb-project {
	background: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--border-light);
	clip-path: polygon(0 0, calc(100% - 26px) 0, 100% 26px, 100% 100%, 0 100%);
	overflow: hidden;
	transition: border-color 0.25s var(--sb-ease), transform 0.25s var(--sb-ease);
	height: 100%;
}
.sb-project:hover { border-color: var(--sb-primary); transform: translateY(-4px); }
.sb-project__media { aspect-ratio: 16 / 10; overflow: hidden; }
.sb-project__media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.5s var(--sb-ease); }
.sb-project:hover .sb-project__media img { transform: scale(1.05); }
.sb-project__body { padding: 1.5rem; }
.sb-project__loc { font-size: 0.82rem; color: var(--wp--preset--color--secondary); margin: 0.15rem 0 0.75rem; }

/* --- CTA band --- */
.sb-cta { background: var(--sb-ink); color: var(--sb-ink-text); text-align: center; padding: clamp(3.5rem, 7vw, 5.5rem) 2rem; }

/* ---------------------------------------------------------------------
   Footer — plain, oversized cropped wordmark.
   --------------------------------------------------------------------- */
.sb-footer { background: var(--sb-ink); color: var(--sb-ink-text); }
.sb-footer img.custom-logo { max-height: 26px; width: auto; }
/* alignfull, so it ignores the footer's own left padding and starts
   flush at the literal edge of the browser window — at this size that
   read as the word "running off"/through the edge of the site rather
   than sitting inside it. A little left padding brings it in line with
   the rest of the footer content instead. */
.sb-footer-word { overflow: hidden; line-height: 0.82; margin-top: clamp(2rem, 6vw, 3.5rem); padding-left: clamp(1.5rem, 4vw, 3rem); }
.sb-footer-word span {
	display: block;
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 700;
	font-size: clamp(3.5rem, 13vw, 11rem);
	letter-spacing: -0.02em;
	color: var(--sb-ink-text);
	opacity: 0.92;
	white-space: nowrap;
}

/* ---------------------------------------------------------------------
   Reveal on scroll — the one animation kept from earlier feedback
   ("cool scroll animations" was explicitly asked for and liked).
   --------------------------------------------------------------------- */
.sb-reveal { opacity: 0; transform: translateY(24px); transition: opacity 0.6s var(--sb-ease), transform 0.6s var(--sb-ease); }
.sb-reveal.sb-in-view { opacity: 1; transform: translateY(0); }
.wp-block-columns > .wp-block-column:nth-child(1) .sb-reveal { transition-delay: 0ms; }
.wp-block-columns > .wp-block-column:nth-child(2) .sb-reveal { transition-delay: 80ms; }
.wp-block-columns > .wp-block-column:nth-child(3) .sb-reveal { transition-delay: 160ms; }

@keyframes sb-rise {
	from { opacity: 0; transform: translateY(22px) scale(0.97); filter: blur(6px); }
	to   { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
.sb-load-in > * { opacity: 0; }
html.sb-ready .sb-load-in > * { animation: sb-rise 0.85s var(--sb-ease) both; }
.sb-load-in > *:nth-child(1) { animation-delay: 0.05s; }
.sb-load-in > *:nth-child(2) { animation-delay: 0.18s; }
.sb-load-in > *:nth-child(3) { animation-delay: 0.31s; }
.sb-load-in > *:nth-child(4) { animation-delay: 0.44s; }
.sb-load-in > *:nth-child(5) { animation-delay: 0.57s; }

@media (prefers-reduced-motion: reduce) {
	.sb-reveal, .sb-load-in > *, .sb-word, .sb-char { opacity: 1 !important; transform: none !important; filter: none !important; animation: none !important; transition: none !important; }
}

/* ---------------------------------------------------------------------
   Buttons — solid / outline, with a hover lift + the load-in animation
   above applying to them like any other hero element.
   --------------------------------------------------------------------- */
.sb-btn {
	display: inline-block;
	padding: 0.8rem 1.6rem;
	text-transform: uppercase;
	font-size: 0.82rem;
	letter-spacing: 0.05em;
	font-weight: 600;
	text-decoration: none;
	border-radius: 0;
	border: 1px solid transparent;
	cursor: pointer;
	transition: transform 0.25s var(--sb-ease), box-shadow 0.25s var(--sb-ease), background-color 0.25s var(--sb-ease), border-color 0.25s var(--sb-ease), color 0.25s var(--sb-ease);
}
.sb-btn:hover { transform: translateY(-3px); }
.sb-btn:active { transform: translateY(-1px); }
.sb-btn--solid { background: var(--sb-primary); color: var(--sb-ink); }
/* Was swapping to a near-black background on hover, which read fine on a
   light section but made the button vanish into the equally dark hero
   panel it also appears on. A relative brightness dip keeps the same
   orange (and stays visible) wherever the button is used. */
.sb-btn--solid:hover { filter: brightness(0.87); box-shadow: 0 14px 28px -14px rgba(0, 0, 0, 0.45); }
.sb-btn--outline-light { background: transparent; color: #F5F1EA; border-color: rgba(245, 241, 234, 0.5); }
.sb-btn--outline-light:hover { background: rgba(245, 241, 234, 0.12); border-color: #F5F1EA; }
.sb-btn--outline-dark { background: transparent; color: var(--wp--preset--color--main); border-color: var(--wp--preset--color--border-light); }
.sb-btn--outline-dark:hover { background: var(--wp--preset--color--tint); border-color: var(--sb-primary); }
@media (prefers-reduced-motion: reduce) {
	.sb-btn, .sb-btn:hover, .sb-btn:active { transform: none !important; }
}

/* Lightweight hover-lift for native wp:button markup (CTA band etc.) where
   the full .sb-btn class would fight WP's own button padding/typography. */
.sb-btn-hover { transition: transform 0.25s var(--sb-ease), box-shadow 0.25s var(--sb-ease); display: inline-block; }
.sb-btn-hover:hover { transform: translateY(-3px); box-shadow: 0 14px 28px -14px rgba(0, 0, 0, 0.45); }
@media (prefers-reduced-motion: reduce) {
	.sb-btn-hover, .sb-btn-hover:hover { transform: none !important; }
}
