/* ==================================================================
   nvtunes.com — the whole stylesheet.
   ------------------------------------------------------------------
   One file, one request, explicit @layer order. It replaces Canvas's
   eleven-stylesheet, ~850 KB stack: bootstrap.css, style.css, dark.css
   (which matched zero elements on the page), animate.css (loaded for a
   single keyframe), font-icons.css (123 KB for ten glyphs), swiper,
   magnific-popup, music.css, fonts.css, bs-switches and custom.css.

   Why one file rather than five @imports: imports are only discovered
   after this file parses, which costs an extra round trip on the LCP
   path, and CSS is now the only render-blocking resource left. Split it
   when it passes ~1500 lines.

   The token block and its contrast measurements are lifted from the old
   custom.css, which was a documented audit log rather than a stylesheet
   and is the one part of the Canvas build worth keeping. Read the
   comments in @layer tokens before retuning any colour.
   ================================================================== */

@layer tokens, base, layout, components, motion, utilities;

/* ==================================================================
   TOKENS
   ================================================================== */
@layer tokens {

	:root {
		/* --- Palette -------------------------------------------------
		   --nv-surface used to be load-bearing: it matched music.css's
		   .bg-color2 exactly because three #content gradients hard-coded
		   rgba(19,23,34,...) to blend into it. music.css is gone, so that
		   coupling is gone with it and these are now free to move. */
		--nv-bg:         #0B0D12;
		--nv-surface:    #131722;
		--nv-surface-2:  #1B2030;
		--nv-surface-3:  #232A3D;
		--nv-border:     rgba(255, 255, 255, .10);
		--nv-border-hi:  rgba(255, 255, 255, .22);

		--nv-text:       #F2F3F5;   /* 15.8:1 on --nv-surface */
		--nv-text-dim:   #A0A6B4;   /*  7.3:1 on --nv-surface */
		--nv-text-faint: #6B7382;   /*  4.0:1 — decoration and big numerals only */

		/* Three tones of one brand orange, because a single hex cannot be
		   both on-brand and readable everywhere. Measured (WCAG 2.1):

		     #bc681a on #131722  4.38:1  fails AA for body text
		     #E8862B on #131722  6.71:1  passes AA for body text
		     #FFFFFF on #bc681a  4.09:1  fails AA for SMALL text, clears
		                                 the 3:1 non-text floor
		     #FFFFFF on #B0601A  4.63:1  passes AA for small text

		   So: --nv-accent for non-text and decoration, --nv-accent-hi for
		   accent-coloured text on dark, --nv-accent-deep for an accent
		   fill that must carry small white text.

		   The CTA keeps the brand hex and solves its 4.09:1 the other
		   way: its label is 1.125rem/700, which is WCAG's large-text
		   bracket where the threshold is 3:1. See .nv-cta. */
		--nv-accent:      #bc681a;
		--nv-accent-hi:   #E8862B;
		--nv-accent-deep: #B0601A;

		/* The focus ring is --nv-text, not the accent. It has to read
		   against both the near-black page and the accent-filled CTA it
		   sits beside, and #E8862B on #bc681a is 1.5:1. White is 15.8:1
		   on the page and 4.09:1 on the button. */
		--nv-focus: var(--nv-text);

		/* --- Type ---------------------------------------------------
		   Six faces: Poppins 300/400/700, Montserrat 300/400/700. The
		   request was once ten; four were fetched and never used
		   (Poppins 200/400i/600 and all of Caveat Brush).

		   Canvas set every h1-h6 to weight 600 while never fetching
		   Montserrat 600, so the browser silently substituted 700 for
		   every heading on the old page. Headings here declare 700
		   outright rather than depending on that substitution. */
		--nv-font-display: 'Montserrat', system-ui, -apple-system, 'Segoe UI', sans-serif;
		--nv-font-body:    'Poppins', system-ui, -apple-system, 'Segoe UI', sans-serif;
		--nv-font-mono:    ui-monospace, 'SFMono-Regular', 'Cascadia Mono', Menlo, monospace;

		--nv-step-0: 1rem;
		--nv-step-1: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
		--nv-step-2: clamp(1.375rem, 1.2rem + 0.9vw, 1.875rem);
		--nv-step-3: clamp(1.75rem, 1.4rem + 1.6vw, 2.5rem);
		--nv-step-4: clamp(2.25rem, 1.6rem + 3vw, 4rem);
		/* New: the hero needs a step above the old scale's ceiling to
		   read as cinematic rather than as a large paragraph. */
		--nv-step-5: clamp(3rem, 1.5rem + 8vw, 8.5rem);
		--nv-step-small: .8125rem;

		--nv-lh-tight: 1.05;
		--nv-lh-snug: 1.15;
		--nv-lh-body: 1.65;
		--nv-tracking-display: -.03em;
		--nv-tracking-eyebrow: .22em;

		/* --- Space, 4px base --------------------------------------- */
		--nv-1: .25rem;  --nv-2: .5rem;  --nv-3: .75rem;  --nv-4: 1rem;
		--nv-5: 1.5rem;  --nv-6: 2rem;   --nv-7: 3rem;    --nv-8: 4rem;
		--nv-9: 6rem;    --nv-10: 8rem;  --nv-11: 12rem;

		--nv-radius: 4px;
		--nv-radius-lg: 12px;
		--nv-radius-xl: 20px;
		--nv-radius-pill: 999px;

		--nv-gutter: clamp(1.25rem, 5vw, 4rem);
		--nv-measure: 62ch;
		--nv-max: 82rem;
		--nv-header-h: 4.5rem;

		/* --- Motion -------------------------------------------------
		   One house ease. --nv-ease-out is the expressive one (a long
		   decelerating tail, which is what makes a reveal feel authored
		   rather than linear); --nv-ease-inout for things that leave. */
		--nv-ease-out:   cubic-bezier(.16, 1, .3, 1);
		--nv-ease-inout: cubic-bezier(.83, 0, .17, 1);
		--nv-ease-back:  cubic-bezier(.34, 1.56, .64, 1);

		--nv-dur-1: 160ms;
		--nv-dur-2: 320ms;
		--nv-dur-3: 620ms;
		--nv-dur-4: 1100ms;

		/* --- Depth ---------------------------------------------------
		   A named stack, so "what sits on top of what" is answerable by
		   reading one block instead of grepping for z-index. */
		--nv-z-under:  -1;
		--nv-z-base:    1;
		--nv-z-mid:    10;
		--nv-z-fg:     20;
		--nv-z-nav:   100;
		--nv-z-panel: 110;
		--nv-z-player:200;
		--nv-z-cursor:300;
		--nv-z-skip:  400;

		--nv-glow:    0 0 48px -12px color-mix(in oklab, var(--nv-accent) 70%, transparent);
		--nv-glow-lg: 0 0 90px -20px color-mix(in oklab, var(--nv-accent) 65%, transparent);
		--nv-shadow:     0 2px 8px rgb(0 0 0 / .4);
		--nv-shadow-lg:  0 24px 64px -16px rgb(0 0 0 / .7), 0 2px 8px rgb(0 0 0 / .4);

		/* Written by ScrollTrigger, read by transform/opacity below. The
		   defaults are the un-animated state, so every scroll effect
		   degrades to "correct but static" if GSAP never loads. */
		--nv-hero-scale: 1;
		--nv-hero-scrim: 0;
		--nv-hero-blur: 0px;

		/* Written by the tile pointer handler. */
		--mx: .5;
		--my: .5;

		color-scheme: dark;
	}

	/* Ask for more contrast and the scrims lift and the hairlines firm
	   up. Cheap to support and the difference is large. */
	@media (prefers-contrast: more) {
		:root {
			--nv-border:     rgba(255, 255, 255, .32);
			--nv-border-hi:  rgba(255, 255, 255, .5);
			--nv-text-dim:   #C7CCD6;
			--nv-text-faint: #9AA2B1;
			--nv-accent-hi:  #FFA149;
		}
	}
}

