/**
 * 14-projects-carousel.css — Hover-Expand Carousel for the #projects section.
 * Hover model: Skiper UI HoverExpand_001 (one active bar expands to a square,
 * the rest sit thin; ease-in-out width transitions; bottom gradient + title on
 * the active bar). Auto-loaded by the gfx glob. Runtime:
 * assets/js/gfx/14-projects-carousel.js.
 *
 * Geometry lives in the custom props below — the dynamic block overrides them
 * inline on .vz-mag-stage from the VizAion Parameters option, and the runtime
 * reads them at mount and on resize, so breakpoints and wp-admin only ever
 * touch the props, never the JS.
 */

/* Section wrapper — arrival title above the carousel, same greeting pattern as
   Practices' .prax-intro. Overflow stays visible: the open square intentionally
   exceeds the bar row. */
.vz-projects {
	/* Clearance of the fixed header + hairline (assets/css/gfx/13-journey-world.css).
	   One number, three consumers: this box, the scroll magnet's top-align, and the
	   runtime's open-square geometry (readCfg picks it up by inheritance). */
	--vz-mc-head: 119px;

	/* The house curve — the same easeInOutCubic the scroll magnet animates on
	   (assets/js/10-scroll-magnet.js). Existing as a token is what makes the
	   mobile deck read as the same product and not a second design. */
	--vz-ease: cubic-bezier(0.65, 0, 0.35, 1);

	/* --vz-mc-blur (30px, from the admin) stays the ATMOSPHERIC blur and is used
	   literally on the mobile ambient layer. This is the FUNCTIONAL one, scaled
	   by element area: 30px across a 358px tile is unreadable mush. */
	--vz-mc-blur-near: clamp(8px, 4vw, 16px);

	/* Gutter — the 24px corner radius needs an inset to exist against. */
	--vz-mc-pad: clamp(1.25rem, 5vw, 2rem);

	/* Portrait tile: more work per screen than 16/9 without becoming a hero.
	   --vz-mc-tile-max is the height budget the width is solved against. */
	--vz-mc-tile-ar: 4 / 5;
	--vz-mc-tile-max: 52svh;

	/* Panel type. Both ratios stay under 2.5x min so WCAG 1.4.4 holds at zoom. */
	--vz-mc-fs-title: clamp(1.25rem, 1.05rem + 1vw, 1.6rem);
	--vz-mc-fs-desc: clamp(0.95rem, 0.92rem + 0.2vw, 1.05rem);

	position: relative;
	display: flex;
	flex-direction: column;
	justify-content: center;
	/* border-box is load-bearing: this element computes as content-box otherwise,
	   so `100svh` plus padding made the section TALLER than the viewport and the
	   open square fell off the bottom of the screen. Sized to the free area under
	   the header so the magnet centres it instead of top-aligning it. */
	box-sizing: border-box;
	min-height: calc(100svh - var(--vz-mc-head));
	padding: 4vh 16px 6vh;
	/* Blend group root: the h2's mix-blend-mode composes against THIS section's
	   bg videos and scrim, never against the fixed butterfly sky behind it. */
	isolation: isolate;
}

/* Sibling lift over the background holder. 10-section-bg.css does this for its
   own sections via `.vz-has-bg`, but that class is not ours to keep — the
   section-bg runtime strips it back off at DOMContentLoaded when it has no
   config for #projects. Owning three lines here beats depending on a class
   another runtime removes. */
.vz-projects > .proj-intro,
.vz-projects > .vz-mag-stage {
	/* Positioned but z-index:auto ON PURPOSE (2026-09-06). DOM order already
	   paints these above the z:0 .vz-projects-bg holder (inserted as first
	   child). Giving them z-index:1 made each a stacking context, which (a)
	   capped the open square (3) and info panel (4) UNDER the .vz-mag-backdrop
	   (2) — every click closed the view — and (b) isolated the h2's
	   mix-blend-mode from the section behind it. */
	position: relative;
}

