/* ==========================================================================
   MZ Consult — shared system.

   Loaded on every route. Anything used by exactly one route lives in that
   route's own file instead.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Reset
   -------------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html {
	-webkit-text-size-adjust: 100%;

	/* `clip`, not `hidden`. overflow:hidden on html/body makes the body a
	   scroll container, which breaks nearest-scroller resolution for
	   position:sticky and for scroll-driven animation timelines. `clip`
	   contains the overflow without creating one. */
	overflow-x: clip;

	/* No global smooth-scroll: it makes the skip link glide, which is a real
	   accessibility annoyance, and it double-smooths the error scroll in
	   details.js. The two places that want it pass `behavior:'smooth'`
	   explicitly. The header is static, so this only needs to clear the
	   heading from the top edge. */
	scroll-padding-top: clamp(16px, 4vh, 40px);
}

body {
	margin: 0;
	background: var(--bg);
	color: var(--ink);
	font-family: var(--sans);
	font-size: var(--t-body);
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

/* Headings follow the brand site: Tenor Sans, uppercase, letterspaced. Tenor
   Sans ships weight 400 only — asking for 600 (as mohammedzohaib.com does)
   makes the browser synthesise a bold that smears the thin strokes at display
   size, so the weight stays at 400 and emphasis comes from size and tracking.
   Caps also need more leading than mixed case, not less. */
h1, h2, h3, h4 {
	margin: 0;
	font-family: var(--display);
	font-weight: 400;
	line-height: 1.16;
	letter-spacing: var(--track-display);
	text-transform: uppercase;
	text-wrap: balance;
}

p { margin: 0; }
img, svg, video { display: block; max-width: 100%; }
img, video { height: auto; }

ul, ol {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* :not(.btn) matters more than it looks.
   A button in this theme is often an <a>, and a bare `a:hover` is (0,1,1) —
   which outranks `.btn` at (0,1,0). So the global link hover was overriding
   the solid button's own colour and painting its label white on a white
   background. Excluding buttons here, and having .btn state its colour in
   both rest and hover below, means the two can never fight again.

   Colour cannot carry links any more (v2.3.0 — see tokens.css). It was
   --accent-text at rest and --ink on hover, and with both now white that is
   white on white: a link you cannot pick out of a paragraph, and a hover with
   no state change at all.

   So the ramp does the work instead. At rest a link is one step down from body
   copy and underlined — the underline is what says "link", which is the
   accessible answer regardless of palette. Hover brings it up to full white
   AND doubles the underline, so the change is legible without colour and
   survives greyscale, low vision and every kind of colour blindness. */
a:not(.btn) {
	color: var(--ink-2);
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	transition: color var(--dur-fast) var(--ease), text-decoration-color var(--dur-fast) var(--ease);
}
a:not(.btn):hover { color: var(--ink); text-decoration-thickness: 2px; }

button { font: inherit; color: inherit; }

::selection { background: var(--accent); color: #050505; }

/* Focus: visible on every interactive thing, and never only a colour change. */
:focus-visible {
	outline: 2px solid var(--accent-text);
	outline-offset: 3px;
	border-radius: 4px;
}

.screen-reader-text {
	position: absolute !important;
	width: 1px; height: 1px;
	margin: -1px; padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.skip {
	position: absolute;
	top: -60px;
	left: 50%;
	z-index: 99;
	transform: translateX(-50%);
	padding: 11px 22px;
	border-radius: 0 0 10px 10px;
	background: var(--ink);
	color: var(--bg);
	font-size: var(--t-small);
	font-weight: 600;
	text-decoration: none;
	transition: top var(--dur) var(--ease);
}
.skip:focus { top: 0; color: var(--bg); }

/* --------------------------------------------------------------------------
   1b. Cross-document transitions

   The funnel is four URLs describing one continuous act. This makes the
   navigation between them read that way: the page crossfades, and the brand
   lockup and the step rail are declared as the *same object* on both sides, so
   the lockup physically shrinks from its hero size to its inner-page size
   instead of being torn down and redrawn.

   Twelve lines, no JavaScript, and a silent no-op in browsers that have not
   shipped it (Firefox at time of writing) — they simply get a normal
   navigation.
   -------------------------------------------------------------------------- */

@view-transition { navigation: auto; }

::view-transition-old(root) { animation: mzc-vt-out 200ms var(--ease-in) both; }
::view-transition-new(root) { animation: mzc-vt-in  420ms var(--ease) 60ms both; }

@keyframes mzc-vt-out { to   { opacity: 0; transform: translateY(-8px); } }
@keyframes mzc-vt-in  { from { opacity: 0; transform: translateY(var(--travel)); } }

.brand { view-transition-name: mzc-brand; }
.rail  { view-transition-name: mzc-rail; }

::view-transition-group(mzc-brand),
::view-transition-group(mzc-rail) {
	animation-duration: 480ms;
	animation-timing-function: var(--ease);
}

/* --------------------------------------------------------------------------
   2. Layout
   -------------------------------------------------------------------------- */

.wrap {
	width: 100%;
	max-width: var(--max);
	margin-inline: auto;
	padding-inline: var(--pad);
}

/* Prose sits at a readable measure regardless of the container.

   em, not ch. `1ch` is the width of "0", which in these faces is about 0.6em,
   while the average character is nearer 0.5em — so a `68ch` cap measured out
   at 90 characters a line on the hero. 34em lands at roughly 66-70, which is
   the range that was actually wanted. */
.details__sub,
.scaffold__body,
.form__consent {
	max-width: 34em;
}

/* --------------------------------------------------------------------------
   3. Header
   -------------------------------------------------------------------------- */

.site-head {
	position: relative;
	z-index: 20;
	padding-block: clamp(20px, 3vw, 34px) 0;
}

.site-head__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--s-s);
}

/* a.brand, not .brand: the generic `a:not(.btn)` rule above is (0,1,1) and a
   bare `.brand` is (0,1,0), so the link colour was winning and the fallback
   brand mark rendered in muted grey instead of white. Invisible while a logo
   image is set — which it is — and wrong the moment one is not. */
a.brand {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	gap: 9px;
	color: var(--ink);
	text-decoration: none;
	transition: opacity var(--dur) var(--ease);
}
a.brand:hover { opacity: .82; color: var(--ink); }

.brand__mark svg { display: block; }
.brand--sm .brand__mark svg { width: 26px; height: 17px; }

/* The logo is a full lockup including the wordmark, so it carries the whole
   identity on its own and is sized accordingly — the three-bar fallback below
   is only a mark and sits above separate text. */
.brand__logo { width: auto; height: 48px; }
.brand--sm .brand__logo { height: 34px; }

@media (max-width: 560px) {
	.brand__logo { height: 40px; }
	.brand--sm .brand__logo { height: 30px; }

	/* Keep the lockup a 44px target even when the mark itself is smaller. */
	.brand--sm { padding-block: 7px; }
}

.brand__name {
	color: var(--ink);
	font-size: var(--t-micro);
	font-weight: 600;
	letter-spacing: .28em;
	text-transform: uppercase;
	/* Letterspaced caps leave a phantom gap on the right; pull it back. */
	margin-right: -.28em;
}
.brand--sm .brand__name { font-size: 11px; letter-spacing: .24em; margin-right: -.24em; }

/* ---- Step rail ---------------------------------------------------------- */

.rail {
	display: flex;
	align-items: center;
	gap: clamp(10px, 3vw, 26px);
	margin: 0;
	padding: 0;
}

.rail__step {
	display: flex;
	align-items: center;
	gap: 8px;
	color: var(--ink-3);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .1em;
	text-transform: uppercase;
}

.rail__step + .rail__step::before {
	content: "";
	width: clamp(14px, 4vw, 40px);
	height: 1px;
	margin-right: clamp(10px, 3vw, 26px);
	background: var(--line-2);
}

.rail__dot {
	position: relative;
	width: 7px;
	height: 7px;
	border: 1px solid var(--line-2);
	border-radius: 50%;
	flex: none;
}

.rail__step.is-done { color: var(--ink-2); }
.rail__step.is-done .rail__dot { background: var(--line-3); border-color: var(--line-3); }

.rail__step.is-now { color: var(--accent-text); }
.rail__step.is-now .rail__dot { background: var(--accent); border-color: var(--accent); }
.rail__step.is-now .rail__dot::after {
	content: "";
	position: absolute;
	inset: -5px;
	border: 1px solid var(--accent);
	border-radius: 50%;
	opacity: .38;
}

/* ---- Advance ------------------------------------------------------------
   The authored moment on the two inner pages. The connector draws into the
   current step, the dot lands, then the halo breathes once — 60ms to 1400ms,
   exactly one --dur-cine.

   This is the only motion in the theme that tells the funnel's story: it says
   "you moved forward", which is the precise reassurance a seven-question form
   after a booking needs. Pure CSS on load, no JS, and if it never runs the
   worst case is the static rail that was there before.
   ------------------------------------------------------------------------- */

.rail__step.is-now::before {
	transform-origin: left;
	animation: mzc-draw-x 380ms var(--ease) 60ms both;
}
.rail__step.is-now .rail__dot {
	animation: mzc-dot-land 420ms var(--ease) 360ms both;
}
.rail__step.is-now .rail__dot::after {
	animation: mzc-halo 700ms var(--ease) 700ms both;
}

@keyframes mzc-draw-x  { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes mzc-dot-land { from { transform: scale(.3); } to { transform: scale(1); } }
@keyframes mzc-halo {
	from { transform: scale(.4); opacity: 0; }
	60%  { opacity: .55; }
	to   { transform: scale(1); opacity: .38; }
}

@media (max-width: 560px) {
	.rail__label { display: none; }
	.rail__step + .rail__step::before { width: 22px; margin-right: 10px; }
}

/* --------------------------------------------------------------------------
   4. Typography helpers
   -------------------------------------------------------------------------- */

.sec-title {
	font-size: var(--t-h2);
	margin-bottom: var(--s-m);
}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */

.btn {
	/* Literal fallbacks on every one of these, and on the properties that read
	   them, for one specific reason:
	   `color: var(--btn-fg)` where `--btn-fg: var(--bg)` is a chain. If --bg
	   ever fails to resolve — tokens.css blocked, cached stale, or loaded out
	   of order — the whole declaration becomes invalid at computed-value time
	   and `color` falls back to INHERITED, which on this page is white. White
	   text on a white button. A hard fallback makes that impossible. */
	--btn-bg: transparent;
	--btn-fg: var(--ink, #ffffff);
	--btn-bd: var(--line-3, #666666);

	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	min-height: 52px;
	padding: 15px 30px;
	border: 1px solid var(--btn-bd, #666666);
	border-radius: var(--pill, 999px);
	background: var(--btn-bg, transparent);
	color: var(--btn-fg, #ffffff);
	font-size: var(--t-small);
	font-weight: 600;
	letter-spacing: .01em;
	line-height: 1.2;
	text-align: center;
	text-decoration: none;
	cursor: pointer;

	/* Removes the 300ms tap delay and double-tap-zoom on mobile. This matters
	   most on the submit button, which is the highest-stakes tap on the site. */
	touch-action: manipulation;

	transition:
		transform 220ms var(--ease),
		box-shadow var(--dur) var(--ease),
		background-color var(--dur-fast) var(--ease),
		border-color var(--dur-fast) var(--ease),
		color var(--dur-fast) var(--ease);
}

/* Hover is guarded everywhere in this file. Without the query, iOS latches
   :hover after a tap and the hover state stays stuck until you tap elsewhere —
   most visible on the choice pills, where a red hover border sits fighting the
   white selected state. */
@media (hover: hover) {
	.btn:hover { transform: translateY(-2px); }
}

/* Down fast, up slow with an overshoot. That asymmetry is the whole physics —
   no spring library, two declarations. */
/* The button owns its colour in every state, at a specificity that beats any
   generic element rule. */
.btn,
.btn:hover,
.btn:focus,
.btn:active { color: var(--btn-fg, #ffffff); }

.btn:active {
	transform: translateY(0) scale(.978);
	box-shadow: var(--lift-1);
	transition-duration: var(--dur-micro);
}

.btn--solid {
	--btn-bg: var(--ink, #ffffff);
	--btn-fg: var(--bg, #050505);
	--btn-bd: var(--ink, #ffffff);
	box-shadow: var(--lift-2);
}
@media (hover: hover) {
	.btn--solid:hover {
		--btn-bg: #ffffff;
		--btn-bd: #ffffff;
		--btn-fg: var(--bg, #050505);
		box-shadow: var(--lift-3);
	}
}

.btn--ghost { --btn-bd: var(--line-3); --btn-fg: var(--ink); }
@media (hover: hover) {
	.btn--ghost:hover {
		--btn-bd: var(--accent, #ffffff);
		--btn-fg: var(--accent-text, #ffffff);
		background: rgba(255, 255, 255, .08);
	}
}

.btn--wide { width: 100%; }

.btn[disabled],
.btn[aria-disabled="true"] {
	opacity: .45;
	cursor: not-allowed;
	transform: none;
	box-shadow: none;
}

/* Submitting state — set by details.js so a slow network cannot produce a
   double submission, and the visitor can see why nothing is happening yet.

   The spinner is absolutely positioned, not a flex child. As a flex child it
   added 15px plus the 10px gap to the button's contents and shoved the label
   sideways at the exact moment of the most important click on the site.

   No opacity dip either: a faded button reads as broken rather than working. */
.btn.is-busy { pointer-events: none; }

.btn.is-busy::after {
	content: "";
	position: absolute;
	right: 18px;
	top: calc(50% - 8px);
	width: 16px;
	height: 16px;
	border: 2px solid currentColor;
	border-right-color: transparent;
	border-radius: 50%;
	animation: mzc-spin 640ms linear infinite;
}

/* An indeterminate progress hairline that asymptotes. The curve reaches ~70%
   in two seconds then creeps, which is the honest shape for a wait whose
   length nobody knows. details.js releases the button at 10s; this lands just
   under that. */
.btn.is-busy::before {
	content: "";
	position: absolute;
	left: 24px;
	right: 24px;
	bottom: 9px;
	height: 2px;
	background: var(--accent);
	transform: scaleX(0);
	transform-origin: left;
	animation: mzc-progress 9s cubic-bezier(.09, .79, .18, 1) forwards;
}

@keyframes mzc-spin { to { transform: rotate(360deg); } }
@keyframes mzc-progress { from { transform: scaleX(0); } to { transform: scaleX(.94); } }

/* --------------------------------------------------------------------------
   6. Badges
   -------------------------------------------------------------------------- */

.badges {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 9px;
	margin-top: clamp(12px, 1.8vw, 20px);
}

.badge {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	padding: 7px 15px 7px 12px;
	border: 1px solid var(--line);
	border-radius: var(--pill);
	background: rgba(255, 255, 255, .025);
	color: var(--ink-2);
	font-size: var(--t-micro);
	font-weight: 500;
	letter-spacing: .01em;
}

.badge__tick { color: var(--accent); flex: none; }

/* --------------------------------------------------------------------------
   7. Video player

   A framed player rather than a bare <video>: hairline border, four corner
   ticks, and a play control that hands over to the native controls the moment
   playback starts.
   -------------------------------------------------------------------------- */

.player {
	position: relative;
	margin: 0;
	width: 100%;
}

.player__frame {
	position: relative;
	border: 1px solid var(--line-2);
	border-radius: var(--radius);
	background: #000;
	overflow: hidden;
	box-shadow: var(--lift-3);
	transition:
		box-shadow var(--dur) var(--ease),
		transform var(--dur) var(--ease);
}

/* A 1px hairline in any of these reds is under 4:1 on this black — a hover
   cannot see. A layered shadow carries it instead, and composites as one
   paint. */
@media (hover: hover) {
	.player:hover .player__frame {
		transform: translateY(-3px);
		box-shadow:
			var(--lift-3),
			0 0 0 1px rgba(255, 255, 255, .30),
			0 26px 90px -32px rgba(255, 255, 255, .16);
	}
}

/* ---- The aperture -------------------------------------------------------
   Two opaque bars with an accent hairline on their meeting edges, which
   retract to reveal the video. This is the authored moment on the landing
   page, and it is specific to this content in a way a fade is not: the product
   here is a video by a man who makes videos, the frame is already drawn as an
   optical instrument (four corner ticks, a drawn play triangle), so a cinema
   masking bar opening is the literal semantics of the thing.

   Critically it animates the *mask*, not the image. The poster paints at full
   opacity from the first frame, so it still registers as the LCP element —
   Chrome ignores opacity:0 elements as LCP candidates but does not do
   occlusion testing, so covering costs nothing and fading costs ~400ms.

   Default state is OPEN. If CSS half-loads or the animation never runs, the
   video is uncovered. The keyframe closes it; nothing else does.
   ------------------------------------------------------------------------- */
.player__bar {
	position: absolute;
	left: 0;
	right: 0;
	height: 50%;
	z-index: 3;
	background: #000;
	pointer-events: none;
}
.player__bar--t { top: 0;    border-bottom: 1px solid var(--accent); transform: translateY(-100%); }
.player__bar--b { bottom: 0; border-top:    1px solid var(--accent); transform: translateY(100%); }

/* Translate, never scaleY — scaling would smear the 1px hairline into a
   sub-pixel blur. */
@keyframes mzc-aperture-t { from { transform: translateY(0); } to { transform: translateY(-100%); } }
@keyframes mzc-aperture-b { from { transform: translateY(0); } to { transform: translateY(100%); } }

/* contain, not cover.

   The frame is locked to 16/9 so the page can reserve its space before the
   file arrives — that is what stops the layout jumping on load. Both current
   clips are exactly 1280x720, so cover and contain render identically today.

   They differ the moment someone uploads a clip that is not precisely 16:9:
   cover fills the frame by cropping the edges, contain fits the whole frame
   and letterboxes. On a page whose video is one man talking to camera,
   silently trimming the sides of his face is the worse failure — better a
   black bar than a crop nobody notices until the client does. */
.player__video {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	object-fit: contain;
	background: #000;
}

/* Corner ticks — drawn, and they retract once the video is playing so they
   never sit over the native control bar. */
.player__tick {
	position: absolute;
	width: 22px;
	height: 22px;
	border: 0 solid var(--accent);
	opacity: .75;
	pointer-events: none;
	transition: opacity var(--dur) var(--ease);
}
.player__tick--tl { top: 11px; left: 11px;  border-top-width: 1px; border-left-width: 1px; }
.player__tick--tr { top: 11px; right: 11px; border-top-width: 1px; border-right-width: 1px; }
.player__tick--bl { bottom: 11px; left: 11px;  border-bottom-width: 1px; border-left-width: 1px; }
.player__tick--br { bottom: 11px; right: 11px; border-bottom-width: 1px; border-right-width: 1px; }

.player.is-playing .player__tick { opacity: 0; }

/* The four ticks push outward: the frame opening. No other video component has
   this gesture available, because no other one has drawn corner ticks. */
@media (hover: hover) {
	.player__tick { transition: transform var(--dur) var(--ease), opacity var(--dur-fast) var(--ease); }
	.player:hover .player__tick { opacity: 1; }
	.player:hover .player__tick--tl { transform: translate(-3px, -3px); }
	.player:hover .player__tick--tr { transform: translate( 3px, -3px); }
	.player:hover .player__tick--bl { transform: translate(-3px,  3px); }
	.player:hover .player__tick--br { transform: translate( 3px,  3px); }
}

.player__play {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	width: 100%;
	border: 0;
	/* Deeper than it was. The clip opens on a bright office window, and at 34%
	   the old scrim left a white triangle on a near-white background. */
	background: radial-gradient(62% 62% at 50% 50%, rgba(5, 5, 5, .58), rgba(5, 5, 5, .18));
	cursor: pointer;
	transition: opacity var(--dur) var(--ease);
}
.player.is-playing .player__play { opacity: 0; pointer-events: none; }

/* ---- Layering inside the play control -----------------------------------
   These three sit in one grid cell, and DOM order is NOT enough to order them.
   The ring carries backdrop-filter, which promotes it into its own stacking
   context — so it painted on top of the triangle that follows it in the
   markup, and its 74%-opaque black simply hid the icon. The button looked
   like an empty circle.

   Grid items honour z-index even at position: static, so three explicit
   values settle it for good.
   ------------------------------------------------------------------------- */
.player__play-ring {
	grid-area: 1 / 1;
	z-index: 0;
	width: 74px;
	height: 74px;
	/* Nearly opaque, not a tint. This has to read as a control against any
	   frame the video happens to open on. */
	border: 2px solid rgba(255, 255, 255, .92);
	border-radius: 50%;
	background: rgba(5, 5, 5, .74);
	backdrop-filter: blur(7px);
	-webkit-backdrop-filter: blur(7px);
	box-shadow: 0 10px 34px -8px rgba(0, 0, 0, .85);
	transition: transform var(--dur-slow) var(--ease), background-color var(--dur) var(--ease);
}
@media (hover: hover) {
	.player__play:hover .player__play-ring { transform: scale(1.07); background: rgba(255, 255, 255, .22); }
}
.player__play:active .player__play-ring { transform: scale(.94); transition-duration: var(--dur-micro); }

/* The triangle is drawn, not a ▶ character. */
.player__play-icon {
	grid-area: 1 / 1;
	z-index: 1;
	width: 0;
	height: 0;
	margin-left: 5px;
	border-top: 10px solid transparent;
	border-bottom: 10px solid transparent;
	border-left: 17px solid #fff;
	transition: opacity var(--dur-fast) var(--ease);
}

/* Buffering. The player ships preload="none", so the first press has to fetch
   the file header before a frame appears. Without this the button just sits
   there and people press it again. */
.player__spinner {
	grid-area: 1 / 1;
	z-index: 2;
	width: 30px;
	height: 30px;
	border: 2px solid rgba(255, 255, 255, .3);
	border-top-color: #fff;
	border-radius: 50%;
	opacity: 0;
	transition: opacity var(--dur-fast) var(--ease);
}

.player.is-buffering .player__play-icon { opacity: 0; }
.player.is-buffering .player__spinner {
	opacity: 1;
	animation: mzc-spin 700ms linear infinite;
}

/* ---- "Tap for sound" ----------------------------------------------------
   An autoplaying clip is necessarily muted (see mzc_video_player), so this is
   the only route back to the audio, and it has to be found without being
   hunted for. Top right: away from the native control bar along the bottom,
   away from the speaker's face in the middle.

   `hidden` has to be honoured explicitly — `display: inline-flex` on the base
   rule would otherwise override the attribute and show the button on a video
   that never autoplayed. */
.player__sound[hidden] { display: none; }

.player__sound {
	position: absolute;
	top: 14px;
	right: 14px;
	z-index: 3;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 9px 15px;
	border: 1px solid rgba(255, 255, 255, .34);
	border-radius: var(--pill);
	/* Near-opaque rather than a tint: this sits over whatever frame the video
	   happens to be showing, and it has to stay legible over all of them. */
	background: rgba(5, 5, 5, .74);
	-webkit-backdrop-filter: blur(7px);
	        backdrop-filter: blur(7px);
	color: #ffffff;
	font-family: var(--sans);
	font-size: var(--t-small);
	font-weight: 500;
	line-height: 1;
	cursor: pointer;
	box-shadow: var(--lift-2);
	transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

@media (hover: hover) {
	.player__sound:hover {
		border-color: var(--accent, #ffffff);
		background: rgba(5, 5, 5, .9);
		transform: translateY(-1px);
	}
}

.player__sound:active { transform: scale(.96); transition-duration: var(--dur-micro); }

/* A slow breath on the ring around it, not on the button. Movement is what
   makes it findable; moving the label itself would make it hard to hit. */
.player__sound::after {
	content: "";
	position: absolute;
	inset: -1px;
	border: 1px solid var(--accent, #ffffff);
	border-radius: inherit;
	opacity: 0;
	pointer-events: none;
	animation: mzc-sound-pulse 2.6s var(--ease-soft) 1.2s 3 both;
}

@keyframes mzc-sound-pulse {
	0%, 100% { opacity: 0; transform: scale(1); }
	38%      { opacity: .8; transform: scale(1.08); }
}

@media (max-width: 640px) {
	.player__play-ring { width: 60px; height: 60px; }
	.player__tick { width: 15px; height: 15px; }
	.player__sound { top: 10px; right: 10px; padding: 8px 12px; font-size: var(--t-micro); }
}

/* --------------------------------------------------------------------------
   8. Footer

   A drawn city skyline lived here first, then a red ember replaced it. Both
   are gone.

   The skyline went because a silhouette of towers is the most predictable
   image in real estate and it read as a bar chart at these surface values.

   The ember went because the colour was never Mohammed's. #720000 was sampled
   off mohammedzohaib.com, but from the one element on that page belonging to
   another company — the "Visit 3X Capital" button. Checked against the live
   site: rgb(114, 0, 0) appears there exactly twice, on that button and its
   arrow, and nowhere else. His own palette is black, white and grey, with no
   background colour anywhere. So the footer is black, like his is.
   -------------------------------------------------------------------------- */

.site-foot { position: relative; }

.site-foot__inner { position: relative; z-index: 1; }

/* A single hairline where a section changes tone, instead of a drawn seam.
   (.book was the inline booking panel; it was removed in v2.0.0 when the two
   buttons moved to a Calendly popup. The stats band inherits the seam.) */
.stats { border-top: 1px solid var(--line); }

/* --------------------------------------------------------------------------
   9. Form controls
   -------------------------------------------------------------------------- */

.field {
	margin: 0 0 var(--s-s);
	padding: 0;
	border: 0;
}

.field__label {
	display: block;
	margin-bottom: 9px;
	padding: 0;
	color: var(--ink);
	font-size: var(--t-small);
	font-weight: 600;
	letter-spacing: .005em;
}

legend.field__label { margin-bottom: 11px; }

.req { color: var(--accent-text); margin-left: 2px; }

.field__optional {
	margin-left: 6px;
	color: var(--ink-3);
	font-size: var(--t-micro);
	font-weight: 400;
	letter-spacing: .06em;
	text-transform: uppercase;
}

.field__input {
	width: 100%;
	min-height: 52px;
	padding: 14px 16px;
	border: 1px solid var(--line-3);
	border-radius: 10px;
	background: rgba(255, 255, 255, .028);
	color: var(--ink);
	font-family: inherit;
	font-size: var(--t-body);
	line-height: 1.45;
	/* 180ms, not 320. A focus indicator you can out-type is not an indicator. */
	transition:
		border-color var(--dur-fast) var(--ease),
		background-color var(--dur-fast) var(--ease),
		box-shadow var(--dur-fast) var(--ease);
}

.field__input::placeholder { color: var(--ink-3); opacity: 1; }

@media (hover: hover) {
	.field__input:hover { border-color: #8a8a8a; }
}

/* The field's own indicator (red border + red glow) is stronger than the
   generic outline, so the generic one stands down for this one control.
   Otherwise focusing a field paints three concentric rings. */
.field__input:focus-visible { outline: none; }

.field__input:focus {
	outline: none;
	border-color: var(--accent);
	background: rgba(255, 255, 255, .05);
	box-shadow: 0 0 0 3px rgba(255, 255, 255, .18), var(--lift-1);
}

.field__input--area { min-height: 96px; resize: vertical; }

.field--error .field__input { border-color: var(--alert); }
.field--error .field__input:focus { box-shadow: 0 0 0 3px rgba(224, 101, 79, .2), var(--lift-1); }

.field__error {
	margin-top: 7px;
	color: #f19684;
	font-size: var(--t-small);
	font-weight: 500;
}

.field__help {
	margin-top: 7px;
	color: var(--ink-3);
	font-size: var(--t-small);
}

/* ---- Choice pills ------------------------------------------------------- */

.choices {
	display: flex;
	flex-wrap: wrap;
	gap: 9px;
}

/* The radio stays in the accessibility tree and keeps keyboard behaviour;
   only its painted box is removed. */
.choices__input {
	position: absolute;
	width: 1px; height: 1px;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
}

.choices__pill {
	position: relative;
	isolation: isolate;
	display: inline-flex;
	align-items: center;
	min-height: 46px;
	padding: 11px 20px;
	border: 1px solid var(--line-3);
	border-radius: var(--pill);
	background: rgba(255, 255, 255, .022);
	color: var(--ink-2);
	font-size: var(--t-small);
	/* Fixed at 500 in BOTH states. A weight change on :checked alters the
	   pill's width, and in a wrapping flex row that can re-wrap the whole
	   group under the user's finger. Colour inversion is contrast enough. */
	font-weight: 500;
	cursor: pointer;
	touch-action: manipulation;
	transition:
		border-color var(--dur-fast) var(--ease),
		color var(--dur-fast) var(--ease),
		transform var(--dur) var(--ease);
}

/* The fill wipes in from the left edge. clip-path carrying its own corner
   radius, not scaleX — scaling a pill distorts its rounded ends for the whole
   320ms. */
.choices__pill::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	border-radius: inherit;
	background: var(--ink);
	clip-path: inset(0 100% 0 0 round var(--pill));
	transition: clip-path var(--dur) var(--ease);
}

@media (hover: hover) {
	.choices__pill:hover { border-color: var(--accent); color: var(--ink); transform: translateY(-1px); }
}

.choices__pill:active { transform: scale(.97); transition-duration: var(--dur-micro); }

.choices__input:checked + .choices__pill {
	border-color: var(--ink);
	color: var(--bg);
	box-shadow: var(--lift-1);
}
.choices__input:checked + .choices__pill::before { clip-path: inset(0 0 0 0 round var(--pill)); }

/* The commit: a small compression felt *after* the click. Armed by details.js
   on first interaction, so answers restored after a validation error do not
   all pop at once on page load. */
.pills-armed .choices__input:checked + .choices__pill {
	animation: mzc-commit var(--dur) var(--ease);
}

@keyframes mzc-commit {
	0%   { transform: scale(1); }
	34%  { transform: scale(.964); }
	100% { transform: scale(1); }
}

.choices__input:focus-visible + .choices__pill {
	outline: 2px solid var(--accent-text);
	outline-offset: 3px;
}

.field--error .choices__pill { border-color: rgba(224, 101, 79, .55); }

/* Honeypot. Off-screen rather than display:none — some bots skip hidden
   inputs, and this one is meant to be filled in. */
.hp {
	position: absolute !important;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* --------------------------------------------------------------------------
   10. Notices
   -------------------------------------------------------------------------- */

.alert,
.notice-inline {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	margin: 0 0 var(--s-s);
	padding: 14px 18px;
	border: 1px solid rgba(224, 101, 79, .42);
	border-radius: 10px;
	background: rgba(224, 101, 79, .09);
	color: #f6b6a8;
	font-size: var(--t-small);
	text-align: left;
}

.notice-inline {
	border-color: var(--line-2);
	background: rgba(255, 255, 255, .03);
	color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   11. Footer
   -------------------------------------------------------------------------- */

.site-foot {
	position: relative;
	margin-top: var(--s-sec);
	padding-block: var(--s-l) var(--s-m);
	background: var(--bg);
}

.site-foot__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 8px;
	text-align: center;
}

.site-foot__brand {
	color: var(--ink);
	font-size: var(--t-micro);
	font-weight: 600;
	letter-spacing: .26em;
	text-transform: uppercase;
	margin-right: -.26em;
}

.site-foot__addr,
.site-foot__wa { color: var(--ink-2); font-size: var(--t-small); }

/* A 17px-tall inline link is a miss on a phone, and this one is the "I have a
   question before I book" escape hatch. Give it a real target. */
.site-foot__wa a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding-inline: 4px;
}

.foot-note {
	margin-top: var(--s-xs);
	max-width: 56ch;
	color: var(--ink-3);
	font-size: var(--t-micro);
	line-height: 1.6;
}

/* --------------------------------------------------------------------------
   12. Scaffold — pages outside the funnel
   -------------------------------------------------------------------------- */

.scaffold { padding-block: var(--s-sec); }

.scaffold__inner { max-width: var(--narrow); }
.scaffold__inner--center { text-align: center; margin-inline: auto; }

.scaffold__title { font-size: var(--t-h2); margin-bottom: var(--s-s); }

.scaffold__body { color: var(--ink-2); }
.scaffold__body p { margin-bottom: 1em; }
.scaffold__body h2 { font-size: var(--t-h3); margin: 1.6em 0 .5em; color: var(--ink); }

.scaffold__back { margin-top: var(--s-m); }

/* --------------------------------------------------------------------------
   13. Reveal on scroll

   Deliberately restrained and identical everywhere it is used: the one
   authored moment on this site is the hero entrance in front.css. Groups rise
   once, from an already-visible default so a failed observer leaves content
   readable rather than invisible.
   -------------------------------------------------------------------------- */

.js [data-reveal] {
	opacity: 0;
	transform: translateY(var(--travel));
	transition:
		opacity var(--dur-slow) var(--ease),
		transform var(--dur-slow) var(--ease);
	transition-delay: calc(var(--i, 0) * var(--step, 70ms));
}

/* ---- The failsafe, and why it is scoped this way -------------------------
   Hiding content in CSS and revealing it in JS means any script failure after
   the `.js` class is set leaves the page permanently blank below the hero. So
   there has to be a rescue.

   But a rescue on a plain timer defeats the whole reveal layer. An earlier
   version fired at 2.5s on EVERY [data-reveal], in viewport or not — so
   anything below the fold had already revealed itself long before the visitor
   scrolled to it, and every scroll animation on the site was dead code.

   The fix is a liveness handshake instead of a timer. main.js adds
   `.reveal-live` to <html> as the last statement of initReveal(), after every
   observe() call has succeeded. If construction or any observe throws, the
   class never lands and the timer still saves the page. If it succeeds, this
   rule stops matching and the timer can never fire.
   ------------------------------------------------------------------------- */
.js:not(.reveal-live) [data-reveal] {
	animation: mzc-failsafe 1ms linear 2200ms forwards;
}

@keyframes mzc-failsafe {
	to { opacity: 1; transform: none; }
}

.js [data-reveal].is-in {
	animation: none;
	opacity: 1;
	transform: none;
}

/* --------------------------------------------------------------------------
   14. Reduced motion

   Removes travel and duration, keeps every end state. Nothing becomes
   unreachable or invisible.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {

	/* 1. Collapse the system at its source. Every duration authored against a
	      token goes to zero and every travel distance goes to zero, so the
	      choreography structure survives and only the movement is gone. */
	:root {
		--dur-micro: 0ms;
		--dur-fast:  0ms;
		--dur:       0ms;
		--dur-slow:  0ms;
		--dur-cine:  0ms;
		--travel:    0px;
		--travel-lg: 0px;
	}

	html { scroll-behavior: auto; }

	/* 2. Backstop for anything hard-coded. Note the DELAYS: without zeroing
	      them, an element with a 900ms delay and a 1ms duration sits in its
	      from-state — invisible — for nearly a second. The previous version of
	      this block zeroed durations only, and worked purely by accident. */
	*,
	*::before,
	*::after {
		animation-duration: 1ms !important;
		animation-delay: 0ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 1ms !important;
		transition-delay: 0ms !important;
		scroll-behavior: auto !important;
	}

	/* 3. Explicit end state for every from-state that hides something. */
	.js [data-reveal] { opacity: 1; transform: none; }
	.js .player__bar--t { transform: translateY(-100%); }
	.js .player__bar--b { transform: translateY(100%); }
	.next__item::after,
	.stat::before,
	.rail__step.is-now::before { transform: none; }
	.next__num { transform: none; opacity: 1; }
	.choices__input:checked + .choices__pill::before { clip-path: inset(0 round var(--pill)); }
	.btn:hover { transform: none; }

	/* 4. Keep the two loops that are information rather than decoration. A
	      16px spinner in a fixed position triggers nothing vestibular, and
	      removing it would leave a submitted form looking frozen. */
	.btn.is-busy::after {
		animation-duration: 900ms !important;
		animation-iteration-count: infinite !important;
	}
	.btn.is-busy::before { animation: none !important; transform: scaleX(.5); }

	/* 5. Cross-document transitions. */
	::view-transition-group(*),
	::view-transition-old(*),
	::view-transition-new(*) { animation: none !important; }
}