/* ==================================================================
   BASE
   ================================================================== */
@layer base {

	*, *::before, *::after { box-sizing: border-box; }

	:where(h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ul, ol) {
		margin: 0;
	}
	:where(ul, ol) { padding: 0; list-style: none; }

	html {
		-webkit-text-size-adjust: 100%;
		/* Anchors land below the fixed header rather than under it. Also
		   correct without JS, which the old data-offset="60" was not. */
		scroll-padding-top: calc(var(--nv-header-h) + var(--nv-4));
	}

	body {
		margin: 0;
		background: var(--nv-bg);
		color: var(--nv-text);
		font-family: var(--nv-font-body);
		font-size: var(--nv-step-0);
		font-weight: 300;
		line-height: var(--nv-lh-body);
		-webkit-font-smoothing: antialiased;
		text-rendering: optimizeLegibility;
		overflow-x: clip;
	}

	h1, h2, h3, h4 {
		font-family: var(--nv-font-display);
		font-weight: 700;
		line-height: var(--nv-lh-snug);
		letter-spacing: var(--nv-tracking-display);
		text-wrap: balance;
	}

	p { text-wrap: pretty; }

	a {
		color: var(--nv-accent-hi);
		text-decoration-thickness: 1px;
		text-underline-offset: .2em;
	}
	a:hover { color: var(--nv-text); }

	/* Canvas set `img { max-width: 100% }` with no matching height, which
	   stretched the tiles 2.4x vertically until it was patched. Both axes
	   are stated here so that class of bug cannot return. */
	img, picture, video, canvas, svg, iframe {
		display: block;
		max-width: 100%;
	}
	img, video { height: auto; }

	button, input, select, textarea { font: inherit; color: inherit; }

	button {
		margin: 0;
		padding: 0;
		border: 0;
		background: none;
		cursor: pointer;
	}

	/* Canvas shipped a global `:focus { outline: none !important }` at
	   style.css:141 and repeated it for .button, which is why the old
	   custom.css had to answer in kind. That file is deleted, so this is
	   a plain rule again - no !important anywhere. */
	:focus-visible {
		outline: 2px solid var(--nv-focus);
		outline-offset: 2px;
		border-radius: var(--nv-radius);
	}

	::selection {
		background: var(--nv-accent);
		color: #fff;
	}

	::-webkit-scrollbar { width: 10px; height: 10px; }
	::-webkit-scrollbar-track { background: var(--nv-bg); }
	::-webkit-scrollbar-thumb {
		background: var(--nv-surface-3);
		border-radius: var(--nv-radius-pill);
		border: 2px solid var(--nv-bg);
	}
	::-webkit-scrollbar-thumb:hover { background: var(--nv-accent-deep); }
}

/* ==================================================================
   LAYOUT
   ================================================================== */
@layer layout {

	.nv-shell {
		width: 100%;
		max-width: var(--nv-max);
		margin-inline: auto;
		padding-inline: var(--nv-gutter);
	}

	.nv-section {
		position: relative;
		padding-block: clamp(var(--nv-9), 12vh, var(--nv-11));
	}

	/* Alternating surfaces give the page depth without a single extra
	   element. The old page was one flat colour top to bottom. */
	.nv-section--raised { background: var(--nv-surface); }

	/* A hairline that fades out at both ends, so sections separate
	   without a hard rule across the full width. */
	.nv-section + .nv-section::before {
		content: '';
		position: absolute;
		inset-block-start: 0;
		inset-inline: var(--nv-gutter);
		height: 1px;
		background: linear-gradient(90deg, transparent, var(--nv-border) 18%, var(--nv-border) 82%, transparent);
	}

	/* ScrollSmoother's required structure. Everything that must not
	   scroll - nav, player, cursor, skip link - lives outside it. */
	#smooth-content { will-change: transform; }
}

/* ==================================================================
   COMPONENTS
   ================================================================== */