.vz-projects .proj-intro {
	text-align: center;
	margin-bottom: 5vh;
}

/* Negative title: inverts against the bg clips behind it (2026-09-06). */
.vz-projects .proj-intro h2 {
	color: #fff;
	mix-blend-mode: difference;
}

.vz-mag-carousel {
	/* Skiper HoverExpand proportions: thin 5rem bars, 24rem tall, the active
	   one a 24rem square. All editable in Appearance → VizAion Parameters. */
	--vz-mc-cw: 80px;       /* collapsed (thin) bar width */
	--vz-mc-ch: 384px;      /* bar height */
	--vz-mc-hw: 384px;      /* expanded bar width (square when = height) */
	--vz-mc-open: 700px;    /* open square size (runtime clamps to viewport) */
	--vz-mc-gap: 4px;
	--vz-mc-blur: 30px;     /* blur on the non-open bars while one is open */
	--vz-mc-dur: 0.3s;      /* shared ease duration (hover, open/close, fades) */
	--vz-mc-radius: 24px;   /* bar corner radius (Skiper rounded-3xl) */
	--vz-mc-panel-w: 420px; /* info panel max width when overlaid */

	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--vz-mc-gap);
	min-height: 420px;
	overflow: visible;
}

.vz-mag-bar {
	flex: none;
	width: var(--vz-mc-cw);
	height: var(--vz-mc-ch);
	padding: 0;
	border: 0;
	appearance: none;
	cursor: pointer;
	background-color: transparent;
	background-size: cover;
	background-position: center;
	background-repeat: no-repeat;
	position: relative;
	z-index: 2;
	border-radius: var(--vz-mc-radius);
	overflow: hidden;
	/* The hover-expand IS this transition — a class toggles the width. left/top
	   join only during .is-anim (the open/close FLIP). */
	transition:
		width var(--vz-mc-dur) ease-in-out,
		height var(--vz-mc-dur) ease-in-out,
		filter var(--vz-mc-dur) ease-in-out,
		opacity var(--vz-mc-dur) ease-in-out;
	will-change: width;
}

/* Full-image reveal (Fabão 2026-09-11, v3 — "bater na borda exata"): the
   runtime measures each cover image and stamps --vz-bar-hw = bar height ×
   image aspect on the bar, so the expanded tile IS the image's own shape —
   every corner visible, no crop, no fill. The fixed token is the fallback for
   bars whose image hasn't loaded (and the --empty gradient bar). */
.vz-mag-bar.is-expanded { width: var(--vz-bar-hw, var(--vz-mc-hw)); }

.vz-mag-bar--empty {
	background-image: linear-gradient(160deg, #26262b, #101014);
}

/* Bottom gradient + project title, revealed on the expanded bar only. */
.vz-mag-bar::before {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgba(0, 0, 0, 0.45), transparent 50%);
	opacity: 0;
	transition: opacity var(--vz-mc-dur) ease-in-out;
	pointer-events: none;
}

.vz-mag-bar.is-expanded::before { opacity: 1; }

.vz-mag-bar-label {
	position: absolute;
	left: 18px;
	right: 18px;
	bottom: 14px;
	text-align: left;
	color: #fff;
	font-size: 0.9rem;
	line-height: 1.3;
	letter-spacing: 0.02em;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	opacity: 0;
	transition: opacity var(--vz-mc-dur) ease-in-out;
	pointer-events: none;
}

.vz-mag-bar.is-expanded .vz-mag-bar-label { opacity: 1; }

/* The open square speaks through the info panel — no duplicate title on it. */
.vz-mag-bar.is-open::before,
.vz-mag-bar.is-open .vz-mag-bar-label { opacity: 0; }

/* Inert stand-in inserted by the runtime while a bar is absolutized open, so
   the flex row keeps its exact layout and siblings never move. */
.vz-mag-placeholder {
	flex: none;
	width: var(--vz-mc-cw);
	height: var(--vz-mc-ch);
	visibility: hidden;
	pointer-events: none;
}

.vz-mag-bar:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