@layer components {

	/* ---------------------------------------------------------------
	   Skip link
	   The page had none. First tab stop, visible only when focused.
	   --------------------------------------------------------------- */
	.nv-skip {
		position: fixed;
		inset-block-start: var(--nv-3);
		inset-inline-start: var(--nv-3);
		z-index: var(--nv-z-skip);
		padding: var(--nv-3) var(--nv-5);
		border-radius: var(--nv-radius-pill);
		background: var(--nv-accent-deep);
		color: #fff;
		font-weight: 700;
		text-decoration: none;
		transform: translateY(-200%);
	}
	.nv-skip:focus-visible {
		color: #fff;
		transform: translateY(0);
	}

	/* ---------------------------------------------------------------
	   Eyebrow + heading
	   The section label, its index, and a rule that draws itself in.
	   --------------------------------------------------------------- */
	.nv-eyebrow {
		display: flex;
		align-items: center;
		gap: var(--nv-3);
		margin-block-end: var(--nv-4);
		color: var(--nv-accent-hi);           /* 6.71:1 */
		font-family: var(--nv-font-display);
		font-size: var(--nv-step-small);
		font-weight: 700;
		letter-spacing: var(--nv-tracking-eyebrow);
		text-transform: uppercase;
	}
	.nv-eyebrow__index {
		font-family: var(--nv-font-mono);
		font-weight: 400;
		letter-spacing: 0;
		color: var(--nv-text-faint);
	}
	.nv-eyebrow__rule {
		flex: 0 0 auto;
		width: clamp(2rem, 8vw, 5rem);
		height: 1px;
		background: currentColor;
		transform: scaleX(var(--rule, 1));
		transform-origin: left;
		opacity: .6;
	}

	.nv-title {
		font-size: var(--nv-step-4);
		line-height: var(--nv-lh-tight);
	}

	.nv-lede {
		max-width: var(--nv-measure);
		margin-block-start: var(--nv-4);
		color: var(--nv-text-dim);           /* 7.3:1 */
		font-size: var(--nv-step-1);
	}

	.nv-section__head { margin-block-end: clamp(var(--nv-7), 6vh, var(--nv-9)); }

	/* ---------------------------------------------------------------
	   Buttons
	   --------------------------------------------------------------- */
	.nv-cta {
		display: inline-flex;
		align-items: center;
		gap: var(--nv-3);
		padding: var(--nv-4) var(--nv-6);
		border-radius: var(--nv-radius-pill);
		background: var(--nv-accent);
		color: #fff;
		/* 1.125rem/700 is WCAG's large-text bracket, where the threshold
		   is 3:1 rather than 4.5:1. White on #bc681a is 4.09:1, so the
		   brand hex is kept exactly and the label carries the compliance.
		   --nv-accent-deep (4.63:1) is the fallback if this ever shrinks. */
		font-family: var(--nv-font-display);
		font-size: 1.125rem;
		font-weight: 700;
		letter-spacing: .01em;
		line-height: 1;
		text-decoration: none;
		position: relative;
		overflow: hidden;
		isolation: isolate;
	}
	/* A sheen that sweeps across on hover, drawn with a pseudo-element so
	   it costs no markup and animates on transform only. */
	.nv-cta::after {
		content: '';
		position: absolute;
		inset: 0;
		z-index: -1;
		background: linear-gradient(100deg, transparent 20%, rgb(255 255 255 / .28) 50%, transparent 80%);
		transform: translateX(-120%);
	}
	.nv-cta:hover, .nv-cta:focus-visible {
		color: #fff;
		box-shadow: var(--nv-glow);
	}
	.nv-cta .nv-icon { width: 1em; height: 1em; }

	.nv-ghost {
		display: inline-flex;
		align-items: center;
		gap: var(--nv-3);
		padding: var(--nv-3) var(--nv-5);
		border: 1px solid var(--nv-border-hi);
		border-radius: var(--nv-radius-pill);
		color: var(--nv-text);
		font-size: var(--nv-step-small);
		font-weight: 400;
		letter-spacing: .04em;
		text-decoration: none;
		text-transform: uppercase;
	}
	.nv-ghost:hover, .nv-ghost:focus-visible {
		border-color: var(--nv-accent);
		background: color-mix(in oklab, var(--nv-accent) 14%, transparent);
		color: var(--nv-text);
	}

	/* ---------------------------------------------------------------
	   Icons — one inline SVG sprite replaces 123 KB of icon CSS and
	   twelve font files for about 3 KB of markup.
	   --------------------------------------------------------------- */
	.nv-icon {
		width: 1.25rem;
		height: 1.25rem;
		fill: none;
		stroke: currentColor;
		stroke-width: 1.75;
		stroke-linecap: round;
		stroke-linejoin: round;
		flex: 0 0 auto;
	}
	.nv-icon--fill { fill: currentColor; stroke: none; }

	/* ---------------------------------------------------------------
	   Header / nav
	   --------------------------------------------------------------- */
	.nv-header {
		position: fixed;
		inset-block-start: 0;
		inset-inline: 0;
		z-index: var(--nv-z-nav);
		height: var(--nv-header-h);
		display: flex;
		align-items: center;
	}
	.nv-header__inner {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--nv-5);
		width: 100%;
	}

	/* The logo was dropped from the old header and never came back, even
	   though the asset was sitting in the repo. */
	.nv-logo {
		display: flex;
		align-items: center;
		flex: 0 0 auto;
	}
	/* A square mark, not a wordmark: tools/make-icons.cjs autocrops the
	   source and writes a 240x240 transparent PNG (3x the rendered size, so
	   it stays sharp at DPR3). Adjust the height here rather than the
	   generated file's dimensions. */
	.nv-logo img {
		height: 1.75rem;
		width: auto;
	}

	.nv-header__nav {
		display: flex;
		align-items: center;
		gap: var(--nv-6);
	}
	@media (max-width: 47.99rem) {
		.nv-header__nav { display: none; }
	}

	.nv-navlink {
		position: relative;
		padding-block: var(--nv-2);
		color: var(--nv-text);
		font-family: var(--nv-font-display);
		font-size: var(--nv-step-small);
		font-weight: 400;
		letter-spacing: .12em;
		text-decoration: none;
		text-transform: uppercase;
	}
	.nv-navlink::after {
		content: '';
		position: absolute;
		inset-block-end: 0;
		inset-inline: 0;
		height: 1px;
		background: var(--nv-accent);
		transform: scaleX(0);
		transform-origin: left;
	}
	.nv-navlink:hover::after,
	.nv-navlink:focus-visible::after,
	.nv-navlink[aria-current='true']::after { transform: scaleX(1); }
	.nv-navlink[aria-current='true'] { color: var(--nv-accent-hi); }

	/* The bar gains a backdrop once the hero is behind it, so the links
	   stay legible over artwork. Toggled by a class, not a scroll tween. */
	.nv-header::before {
		content: '';
		position: absolute;
		inset: 0;
		z-index: -1;
		background: color-mix(in oklab, var(--nv-bg) 78%, transparent);
		backdrop-filter: blur(14px) saturate(1.4);
		-webkit-backdrop-filter: blur(14px) saturate(1.4);
		border-block-end: 1px solid var(--nv-border);
		opacity: 0;
	}
	html.nv-scrolled .nv-header::before { opacity: 1; }

	/* --- Menu trigger: three bars that become an X ---------------- */
	.nv-burger {
		position: relative;
		z-index: var(--nv-z-panel);
		display: grid;
		place-items: center;
		width: 2.75rem;
		height: 2.75rem;
		margin-inline-end: calc(var(--nv-2) * -1);
		border-radius: var(--nv-radius-pill);
		flex: 0 0 auto;
	}
	@media (min-width: 48rem) {
		.nv-burger { display: none; }
	}
	.nv-burger__box {
		position: relative;
		width: 1.25rem;
		height: .75rem;
	}
	.nv-burger__bar {
		position: absolute;
		inset-inline: 0;
		height: 2px;
		background: var(--nv-text);
		border-radius: 2px;
	}
	.nv-burger__bar:nth-child(1) { inset-block-start: 0; }
	.nv-burger__bar:nth-child(2) { inset-block-start: calc(50% - 1px); }
	.nv-burger__bar:nth-child(3) { inset-block-end: 0; }
	.nv-burger[aria-expanded='true'] .nv-burger__bar:nth-child(1) {
		transform: translateY(5px) rotate(45deg);
	}
	.nv-burger[aria-expanded='true'] .nv-burger__bar:nth-child(2) { opacity: 0; }
	.nv-burger[aria-expanded='true'] .nv-burger__bar:nth-child(3) {
		transform: translateY(-5px) rotate(-45deg);
	}

	/* --- Full-screen nav panel ------------------------------------ */
	.nv-panel {
		position: fixed;
		inset: 0;
		z-index: var(--nv-z-nav);
		display: grid;
		align-content: center;
		gap: var(--nv-6);
		padding: var(--nv-9) var(--nv-gutter);
		background: var(--nv-bg);
		/* Closed state. The panel is also [hidden] when closed, so this
		   is belt and braces for the transition. */
		clip-path: inset(0 0 100% 0);
	}
	.nv-panel[data-open='true'] { clip-path: inset(0 0 0 0); }
	.nv-panel__list {
		display: grid;
		gap: var(--nv-4);
	}
	.nv-panel__link {
		display: block;
		font-family: var(--nv-font-display);
		font-size: var(--nv-step-4);
		font-weight: 700;
		letter-spacing: var(--nv-tracking-display);
		line-height: 1.1;
		color: var(--nv-text);
		text-decoration: none;
	}
	.nv-panel__link:hover, .nv-panel__link:focus-visible { color: var(--nv-accent-hi); }
	.nv-panel__socials {
		display: flex;
		flex-wrap: wrap;
		gap: var(--nv-3);
		padding-block-start: var(--nv-6);
		border-block-start: 1px solid var(--nv-border);
	}

	/* ---------------------------------------------------------------
	   HERO — five stacked layers
	     0  the branded logo clip (or its poster)
	     1  scrim: a bottom gradient plus a corner vignette
	     2  film grain
	     3  caption
	     4  marquee, socials, scroll cue
	   The old hero was layer 0 and layer 3, with the scrim set to
	   rgba(0,0,0,0) - so legibility depended entirely on the clip
	   happening to be dark.
	   --------------------------------------------------------------- */
	.nv-hero {
		position: relative;
		min-height: 100svh;
		display: grid;
		grid-template-rows: 1fr auto;
		overflow: clip;
		isolation: isolate;
		background: #000;
	}

	.nv-hero__media {
		position: absolute;
		inset: 0;
		z-index: var(--nv-z-under);
		overflow: clip;
	}
	.nv-hero__video,
	.nv-hero__poster {
		width: 100%;
		height: 100%;
		object-fit: cover;
		/* Biased upward rather than centred. This only bites on tall,
		   portrait viewports, where a 16:9 clip covering by width overflows
		   vertically and there is slack to move: it lifts the mark clear of
		   the caption. On a landscape viewport the clip's height matches
		   exactly, so there is no slack and this is a no-op - which is why the
		   brightness treatment below, not this, is what actually resolves the
		   collision between the waveform and the headline. */
		object-position: center 28%;
		/* Scroll writes --nv-hero-scale; the default of 1 means this is a
		   no-op when GSAP is absent. */
		transform: scale(var(--nv-hero-scale));
		transform-origin: center 30%;
	}
	/* The clip is atmosphere, not a graphic to read.
	   NV's mark is a bright white waveform dead centre of the frame, and at
	   full brightness it cut straight through the headline - two high-contrast
	   marks competing for the same band of the screen, and the headline lost.
	   Held at 58% it still plays, still reads as the brand, and the words win.
	   The logo gets to be itself in the header, the favicon and the share
	   card, where nothing is layered over it. */
	.nv-hero__video {
		position: absolute;
		inset: 0;
		filter: brightness(.58) contrast(1.04);
	}

	/* Layer 1. Two gradients: one lifting the bottom third so the caption
	   always has contrast, one darkening the corners so the eye goes to
	   the middle. --nv-hero-scrim is scrubbed on scroll, deepening the
	   whole thing as the section leaves. */
	.nv-hero__scrim {
		position: absolute;
		inset: 0;
		z-index: var(--nv-z-base);
		pointer-events: none;
		background:
			radial-gradient(120% 80% at 50% 32%, transparent 38%, rgb(0 0 0 / .6) 100%),
			/* Reaches higher and holds opacity longer than a normal bottom
			   scrim, because the caption is large display type sitting over a
			   moving high-contrast clip. Anything softer and the headline's
			   counters fill with waveform. */
			linear-gradient(to top,
				var(--nv-bg) 4%,
				rgb(11 13 18 / .92) 30%,
				rgb(11 13 18 / .6) 52%,
				transparent 78%);
	}
	.nv-hero__scrim::after {
		content: '';
		position: absolute;
		inset: 0;
		background: var(--nv-bg);
		opacity: calc(var(--nv-hero-scrim) * .75);
	}

	/* Layer 2. A 4% noise tile at overlay - the thing that stops a flat
	   dark gradient looking like a flat dark gradient. The SVG is inline
	   as a data URI because the CSP-free win here is one fewer request. */
	.nv-hero__grain,
	.nv-grain {
		position: absolute;
		inset: 0;
		z-index: var(--nv-z-mid);
		pointer-events: none;
		opacity: .045;
		mix-blend-mode: overlay;
		background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
	}

	/* Layer 3 */
	.nv-hero__body {
		position: relative;
		z-index: var(--nv-z-fg);
		align-self: end;
		display: grid;
		gap: var(--nv-5);
		justify-items: start;
		padding-block: var(--nv-9) var(--nv-7);
	}

	.nv-hero__title {
		font-size: var(--nv-step-5);
		line-height: .95;
		letter-spacing: -.04em;
		text-transform: uppercase;
		text-wrap: balance;
	}
	/* Each line is a mask; the chars inside translate up out of it. The
	   wrapper is what makes a split reveal read as deliberate rather than
	   as text sliding around loose on the page. */
	.nv-hero__title .nv-line {
		display: block;
		overflow: hidden;
		padding-block-end: .06em;   /* room for descenders inside the mask */
	}
	.nv-hero__title .nv-accent {
		color: var(--nv-accent-hi);
		-webkit-text-stroke: 0;
	}

	.nv-hero__tagline {
		max-width: 34ch;
		color: var(--nv-text-dim);
		font-size: var(--nv-step-1);
	}

	.nv-hero__actions {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--nv-4);
	}

	/* Layer 4 */
	.nv-hero__foot {
		position: relative;
		z-index: var(--nv-z-fg);
		display: grid;
		gap: var(--nv-4);
		padding-block-end: var(--nv-5);
	}
	.nv-hero__meta {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--nv-5);
		flex-wrap: wrap;
	}

	.nv-scrollcue {
		display: inline-flex;
		align-items: center;
		gap: var(--nv-3);
		color: var(--nv-text-faint);
		font-family: var(--nv-font-display);
		font-size: .75rem;
		font-weight: 400;
		letter-spacing: var(--nv-tracking-eyebrow);
		text-transform: uppercase;
		text-decoration: none;
	}
	.nv-scrollcue:hover { color: var(--nv-text); }
	.nv-scrollcue__line {
		display: block;
		width: 3rem;
		height: 1px;
		background: linear-gradient(90deg, currentColor, transparent);
	}

	/* ---------------------------------------------------------------
	   Social rail
	   --------------------------------------------------------------- */
	.nv-socials {
		display: flex;
		align-items: center;
		gap: var(--nv-2);
	}
	.nv-social {
		display: grid;
		place-items: center;
		width: 2.5rem;
		height: 2.5rem;
		border: 1px solid var(--nv-border);
		border-radius: var(--nv-radius-pill);
		color: var(--nv-text-dim);
	}
	.nv-social:hover, .nv-social:focus-visible {
		border-color: var(--nv-accent);
		background: color-mix(in oklab, var(--nv-accent) 16%, transparent);
		color: var(--nv-text);
	}
	.nv-social .nv-icon { width: 1.0625rem; height: 1.0625rem; }

	/* ---------------------------------------------------------------
	   Marquee
	   A real <ul> plus one aria-hidden duplicate, so the track can
	   translate -50% forever without the list being read twice.
	   --------------------------------------------------------------- */
	.nv-marquee {
		position: relative;
		overflow: clip;
		padding-block: var(--nv-3);
		border-block: 1px solid var(--nv-border);
		mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
	}
	.nv-marquee__track {
		display: flex;
		width: max-content;
		gap: var(--nv-6);
		will-change: transform;
	}
	.nv-marquee__list {
		display: flex;
		gap: var(--nv-6);
		flex: 0 0 auto;
	}
	.nv-marquee__item {
		display: flex;
		align-items: center;
		gap: var(--nv-6);
		color: var(--nv-text-faint);
		font-family: var(--nv-font-display);
		font-size: .8125rem;
		font-weight: 700;
		letter-spacing: var(--nv-tracking-eyebrow);
		text-transform: uppercase;
		white-space: nowrap;
	}
	.nv-marquee__item::after {
		content: '';
		width: 4px;
		height: 4px;
		border-radius: 50%;
		background: var(--nv-accent);
		flex: 0 0 auto;
	}

	/* ---------------------------------------------------------------
	   LISTEN — 2x2 playlist cards
	   Was four full-width 16:9 iframes stacked vertically: ~1600px of
	   near-identical grey rectangles, and four third-party embeds on
	   first paint.
	   --------------------------------------------------------------- */
	.nv-listen__grid {
		display: grid;
		gap: var(--nv-5);
		grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
	}

	.nv-card {
		position: relative;
		display: grid;
		grid-template-rows: auto 1fr;
		overflow: clip;
		border: 1px solid var(--nv-border);
		border-radius: var(--nv-radius-lg);
		background: var(--nv-surface);
		isolation: isolate;
	}
	.nv-card:hover { border-color: var(--nv-border-hi); }

	/* aspect-ratio, not a padding hack and not a height attribute the
	   vendor CSS would override on one axis only. No CLS when the iframe
	   swaps in, because the box is already the right shape. */
	.nv-card__frame {
		position: relative;
		aspect-ratio: 16 / 9;
		overflow: clip;
		background: var(--nv-surface-2);
	}
	.nv-card__frame img,
	.nv-card__frame iframe {
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		object-fit: cover;
		border: 0;
	}
	.nv-card__frame img { transform: scale(1.001); }
	.nv-card:hover .nv-card__frame img { transform: scale(1.06); }

	/* The facade's own scrim, so the play button and genre chip read over
	   any artwork. */
	.nv-card__veil {
		position: absolute;
		inset: 0;
		background: linear-gradient(to top, rgb(11 13 18 / .92) 0%, rgb(11 13 18 / .25) 55%, rgb(11 13 18 / .5) 100%);
	}

	.nv-card__play {
		position: absolute;
		inset: 0;
		z-index: var(--nv-z-fg);
		display: grid;
		place-items: center;
		width: 100%;
		color: var(--nv-text);
	}
	.nv-card__play::before {
		content: '';
		position: absolute;
		inset: 0;
	}
	.nv-playring {
		display: grid;
		place-items: center;
		width: 4.5rem;
		height: 4.5rem;
		border: 1px solid var(--nv-border-hi);
		border-radius: 50%;
		background: color-mix(in oklab, var(--nv-bg) 55%, transparent);
		backdrop-filter: blur(6px);
		-webkit-backdrop-filter: blur(6px);
	}
	.nv-playring .nv-icon { width: 1.5rem; height: 1.5rem; margin-inline-start: 3px; }
	.nv-card__play:hover .nv-playring,
	.nv-card__play:focus-visible .nv-playring {
		border-color: var(--nv-accent);
		background: var(--nv-accent);
		box-shadow: var(--nv-glow);
		color: #fff;
	}

	.nv-card__body {
		display: grid;
		gap: var(--nv-2);
		align-content: start;
		padding: var(--nv-5);
	}
	.nv-card__title {
		font-size: var(--nv-step-2);
		letter-spacing: -.02em;
	}
	.nv-card__sub {
		color: var(--nv-text-faint);
		font-family: var(--nv-font-display);
		font-size: .75rem;
		font-weight: 400;
		letter-spacing: var(--nv-tracking-eyebrow);
		text-transform: uppercase;
	}
	.nv-card__blurb {
		color: var(--nv-text-dim);
		font-size: .9375rem;
	}

	.nv-chip {
		position: absolute;
		inset-block-start: var(--nv-4);
		inset-inline-start: var(--nv-4);
		z-index: var(--nv-z-fg);
		padding: var(--nv-1) var(--nv-3);
		border: 1px solid color-mix(in oklab, var(--nv-accent) 55%, transparent);
		border-radius: var(--nv-radius-pill);
		background: color-mix(in oklab, var(--nv-bg) 70%, transparent);
		backdrop-filter: blur(6px);
		-webkit-backdrop-filter: blur(6px);
		color: var(--nv-accent-hi);
		font-family: var(--nv-font-display);
		font-size: .6875rem;
		font-weight: 700;
		letter-spacing: .14em;
		text-transform: uppercase;
	}

	/* ---------------------------------------------------------------
	   MIXES — a horizontal strip
	   The substrate is a native scroll-snap flex row, so it works with
	   zero JS, on touch, and from the keyboard. The drag, inertia and
	   tilt are additions on top, never the mechanism.

	   The old carousel was configured 6-up at desktop with sizes="180px":
	   six 9:16 posters at 180px each, which is most of why the section
	   read as postage stamps.
	   --------------------------------------------------------------- */
	.nv-strip {
		display: flex;
		gap: var(--nv-4);
		/* Full-bleed, and the last tile can still scroll to the gutter. */
		margin-inline: calc(var(--nv-gutter) * -1);
		padding-inline: var(--nv-gutter);
		padding-block-end: var(--nv-4);
		overflow-x: auto;
		overscroll-behavior-x: contain;
		scroll-snap-type: x mandatory;
		scrollbar-width: none;
		perspective: 1400px;
	}
	.nv-strip::-webkit-scrollbar { display: none; }
	.nv-strip[data-dragging='true'] {
		cursor: grabbing;
		scroll-snap-type: none;
	}

	.nv-tile {
		position: relative;
		/* 1.35 tiles on a phone, up to 4 on a wide screen. The clipped
		   partial tile at the right edge is deliberate: it is what tells
		   you the row scrolls without needing a caption to say so. */
		flex: 0 0 clamp(13rem, 30vw, 19rem);
		scroll-snap-align: center;
		aspect-ratio: 9 / 16;
		overflow: clip;
		border-radius: var(--nv-radius-lg);
		background: var(--nv-surface-2);
		isolation: isolate;
		transform-style: preserve-3d;
	}
	.nv-tile__art {
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}
	.nv-tile__veil {
		position: absolute;
		inset: 0;
		background: linear-gradient(to top, rgb(11 13 18 / .95) 4%, rgb(11 13 18 / .35) 48%, rgb(11 13 18 / .15) 100%);
	}
	.nv-tile__body {
		position: absolute;
		inset-block-end: 0;
		inset-inline: 0;
		z-index: var(--nv-z-fg);
		display: grid;
		gap: var(--nv-2);
		padding: var(--nv-4);
	}
	.nv-tile__title {
		font-size: 1.0625rem;
		line-height: 1.25;
		letter-spacing: -.01em;
	}
	.nv-tile__title a {
		color: var(--nv-text);
		text-decoration: none;
	}
	/* The whole tile is the link's hit area, but the <a> stays the
	   accessible name - no nested-interactive, no redundant tabstop. */
	.nv-tile__title a::before {
		content: '';
		position: absolute;
		inset: 0;
		z-index: var(--nv-z-base);
	}
	.nv-tile__title a:hover { color: var(--nv-accent-hi); }
	.nv-tile__meta {
		display: flex;
		align-items: center;
		gap: var(--nv-2);
		color: var(--nv-text-faint);
		font-family: var(--nv-font-display);
		font-size: .6875rem;
		font-weight: 400;
		letter-spacing: .14em;
		text-transform: uppercase;
	}

	/* The play button must sit above the title link's overlay. */
	.nv-tile__play {
		position: absolute;
		inset-block-end: var(--nv-4);
		inset-inline-end: var(--nv-4);
		z-index: var(--nv-z-mid);
		display: grid;
		place-items: center;
		width: 3rem;
		height: 3rem;
		border: 1px solid var(--nv-border-hi);
		border-radius: 50%;
		background: color-mix(in oklab, var(--nv-bg) 62%, transparent);
		backdrop-filter: blur(8px);
		-webkit-backdrop-filter: blur(8px);
		color: var(--nv-text);
	}
	.nv-tile__play:hover,
	.nv-tile__play:focus-visible {
		border-color: var(--nv-accent);
		background: var(--nv-accent);
		color: #fff;
		box-shadow: var(--nv-glow);
	}
	.nv-tile__play .nv-icon { width: 1.125rem; height: 1.125rem; }
	/* Play and pause swap on aria-pressed rather than on a class name, so the
	   icon is a pure function of what the audio element is doing and there is
	   no second source of truth to keep in step.

	   Keyed off the data attributes, not off .nv-tile__play - the hero CTA is
	   a .nv-cta and needs the same behaviour. */
	[data-nv-play] .nv-icon--pause,
	[data-nv-toggle] .nv-icon--pause { display: none; }
	[aria-pressed='true'] .nv-icon--pause { display: block; }
	[aria-pressed='true'] .nv-icon--play { display: none; }
	.nv-tile__play[aria-pressed='true'] {
		border-color: var(--nv-accent);
		background: var(--nv-accent);
		color: #fff;
	}

	/* A thin bar that fills while this mix is the one playing. */
	.nv-tile__progress {
		position: absolute;
		inset-block-end: 0;
		inset-inline: 0;
		z-index: var(--nv-z-fg);
		height: 2px;
		background: var(--nv-accent);
		transform: scaleX(var(--p, 0));
		transform-origin: left;
		opacity: 0;
	}
	.nv-tile[data-playing='true'] .nv-tile__progress { opacity: 1; }

	.nv-strip__controls {
		display: flex;
		align-items: center;
		gap: var(--nv-2);
	}
	.nv-strip__btn {
		display: grid;
		place-items: center;
		width: 2.75rem;
		height: 2.75rem;
		border: 1px solid var(--nv-border);
		border-radius: 50%;
		color: var(--nv-text);
	}
	.nv-strip__btn:hover:not([aria-disabled='true']) {
		border-color: var(--nv-accent);
		background: color-mix(in oklab, var(--nv-accent) 16%, transparent);
	}
	.nv-strip__btn[aria-disabled='true'] {
		color: var(--nv-text-faint);
		opacity: .45;
		cursor: default;
	}

	.nv-section__bar {
		display: flex;
		align-items: end;
		justify-content: space-between;
		gap: var(--nv-5);
		flex-wrap: wrap;
	}

	/* ---------------------------------------------------------------
	   FOLLOW — a tab set
	   Was three stacked blocks: a TikTok creator embed, an Elfsight
	   widget rendering the same feed, a 450px SoundCloud iframe and two
	   120px Mixcloud iframes, all live on load. Five third-party
	   requests before a visitor did anything.
	   --------------------------------------------------------------- */
	.nv-tabs {
		display: flex;
		gap: var(--nv-2);
		flex-wrap: wrap;
		margin-block-end: var(--nv-6);
	}
	.nv-tab {
		padding: var(--nv-3) var(--nv-5);
		border: 1px solid var(--nv-border);
		border-radius: var(--nv-radius-pill);
		color: var(--nv-text-dim);
		font-family: var(--nv-font-display);
		font-size: var(--nv-step-small);
		font-weight: 700;
		letter-spacing: .08em;
		text-transform: uppercase;
	}
	.nv-tab:hover { color: var(--nv-text); border-color: var(--nv-border-hi); }
	.nv-tab[aria-selected='true'] {
		border-color: transparent;
		background: var(--nv-accent-deep);   /* 4.63:1 with white - this label is small */
		color: #fff;
	}

	.nv-panel-embed {
		display: grid;
		gap: var(--nv-5);
		min-height: 12rem;
		/* Skips layout and paint for whichever panel is offscreen. */
		content-visibility: auto;
		contain-intrinsic-size: auto 28rem;
	}
	.nv-panel-embed iframe {
		width: 100%;
		border: 0;
		border-radius: var(--nv-radius-lg);
		background: var(--nv-surface);
	}

	.nv-facade {
		display: grid;
		gap: var(--nv-4);
		place-content: center;
		justify-items: center;
		padding: var(--nv-8) var(--nv-5);
		border: 1px dashed var(--nv-border-hi);
		border-radius: var(--nv-radius-lg);
		background:
			radial-gradient(80% 60% at 50% 0%, color-mix(in oklab, var(--nv-accent) 12%, transparent), transparent 70%),
			var(--nv-surface);
		text-align: center;
	}
	.nv-facade__note {
		max-width: 40ch;
		color: var(--nv-text-dim);
		font-size: .9375rem;
	}

	/* ---------------------------------------------------------------
	   PLAYER — replaces MediaElement.js, its CSS and its sprite
	   --------------------------------------------------------------- */
	.nv-player {
		position: fixed;
		inset-block-end: 0;
		inset-inline: 0;
		z-index: var(--nv-z-player);
		border-block-start: 1px solid var(--nv-border);
		background: color-mix(in oklab, var(--nv-bg) 88%, transparent);
		backdrop-filter: blur(18px) saturate(1.3);
		-webkit-backdrop-filter: blur(18px) saturate(1.3);
		/* Docked out of view until the first track starts. */
		transform: translateY(101%);
	}
	.nv-player[data-visible='true'] { transform: translateY(0); }

	.nv-player__inner {
		position: relative;
		z-index: var(--nv-z-base);
		display: grid;
		grid-template-columns: auto auto 1fr;
		align-items: center;
		gap: var(--nv-4);
		padding-block: var(--nv-3);
	}
	@media (max-width: 40rem) {
		.nv-player__inner { grid-template-columns: auto 1fr; }
		.nv-player__art { display: none; }
	}

	/* The visualiser is the player's backdrop, not a widget beside it.
	   Decorative, so aria-hidden and never in the tab order. */
	.nv-visualizer {
		position: absolute;
		inset: 0;
		z-index: 0;
		width: 100%;
		height: 100%;
		opacity: .3;
		pointer-events: none;
		mask-image: linear-gradient(to top, #000, transparent);
	}
	html.nv-no-analyser .nv-visualizer { display: none; }

	.nv-player__toggle {
		display: grid;
		place-items: center;
		width: 3rem;
		height: 3rem;
		border-radius: 50%;
		background: var(--nv-accent);
		color: #fff;
		flex: 0 0 auto;
	}
	.nv-player__toggle:hover { box-shadow: var(--nv-glow); }
	.nv-player__toggle .nv-icon { width: 1.125rem; height: 1.125rem; }

	.nv-player__art {
		width: 3rem;
		height: 3rem;
		border-radius: var(--nv-radius);
		object-fit: cover;
		background: var(--nv-surface-2);
	}

	.nv-player__main {
		display: grid;
		gap: var(--nv-1);
		min-width: 0;
	}
	.nv-player__now {
		display: flex;
		align-items: baseline;
		gap: var(--nv-3);
		min-width: 0;
		font-size: .875rem;
	}
	.nv-player__track {
		overflow: hidden;
		white-space: nowrap;
		text-overflow: ellipsis;
		font-family: var(--nv-font-display);
		font-weight: 700;
	}
	.nv-player__time {
		flex: 0 0 auto;
		color: var(--nv-text-dim);
		font-family: var(--nv-font-mono);
		font-size: .75rem;
		font-variant-numeric: tabular-nums;
	}
	.nv-player__hint {
		flex: 0 0 auto;
		color: var(--nv-text-faint);
		font-size: .6875rem;
		letter-spacing: .1em;
		text-transform: uppercase;
	}
	@media (max-width: 52rem) {
		.nv-player__hint { display: none; }
	}

	/* A range input styled to a hairline. Native element on purpose: it
	   is keyboard-operable, announces a value, and needs no ARIA. */
	.nv-seek {
		-webkit-appearance: none;
		appearance: none;
		width: 100%;
		height: 1rem;
		background: none;
		cursor: pointer;
	}
	.nv-seek::-webkit-slider-runnable-track {
		height: 3px;
		border-radius: 3px;
		background: linear-gradient(90deg,
			var(--nv-accent) 0 calc(var(--seek, 0) * 100%),
			var(--nv-border) calc(var(--seek, 0) * 100%) 100%);
	}
	.nv-seek::-moz-range-track {
		height: 3px;
		border-radius: 3px;
		background: var(--nv-border);
	}
	.nv-seek::-moz-range-progress {
		height: 3px;
		border-radius: 3px;
		background: var(--nv-accent);
	}
	.nv-seek::-webkit-slider-thumb {
		-webkit-appearance: none;
		appearance: none;
		width: 12px;
		height: 12px;
		margin-block-start: -4.5px;
		border: 0;
		border-radius: 50%;
		background: var(--nv-text);
	}
	.nv-seek::-moz-range-thumb {
		width: 12px;
		height: 12px;
		border: 0;
		border-radius: 50%;
		background: var(--nv-text);
	}
	.nv-seek:hover::-webkit-slider-thumb { background: #fff; }

	.nv-player[data-state='error'] .nv-player__track { color: var(--nv-accent-hi); }

	/* ---------------------------------------------------------------
	   FOOTER
	   --------------------------------------------------------------- */
	.nv-footer {
		padding-block: var(--nv-8);
		border-block-start: 1px solid var(--nv-border);
		background: var(--nv-bg);
	}
	.nv-footer__inner {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--nv-5);
		flex-wrap: wrap;
	}
	.nv-footer__meta {
		display: grid;
		gap: var(--nv-1);
		color: var(--nv-text-dim);
		font-size: .8125rem;
	}
	.nv-footer__meta a { color: var(--nv-text-dim); }
	.nv-footer__meta a:hover { color: var(--nv-accent-hi); }

	/* ---------------------------------------------------------------
	   Back to top
	   Was a <div>, so a keyboard user could never reach it.
	   --------------------------------------------------------------- */
	.nv-totop {
		position: fixed;
		inset-block-end: var(--nv-5);
		inset-inline-end: var(--nv-5);
		z-index: var(--nv-z-fg);
		display: grid;
		place-items: center;
		width: 2.75rem;
		height: 2.75rem;
		border: 1px solid var(--nv-border);
		border-radius: 50%;
		background: color-mix(in oklab, var(--nv-bg) 80%, transparent);
		backdrop-filter: blur(8px);
		-webkit-backdrop-filter: blur(8px);
		color: var(--nv-text);
		opacity: 0;
		pointer-events: none;
		transform: translateY(8px);
	}
	html.nv-scrolled .nv-totop {
		opacity: 1;
		pointer-events: auto;
		transform: translateY(0);
	}
	.nv-player[data-visible='true'] ~ .nv-totop { inset-block-end: calc(var(--nv-5) + 4.5rem); }
	.nv-totop:hover { border-color: var(--nv-accent); color: var(--nv-accent-hi); }

	/* ---------------------------------------------------------------
	   Magnetic cursor
	   Desktop-only by definition. The native cursor is only hidden once
	   the module has actually initialised and added .nv-cursor-on, so a
	   JS failure can never leave a visitor with no pointer.
	   --------------------------------------------------------------- */
	.nv-cursor {
		position: fixed;
		inset-block-start: 0;
		inset-inline-start: 0;
		z-index: var(--nv-z-cursor);
		pointer-events: none;
		display: none;
		/* Revealed by cursor.js on the first real pointermove. Without this
		   the dot sat in the top-left corner until the pointer moved. */
		opacity: 0;
	}
	html.nv-cursor-on .nv-cursor { display: block; }
	/* Hiding the native cursor is handled in @layer utilities, not here.
	   The cursor property is inherited, so setting it once on html ought to
	   be enough - but .nv-seek, .nv-strip[data-dragging] and
	   .nv-strip__btn[aria-disabled] each declare their own cursor in THIS
	   layer, and those won. The result was the native pointer flickering back
	   into view over the scrubber and during a drag. */

	.nv-cursor__dot,
	.nv-cursor__ring {
		position: absolute;
		inset-block-start: 0;
		inset-inline-start: 0;
		border-radius: 50%;
		will-change: transform;
	}
	.nv-cursor__dot {
		width: 6px;
		height: 6px;
		margin: -3px 0 0 -3px;
		background: var(--nv-text);
	}
	.nv-cursor__ring {
		width: 40px;
		height: 40px;
		margin: -20px 0 0 -20px;
		border: 1px solid var(--nv-border-hi);
		/* The independent `scale` property, not a transform function - GSAP
		   owns this element's transform for the position, and folding scale
		   into the same property meant the per-frame position writes wiped it
		   out. cursor.js writes --ring-scale; this transitions it. */
		scale: var(--ring-scale, 1);
		/* 140ms, not --nv-dur-2 (320ms). The page's general-purpose duration is
		   right for a section revealing itself and much too slow for a cursor
		   answering a hover - at 320ms the ring was still growing after the
		   pointer had crossed the whole control, which read as the cursor
		   lagging rather than responding. */
		transition: scale 140ms var(--nv-ease-out),
		            border-color 110ms ease,
		            background-color 110ms ease;
	}
	html.nv-cursor-press .nv-cursor__ring {
		scale: calc(var(--ring-scale, 1) * .82);
		transition-duration: 120ms;
	}
	html.nv-cursor-hot .nv-cursor__ring {
		border-color: var(--nv-accent);
		background: color-mix(in oklab, var(--nv-accent) 16%, transparent);
	}
	html.nv-cursor-hot .nv-cursor__dot { opacity: 0; }
	.nv-cursor__dot { transition: opacity 110ms ease; }
}

/* ==================================================================
   MOTION
   Everything here must be safe for the reduced-motion block at the end
   to neutralise. Nothing in this layer may be the only thing making an
   element visible or reachable.
   ================================================================== */
@layer motion {

	/* The load gate. html.nv-loading is on the element from the start and
	   removed by main.js, so there is no flash of unstyled reveal. It is
	   also removed by a timeout, so a JS failure cannot leave the page
	   blank - see main.js. */
	html.nv-loading body { opacity: 0; }
	body { transition: opacity var(--nv-dur-3) var(--nv-ease-out); }

	/* --- Transitions --------------------------------------------- */
	.nv-skip { transition: transform var(--nv-dur-2) var(--nv-ease-out); }

	.nv-header::before,
	.nv-totop { transition: opacity var(--nv-dur-2) ease, transform var(--nv-dur-2) var(--nv-ease-out); }

	.nv-navlink::after,
	.nv-eyebrow__rule { transition: transform var(--nv-dur-2) var(--nv-ease-out); }

	.nv-cta,
	.nv-ghost,
	.nv-social,
	.nv-tab,
	.nv-strip__btn,
	.nv-playring,
	.nv-tile__play,
	.nv-player__toggle,
	.nv-totop {
		transition:
			background-color var(--nv-dur-1) ease,
			border-color var(--nv-dur-1) ease,
			color var(--nv-dur-1) ease,
			box-shadow var(--nv-dur-2) ease,
			transform var(--nv-dur-2) var(--nv-ease-out);
	}

	.nv-cta::after { transition: transform var(--nv-dur-4) var(--nv-ease-out); }
	.nv-cta:hover::after, .nv-cta:focus-visible::after { transform: translateX(120%); }

	.nv-card { transition: border-color var(--nv-dur-2) ease, transform var(--nv-dur-3) var(--nv-ease-out); }
	.nv-card__frame img { transition: transform var(--nv-dur-4) var(--nv-ease-out); }

	.nv-burger__bar { transition: transform var(--nv-dur-2) var(--nv-ease-out), opacity var(--nv-dur-1) ease; }
	.nv-panel { transition: clip-path var(--nv-dur-3) var(--nv-ease-inout); }

	.nv-player { transition: transform var(--nv-dur-3) var(--nv-ease-out); }
	.nv-player__art { transition: opacity var(--nv-dur-2) ease; }
	.nv-tile__progress { transition: opacity var(--nv-dur-2) ease; }

	.nv-hero__video, .nv-hero__poster { transition: opacity var(--nv-dur-4) ease; }

	/* --- Hover depth ---------------------------------------------
	   Pointer-gated: a tilt that responds to a finger tap is just a
	   jolt, and :hover on touch sticks after the tap. */
	@media (hover: hover) and (pointer: fine) {
		.nv-tile {
			transition: transform var(--nv-dur-3) var(--nv-ease-out), box-shadow var(--nv-dur-3) ease;
		}
		.nv-tile__art { transition: transform var(--nv-dur-4) var(--nv-ease-out); }
		.nv-tile:hover {
			/* --mx/--my are written by one delegated, rAF-coalesced
			   pointermove handler on the strip that reads no geometry. */
			transform:
				translateY(-10px)
				rotateX(calc((var(--my) - .5) * -7deg))
				rotateY(calc((var(--mx) - .5) * 9deg));
			box-shadow: var(--nv-shadow-lg), var(--nv-glow-lg);
		}
		.nv-tile:hover .nv-tile__art { transform: scale(1.07); }
		.nv-card:hover { transform: translateY(-4px); }
		.nv-strip__btn:hover:not([aria-disabled='true']) { transform: scale(1.06); }
		.nv-social:hover { transform: translateY(-2px); }
	}

	/* --- Reveals -------------------------------------------------
	   The pre-reveal state lives here so that when the motion layer is
	   neutralised, [data-reveal] elements are simply visible. JS adds
	   .is-in; CSS handles the rest. No inline styles to unwind.       */
	[data-reveal] {
		opacity: 0;
		transform: translateY(var(--reveal-y, 24px));
		transition:
			opacity var(--nv-dur-3) var(--nv-ease-out),
			transform var(--nv-dur-3) var(--nv-ease-out);
		transition-delay: var(--reveal-delay, 0ms);
	}
	[data-reveal].is-in {
		opacity: 1;
		transform: none;
	}

	/* Fallback only: if GSAP never loads, .nv-line children have not
	   been split, so animate the line as one block instead of per char. */
	html.nv-no-gsap .nv-hero__title .nv-line {
		opacity: 0;
		animation: nv-rise var(--nv-dur-4) var(--nv-ease-out) forwards;
	}
	html.nv-no-gsap .nv-hero__title .nv-line:nth-child(2) { animation-delay: 120ms; }

	@keyframes nv-rise {
		from { opacity: 0; transform: translateY(40%); }
		to   { opacity: 1; transform: none; }
	}

	/* The marquee is a GSAP tween so it can be paused offscreen, on
	   hover, and on visibilitychange. This keyframe is the no-GSAP
	   fallback only. */
	html.nv-no-gsap .nv-marquee__track {
		animation: nv-marquee 26s linear infinite;
	}
	@keyframes nv-marquee {
		from { transform: translateX(0); }
		to   { transform: translateX(-50%); }
	}

	/* The scroll cue's little nudge - the one ambient animation that
	   earns its keep, because it says "there is more below". */
	.nv-scrollcue__line {
		animation: nv-nudge 2.4s var(--nv-ease-inout) infinite;
		transform-origin: left;
	}
	@keyframes nv-nudge {
		0%, 100% { transform: scaleX(1); opacity: 1; }
		50%      { transform: scaleX(1.5); opacity: .55; }
	}
}