.vz-mag-bar.is-open { z-index: 3; }

.vz-mag-bar.is-dim {
	filter: blur(var(--vz-mc-blur));
	opacity: 0.6;
}

/* Open/close FLIP: left/top join the eased set while this class is on. */
.vz-mag-carousel.is-anim .vz-mag-bar {
	transition:
		width var(--vz-mc-dur) ease-in-out,
		height var(--vz-mc-dur) ease-in-out,
		left var(--vz-mc-dur) ease-in-out,
		top var(--vz-mc-dur) ease-in-out,
		filter var(--vz-mc-dur) ease-in-out,
		opacity var(--vz-mc-dur) ease-in-out;
}

/* Click-anywhere-blurred-to-close layer (inserted by the runtime as a child of
   .vz-projects, so it spans the whole section, not just the bar row).
   z-index 2 sits above the dimmed bars and the intro; the open square (3) and
   the info panel (4) rise above it — which only works because .vz-mag-stage is
   NOT a stacking context (see the .proj-intro/.vz-mag-stage rule above). */
.vz-mag-backdrop {
	position: absolute;
	inset: 0;
	z-index: 2;
	pointer-events: none;
}
.vz-projects.has-open .vz-mag-backdrop { pointer-events: auto; }

/* Belt and braces on the same bug: a blurred bar is scenery, not a target. */
.vz-mag-carousel.has-open .vz-mag-bar.is-dim { pointer-events: none; }

/* ---------------------------------------------------------------------------
   Live project (2026-09-22). The centre card boots open via the runtime's
   openLive/closeLive — a light state with none of the modal furniture (no
   backdrop, no dimming), so the neighbours stay hoverable around it. */

.vz-mag-bar.is-live-open { z-index: 3; cursor: pointer; }

/* Unlike the modal square, the open live card keeps its title readable —
   the whole card is a doorway into the project's universe. */
.vz-mag-bar.is-live-open::before,
.vz-mag-bar.is-live-open .vz-mag-bar-label { opacity: 1; }

/* LIVE badge on any bar flagged live — closed, expanded or open, both tiers.
   Rides on the data-vz-status attribute the PHP already emits. It lives on the
   BAR's ::after, INSIDE the card box: the bar clips its overflow (rounded
   corners) and .vz-mag-bar-label clips its own too, so anything hung outside
   either box is never painted. z-index 3 clears the runtime slideshow layer
   (.vz-mag-slides, z-index 1). The red dot is painted into the pill's own
   background and the ring pulse carries the "on air" signal. */