/* ==================================================================
   UTILITIES
   ================================================================== */
@layer utilities {

	/* --- Native cursor suppression -------------------------------
	   In utilities, the last layer, so it beats the per-component cursor
	   declarations in @layer components without needing !important. The
	   universal selector is deliberate: any element that sets its own cursor
	   would otherwise punch a hole in the effect. */
	html.nv-cursor-on,
	html.nv-cursor-on * { cursor: none; }

	/* Dragging the strip is the one exception. The native grabbing cursor is
	   the right affordance there and a floating ring is just noise, so the
	   custom one steps aside for the duration. */
	html.nv-cursor-drag,
	html.nv-cursor-drag * { cursor: grabbing; }
	html.nv-cursor-drag .nv-cursor { opacity: 0; }

	/* Form controls keep a real cursor. A caret carries information - it says
	   "you can type here" - and the scrubber is the one control on the page
	   where pointing precisely actually matters. */
	html.nv-cursor-on :is(input, textarea, [contenteditable]) { cursor: auto; }
	html.nv-cursor-on .nv-seek { cursor: pointer; }

	.nv-sr-only {
		position: absolute !important;
		width: 1px; height: 1px;
		padding: 0; margin: -1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
		border: 0;
	}
	.nv-stack { display: grid; gap: var(--nv-4); }
	.nv-hidden { display: none !important; }
}

/* ==================================================================
   REDUCED MOTION
   Outside the layers so it wins regardless of layer order. JS has its
   own kill switch in motion.js; this is the half that works with no JS
   at all, and the half that catches anything JS forgets.
   ================================================================== */
@media (prefers-reduced-motion: reduce) {

	*, *::before, *::after {
		animation-duration: 1ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 1ms !important;
		scroll-behavior: auto !important;
	}

	/* Reveals must not depend on a class arriving. */
	[data-reveal] { opacity: 1 !important; transform: none !important; }

	html.nv-loading body { opacity: 1; }

	/* Purely kinetic things simply do not exist. */
	.nv-cursor,
	.nv-visualizer { display: none !important; }

	/* cursor.js never initialises under reduced motion, so .nv-cursor-on is
	   never added - but if it somehow were, the native cursor must come back. */
	html.nv-cursor-on,
	html.nv-cursor-on * { cursor: auto; }

	.nv-scrollcue__line { animation: none !important; }

	/* The ticker becomes a list you can scroll yourself. */
	.nv-marquee { overflow-x: auto; mask-image: none; }
	.nv-marquee__track { transform: none !important; animation: none !important; }

	/* No tilt, no lift. */
	.nv-tile:hover { transform: none !important; box-shadow: var(--nv-shadow-lg); }
	.nv-tile:hover .nv-tile__art { transform: none !important; }
	.nv-card:hover { transform: none !important; }
	.nv-card:hover .nv-card__frame img { transform: none !important; }

	/* The hero holds its final composition. */
	.nv-hero__video, .nv-hero__poster { transform: none !important; }

	.nv-panel { transition: none !important; }
	.nv-player { transition: none !important; }
}

/* Windows High Contrast: drop the scrims and let the OS palette through
   rather than fighting it with colour-mix. */
@media (forced-colors: active) {
	.nv-hero__scrim,
	.nv-hero__grain,
	.nv-grain,
	.nv-card__veil,
	.nv-tile__veil,
	.nv-visualizer { display: none; }

	.nv-social, .nv-tab, .nv-strip__btn, .nv-tile__play, .nv-totop {
		border-color: ButtonBorder;
	}
	.nv-cta { border: 1px solid ButtonBorder; }
}