.vz-mag-bar[data-vz-status="live"]::after {
	content: "LIVE";
	position: absolute;
	top: 12px;
	left: 12px;
	z-index: 3;
	padding: 3px 9px 3px 19px;
	border-radius: 999px;
	background:
		radial-gradient(circle at 10px 50%, #ff4b4b 3px, transparent 4px),
		rgba(10, 10, 16, 0.6);
	color: #fff;
	font-size: 10px;
	letter-spacing: 0.18em;
	line-height: 1.4;
	animation: vz-live-pulse 2.2s ease-in-out infinite;
	pointer-events: none;
}

/* Gradient + title readable over the slideshow on the OPEN live card (the
   stock rule hides them on .is-open; live keeps its doorway label). */
.vz-mag-bar[data-vz-status="live"] .vz-mag-bar-label { z-index: 3; }
.vz-mag-bar.is-live-open::before { z-index: 2; }

@keyframes vz-live-pulse {
	0%, 100% { box-shadow: 0 0 0 0 rgba(255, 75, 75, 0.45); }
	50% { box-shadow: 0 0 0 6px rgba(255, 75, 75, 0); }
}

/* The centred square meets its side panel sooner than an edge-flush one —
   slim the panel down, and below ~1180px let the live card stand alone. */
.vz-projects.has-live-open .vz-mag-panel { width: min(var(--vz-mc-panel-w, 420px), 26vw); }
@media (max-width: 1180px) {
	.vz-projects.has-live-open .vz-mag-panel { display: none; }
}

/* ---------------------------------------------------------------------------
   Info panels — server-rendered for every project (AEO law).
   Baseline (no JS): a plain readable stack below the carousel, nothing hidden.
   Enhanced (.is-enhanced, added by the runtime): panels overlay the stage on the
   side opposite the opened square, fading in after the square lands. */

.vz-mag-stage { position: relative; }

.vz-mag-panels {
	display: grid;
	gap: 32px;
	margin-top: 48px;
}

.vz-mag-panel {
	max-width: 42rem;
	margin: 0 auto;
	text-align: left;
}

.vz-mag-panel-title { margin: 0 0 0.5em; }

.vz-mag-panel-desc { margin: 0 0 1em; }

.vz-mag-panel-media { margin: 0 0 1em; }

.vz-mag-panel-media img,
.vz-mag-panel-media video {
	display: block;
	width: 100%;
	height: auto;
}

.vz-mag-panel-link { display: inline-block; }

.is-enhanced .vz-mag-panels {
	display: contents; /* panels position against .vz-mag-stage, not the grid */
}

.is-enhanced .vz-mag-panel {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	width: min(var(--vz-mc-panel-w, 420px), 40vw);
	max-width: none;
	margin: 0;
	z-index: 4; /* above the backdrop (2); open bar is 3 — section-level ladder */
	opacity: 0;
	transition: opacity var(--vz-mc-dur) ease-in-out;
	/* readable over the dimmed bars / dark stage */
	color: #fff;
	background: rgba(10, 10, 16, 0.55);
	-webkit-backdrop-filter: blur(14px);
	backdrop-filter: blur(14px);
	padding: 28px;
	border-radius: var(--vz-mc-radius, 24px);
}

.is-enhanced .vz-mag-panel a {
	color: inherit;
}

.is-enhanced .vz-mag-panel.is-side-left { left: 0; }
.is-enhanced .vz-mag-panel.is-side-right { right: 0; }

.is-enhanced .vz-mag-panel.is-active {
	opacity: 1;
	/* fade in only after the square has landed; fade-out stays immediate */
	transition-delay: var(--vz-mc-dur);
}

/* ---------------------------------------------------------------------------
   Hover background — the site surface behind the carousel becomes the hovered
   project's complementary clip. Reuses the .vz-section-bg holder contract from
   assets/css/10-section-bg.css (absolute, inset 0, z-index 0, own overflow) so
   there is no second background system; only the crossfade is new here.
   Two stacked <video>s swap by opacity, so moving along the bars reads as a
   dissolve instead of a cut. */

.vz-projects-bg-slot {
	position: absolute;
	inset: 0;
	opacity: 0;
	transition: opacity var(--vz-mc-bg-fade, 700ms) ease-in-out;
}

.vz-projects-bg-slot.is-active { opacity: 1; }

.vz-projects-bg-slot > video,
.vz-projects-bg-slot > img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* The gallery-still fallback (a project with nothing in projects-bg/) is
   atmosphere borrowed from content — soften it so it reads as backdrop, not as
   a fifth image competing with the bars. Authored stills ship unsoftened. */
.vz-projects-bg-slot.is-soft > img {
	filter: blur(28px) saturate(1.1);
	transform: scale(1.08); /* hide the blur bleed at the edges */
}

/* Feathered edges (2026-09-06) + vertical bleed (Fabão 2026-09-11): the holder
   overshoots the section by 20vh on both ends so the butterfly sky behind is
   much harder to glimpse at the seams — the feather ramp now lives mostly
   inside the bleed (length-based stops, not %), leaving the section's own
   edges near-solid. The overshoot paints inside this section's isolated
   stacking context: practices content above (z:1 lift) and the contact card
   below (later positioned sibling) still cover it, so only backdrop is hidden,
   never content. Masks the slots AND the ::after scrim together. */
.vz-projects .vz-projects-bg {
	top: -20vh;
	/* Bottom overshoot is BACK, feathered (Fabão 2026-09-23, supersedes the
	   2026-09-11 retraction). With bottom:0 the 12vh feather died INSIDE the
	   viewport, so the hover video never reached the bottom of the screen and
	   the frame read as misaligned. Now the solid area runs 4vh past the
	   section's bottom edge (the fold, when the magnet has the section seated)
	   and the 12vh fade lives below the fold — full-screen coverage, and the
	   feather still protects the sky gap on the way to Contact. Unlike the
	   09-11 problem this is a FADE, not a solid bleed, and it only exists
	   while a bar is hovered. 20vh (mirroring the top) rather than 16: the
	   section can seat up to ~8vh above its ideal berth and the solid area
	   must still reach the fold. */
	bottom: -20vh;
	-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16vh, #000 calc(100% - 12vh), transparent 100%);
	mask-image: linear-gradient(to bottom, transparent 0, #000 16vh, #000 calc(100% - 12vh), transparent 100%);
}

/* While a project is open the whole area backdrop goes soft, same treatment the
   dimmed bars get (2026-09-06). Tokens inherit from the section (inline admin
   style lives there). */
.vz-projects-bg {
	transition: filter var(--vz-mc-dur, 0.3s) ease-in-out;
}
.vz-projects.has-open .vz-projects-bg {
	filter: blur(var(--vz-mc-blur, 30px));
}

/* The title joins the immersion (phase 2, 2026-09-06): blur ON THE H2 — a filter
   on the element itself does not isolate its own mix-blend-mode (only an
   ancestor's filter would), so the difference blend survives, blurred. The
   var fallbacks are load-bearing: the tokens live on the section/carousel,
   not on .proj-intro. */
.vz-projects .proj-intro h2 {
	transition: filter var(--vz-mc-dur, 0.3s) ease-in-out;
}
.vz-projects.has-open .proj-intro h2 {
	filter: blur(var(--vz-mc-blur, 30px));
}

/* Scrim: the clips are atmosphere, the bars are the content. Painted after the
   videos (::after on their parent) so it dims them without touching the bars. */
.vz-projects-bg::after {
	content: "";
	position: absolute;
	inset: 0;
	background: rgba(6, 4, 16, 0.55);
	pointer-events: none;
}

/* ---------------------------------------------------------------------------
   Open square slideshow — the folder's images and clips cycle inside the opened
   bar. Two slides crossfade; the runtime owns which is active. The layer fades
   in only after the square has landed (same delay trick as the info panel), and
   the first slide is the bar image itself, so the handover is invisible. */

.vz-mag-slides {
	position: absolute;
	inset: 0;
	z-index: 1;
	border-radius: inherit;
	overflow: hidden;
	opacity: 0;
	transition: opacity var(--vz-mc-dur) ease-in-out;
	pointer-events: none;
}

.vz-mag-bar.is-open .vz-mag-slides {
	opacity: 1;
	transition-delay: var(--vz-mc-dur);
}

.vz-mag-slide {
	position: absolute;
	inset: 0;
	opacity: 0;
	/* Deliberately slower than --vz-mc-dur: this is the resting rhythm of the
	   open state, not a UI response. */
	transition: opacity 600ms ease-in-out;
}

.vz-mag-slide.is-active { opacity: 1; }

.vz-mag-slide > img,
.vz-mag-slide > video {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* ---------------------------------------------------------------------------
   Gallery — server-rendered for every project (AEO law). Baseline (no JS): a
   readable strip inside the panel. Enhanced: hidden, because the runtime has
   lifted these sources into the open square. */

.vz-mag-gallery {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: 12px;
	margin: 0 0 1em;
}

.vz-mag-gallery-item { margin: 0; }

.vz-mag-gallery-item img,
.vz-mag-gallery-item video {
	display: block;
	width: 100%;
	height: auto;
}

.is-enhanced .vz-mag-gallery { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE ≤781px — THE DECK (interpolation pass, 2026-09-04).

   Desktop is the premium tier; this is a TRANSLATION of the same material, not
   a pruned copy of it. What used to live here was the subtractive version —
   uniform bars, `display: none` on the project title, panels stacked but left
   `hidden` by the runtime, which meant [hidden]'s UA display:none made every
   word of this section invisible on a phone. Every unit now gets a named
   transformation instead (see .claude/skills/desktop-to-mobile-interpolation):

     · thin bar            → RECOMPOSE  full-width portrait tile
     · hover → background  → RECODE     the tile in the middle of the screen wins
     · click → open square → RECODE     no open state; arriving IS opening
     · background clip     → DISTIL     the project's own first still, blurred
                                        at the same 30px (zero extra bytes)
     · the row of bars     → DISTIL     a four-tick rail in the thumb zone
     · scroll magnet       → DROP       free scroll on mobile (§8, 2026-09-06)
     · blur(30px)          → RESCALE    --vz-mc-blur-near, scaled by tile area
     · backdrop/placeholder→ OMIT       they only ever served the open square

   Everything is scoped to .is-deck (added by the runtime), so with JS off the
   baseline single column still renders — readable and crawlable.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 781px) {

	/* ── 1 · Section: a deck, not a stage ──────────────────────────────────── */
	.vz-projects {
		min-height: 0; /* the 100svh stage is a desktop idea; here it is a cost */
		padding: clamp(3rem, 11vh, 4.5rem) 0 clamp(4rem, 14vh, 6rem);
		overflow-x: clip; /* clip, not hidden: does not create a scroll container */
	}

	.vz-projects .proj-intro {
		margin-bottom: clamp(1.5rem, 5vh, 2.5rem);
		padding-inline: var(--vz-mc-pad);
	}

	/* ── 2 · Interleave bar_i → panel_i into one tile per project ──────────── */
	.is-deck .vz-mag-stage {
		display: flex;
		flex-direction: column;
		gap: 0;
	}

	.is-deck .vz-mag-carousel,
	.is-deck .vz-mag-panels { display: contents; }

	/* --vz-i is emitted per pair by the block. Deliberately not :nth-child(): the
	   project count is editable in the admin repeater, and a fixed list would
	   break silently on the fifth project. */
	.is-deck .vz-mag-bar { order: calc(var(--vz-i, 0) * 2); }
	.is-deck .vz-mag-panel { order: calc(var(--vz-i, 0) * 2 + 1); }

	.vz-mag-carousel {
		flex-direction: column;
		flex-wrap: nowrap;
		align-items: stretch;
		gap: clamp(2rem, 8vw, 3rem);
		min-height: 0;
		padding: 0;
	}

	/* ── 3 · Ambient background: the distilled clip ────────────────────────── */
	.vz-mag-amb {
		order: -1;
		position: sticky;
		top: 0;
		height: 100svh;
		margin-bottom: -100svh; /* stays in view without occupying any height */
		z-index: 0;
		overflow: hidden;
		pointer-events: none;
	}

	.vz-mag-amb > span {
		position: absolute;
		inset: -8%; /* headroom so the blur does not bleed a soft edge inwards */
		background-size: cover;
		background-position: center;
		filter: blur(var(--vz-mc-blur)) saturate(1.15);
		opacity: 0;
		transition: opacity calc(var(--vz-mc-dur) * 2) var(--vz-ease);
	}

	.vz-mag-amb > span.is-on { opacity: 0.38; }

	/* ── 4 · The bar becomes a tile ────────────────────────────────────────── */
	.vz-mag-bar,
	.vz-mag-bar.is-expanded,
	.vz-mag-bar.is-open {
		--tile-blur: var(--vz-mc-blur-near);
		--tile-scale: 0.965;
		/* Bound the WIDTH by the height budget, not just `max-height`: capping the
		   height alone leaves a stretched block-level width behind and the portrait
		   tile silently goes landscape on wide phones and tablets. Solved for the
		   ratio instead, so 4/5 always survives. */
		width: min(100% - var(--vz-mc-pad) * 2, calc(var(--vz-mc-tile-max) * 4 / 5));
		height: auto;
		align-self: center;
		margin-inline: auto;
		aspect-ratio: var(--vz-mc-tile-ar);
		max-height: var(--vz-mc-tile-max);
		position: relative;
		z-index: 1;
		filter: blur(var(--tile-blur));
		opacity: 0.55;
		transform: scale(var(--tile-scale));
		transition:
			filter var(--vz-mc-dur) var(--vz-ease),
			opacity var(--vz-mc-dur) var(--vz-ease),
			transform var(--vz-mc-dur) var(--vz-ease);
		will-change: auto;
		touch-action: pan-y; /* the vertical axis is the browser's, always */
		/* (scroll-snap-align removed with the mobile snap, 2026-09-06 — see §8) */
	}

	.vz-mag-bar.is-active {
		--tile-blur: 0px;
		--tile-scale: 1;
		opacity: 1;
	}

	/* Touch has no hover, so it needs a sub-100ms answer of its own. */
	.vz-mag-bar:active { --tile-scale: 0.955; }

	/* Neutralise the desktop dim rule if the breakpoint was crossed mid-open. */
	.vz-mag-bar.is-dim {
		filter: blur(var(--tile-blur));
		opacity: 0.55;
	}

	/* The gradient stays (depth); the label goes because the panel's <h3> now
	   carries the title permanently, off the image, at real contrast. */
	.vz-mag-bar::before { opacity: 1; }
	.vz-mag-bar-label { display: none; }

	.vz-mag-slides { opacity: 1; }
	.vz-mag-bar.is-open .vz-mag-slides { transition-delay: 0s; }

	/* Segmented progress: without hover, "how many are there and where am I"
	   needs a permanent carrier. */
	.vz-mag-progress {
		position: absolute;
		top: 10px;
		left: 10px;
		right: 10px;
		z-index: 2;
		display: flex;
		gap: 4px;
		opacity: 0;
		transition: opacity var(--vz-mc-dur) var(--vz-ease);
		pointer-events: none;
	}

	.vz-mag-bar.is-active .vz-mag-progress { opacity: 1; }

	.vz-mag-seg {
		flex: 1;
		height: 2px;
		border-radius: 2px;
		background: rgba(255, 255, 255, 0.28);
		overflow: hidden;
	}

	.vz-mag-seg > i {
		display: block;
		height: 100%;
		background: #fff;
		transform: scaleX(0);
		transform-origin: left center;
	}

	.vz-mag-seg.is-done > i { transform: scaleX(1); }
	.vz-mag-seg.is-cur > i { animation: vz-mag-fill var(--seg-dur, 5000ms) linear forwards; }

	/* ── 5 · Panel: under the media, always on screen ──────────────────────── */
	.is-deck .vz-mag-panel {
		position: static;
		inset: auto;
		transform: none;
		width: auto;
		max-width: none;
		margin: 0;
		z-index: 1;
		/* The extra bottom space is the rail's footprint: it floats over the page,
		   so the copy needs somewhere to end that is not underneath it. */
		padding: clamp(0.9rem, 4vw, 1.15rem) var(--vz-mc-pad) clamp(5rem, 8vh + 4rem, 7rem);
		background: none;
		-webkit-backdrop-filter: none;
		backdrop-filter: none;
		border-radius: 0;
		opacity: 1;
		transition: opacity var(--vz-mc-dur) var(--vz-ease);
		transition-delay: 0s;
	}

	.is-deck .vz-mag-panel:not(.is-active) { opacity: 0.42; }

	/* Safety net for the AEO law: the deck controller never sets `hidden`, but if
	   anything ever does, the copy must not vanish on a phone. */
	.is-deck .vz-mag-panel[hidden] { display: block; }

	.vz-mag-panel-title {
		font-size: var(--vz-mc-fs-title);
		line-height: 1.2;
		margin: 0 0 0.4em;
	}

	.vz-mag-panel-desc {
		font-size: var(--vz-mc-fs-desc);
		line-height: 1.55;
		max-width: 34ch;
		margin: 0 0 0.9em;
	}

	/* Duplicate of the tile's media — same information, second decoder. */
	.is-deck .vz-mag-panel-media { display: none; }

	.vz-mag-panel-link {
		display: inline-flex;
		align-items: center;
		min-height: 44px;
		padding-inline: 2px;
		text-decoration: underline;
		text-underline-offset: 0.28em;
	}

	/* ── 6 · Rail: the bar row's index function, in the thumb zone ─────────── */
	.vz-mag-rail {
		position: fixed;
		left: 50%;
		translate: -50% 0;
		bottom: calc(14px + env(safe-area-inset-bottom));
		z-index: 6;
		display: flex;
		align-items: center;
		padding: 12px 20px;
		border-radius: 999px;
		background: rgba(10, 10, 16, 0.55);
		-webkit-backdrop-filter: blur(14px);
		backdrop-filter: blur(14px);
		opacity: 0;
		visibility: hidden;
		transition:
			opacity var(--vz-mc-dur) var(--vz-ease),
			visibility 0s linear var(--vz-mc-dur);
	}

	.vz-projects.is-in-view .vz-mag-rail {
		opacity: 1;
		visibility: visible;
		transition-delay: 0s, 0s;
	}

	.vz-mag-rail-ticks {
		display: flex;
		align-items: center;
		gap: 34px;
	}

	.vz-mag-tick {
		appearance: none;
		border: 0;
		padding: 0;
		cursor: pointer;
		width: 4px;
		height: 18px;
		border-radius: 2px;
		background: rgba(243, 241, 250, 0.34);
		position: relative;
		transition:
			height var(--vz-mc-dur) var(--vz-ease),
			background-color var(--vz-mc-dur) var(--vz-ease);
	}

	/* 44px of target without changing the 4px of drawing. */
	.vz-mag-tick::after {
		content: "";
		position: absolute;
		inset: 50% auto auto 50%;
		translate: -50% -50%;
		width: 44px;
		height: 44px;
	}

	.vz-mag-tick[aria-current="true"] {
		height: 26px;
		background: #fff;
	}

	.vz-mag-tick:focus-visible {
		outline: 2px solid #fff;
		outline-offset: 4px;
	}

	/* ── 7 · Desktop mechanisms that have no counterpart here ──────────────── */
	.vz-mag-backdrop,
	.vz-mag-placeholder { display: none; }

	/* ── 8 · No scroll magnet on mobile (Fabão ruling 2026-09-06) ──────────── */
	/* The native `y proximity` snap that used to live here grabbed on the
	   lightest touch — "mau toquei na tela ele já tá procurando alguma sessão
	   pra grudar". Mobile scrolls FREE until the dedicated mobile refinement
	   pass revisits it. scroll-padding-top stays: it still lands hash-anchor
	   jumps (nav links) under the fixed header. */
	html {
		scroll-padding-top: var(--vz-mc-head, 119px);
	}
}

@keyframes vz-mag-fill {
	from { transform: scaleX(0); }
	to { transform: scaleX(1); }
}

@media (prefers-reduced-motion: reduce) {
	.vz-mag-bar,
	.vz-mag-carousel.is-anim .vz-mag-bar,
	.vz-mag-slides,
	.vz-mag-slide,
	.vz-projects-bg-slot,
	.vz-mag-amb > span,
	.vz-mag-tick,
	.is-enhanced .vz-mag-panel { transition: none; }

	/* The progress bar still says how many items there are — it just stops
	   pretending anything is advancing on its own. */
	.vz-mag-seg.is-cur > i {
		animation: none;
		transform: scaleX(1);
	}

	/* The LIVE badge holds still; the red dot alone carries the signal. */
	.vz-mag-bar[data-vz-status="live"]::after { animation: none; }

	/* (html scroll-snap-type reset removed 2026-09-06 — no snap is set anywhere now) */
}
