/* ==========================================================================
   Shader Landing - fixed, non-scrollable one-page template
   ========================================================================== */

:root {
	--sl-bg: #ffdfc4;
	--sl-text: #1a1a1a;
	--sl-outline-color: rgba(15, 15, 15, 0.32);
	--sl-shadow: rgba(0, 0, 0, 0.5);
}

@media (prefers-color-scheme: dark) {
	:root {
		--sl-bg: #101014;
		--sl-text: #f2f2f2;
		--sl-shadow: rgba(0, 0, 0, 0.7);
	}
}

html,
body {
	height: 100%;
	width: 100%;
	margin: 0;
	padding: 0;
	overflow: hidden;
	overscroll-behavior: none;
	touch-action: none;
}

body.shader-landing-page {
	position: fixed;
	inset: 0;
	background: var(--sl-bg);
	color: var(--sl-text);
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	transition: background-color 0.3s ease, color 0.3s ease;
}

.shader-landing {
	position: fixed;
	inset: 0;
	width: 100vw;
	height: 100vh;
	/* iOS Safari's 100vh is the LARGEST possible viewport (as if its
	   toolbars were collapsed), not the currently-visible one - with both
	   toolbars expanded, a 100vh-tall fixed container is taller than what's
	   actually on screen, pushing .thumbnails-row (position:absolute,
	   anchored to this container's bottom) below the visible fold, hidden
	   under Safari's own chrome. 100dvh tracks the real, current viewport
	   instead; kept as a progressive-enhancement override after the 100vh
	   fallback for browsers that don't support dvh. */
	height: 100dvh;
	overflow: hidden;
}

/*
 * Fake first-load preloader: dot grid (.dot-grid-canvas) and the
 * time-of-day background gradient stay visible throughout (nothing to
 * hide there - they're the "loading" backdrop), while the actual content
 * - logo, intro text, the card letter, the thumbnail cards - stays hidden
 * until body loses .is-preloading. See initPreloader() in
 * shader-landing.js: sessionStorage gates this to the very first load
 * per session, so it never replays on other page loads or when the logo
 * link brings you back to this same homepage mid-session.
 */
body.is-preloading .shader-landing__logo,
body.is-preloading .shader-landing__intro-backdrop,
body.is-preloading .shader-landing__intro,
body.is-preloading .border-outside,
body.is-preloading .thumbnails,
body.is-preloading .thumbnails-frames,
body.is-preloading .thumbnails-scroll-prev {
	opacity: 0;
	pointer-events: none;
}

.shader-landing__logo,
.shader-landing__intro-backdrop,
.shader-landing__intro,
.border-outside,
.thumbnails,
.thumbnails-frames {
	transition: opacity 0.6s ease;
}

/* Logo */

.shader-landing__logo {
	position: absolute;
	top: 24px;
	right: 24px;
	z-index: 10;
	display: inline-flex;
	line-height: 0;
}

.shader-landing__logo-img {
	display: none;
	height: 40px;
	width: auto;
	/* Softened to a muted grey rather than stark pure black/white. */
	opacity: 0.78;
}

.shader-landing__logo-img--light {
	display: block;
}

@media (prefers-color-scheme: dark) {
	.shader-landing__logo-img--light {
		display: none;
	}

	.shader-landing__logo-img--dark {
		display: block;
	}
}

/* Intro text (left side) */

.shader-landing__intro {
	position: absolute;
	top: 50%;
	left: clamp(40px, 9vw, 96px);
	transform: translateY(-50%);
	max-width: 360px;
	z-index: 6;
	color: var(--sl-text);
	font-family: "Helvetica Neue LT Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

.shader-landing__intro-title {
	margin: 0 0 12px;
	font-size: 32px;
	font-weight: 700;
	line-height: 1.15;
	letter-spacing: -0.01em;
}

.shader-landing__intro-text {
	margin: 0;
	font-size: 16px;
	font-weight: 400;
	line-height: 1.55;
	opacity: 0.75;
}

/*
 * Left-to-right reveal ("typing") effect for the intro title/text: a
 * clip-path wipe rather than a per-character JS typewriter, since
 * clip-path is a continuous, GPU-composited value - it draws smoothly at
 * any speed instead of the discrete per-glyph stepping a real typewriter
 * effect has. Played via the Web Animations API (see playIntroTextReveal
 * in shader-landing.js) rather than a restartable CSS animation - the
 * "animation:none + read offsetWidth + animation:''" trick needed to
 * restart a CSS animation forces a synchronous layout flush of whatever
 * else changed that tick (aurora blob recolour, opacity toggles, the new
 * textContent), which is what made the switch feel like it hitched.
 * Default state here is fully hidden - JS both plays the initial reveal
 * on load and replays it on every thumbnail click.
 */
.shader-landing__intro-title,
.shader-landing__intro-text {
	clip-path: inset( 0 100% 0 0 );
}

/* Mobile-only dark gradient panel behind the intro text - hidden on desktop,
   where there's already enough empty space for the text to sit on the
   ordinary page background without needing extra contrast help. */
.shader-landing__intro-backdrop {
	display: none;
	position: absolute;
	inset: 0;
	z-index: 5;
	pointer-events: none;
	background: linear-gradient(to right, rgba(0, 0, 0, 0.72) 0%, rgba(0, 0, 0, 0.5) 42%, rgba(0, 0, 0, 0) 78%);
}

@media (max-width: 1024px) {
	.shader-landing__intro-backdrop {
		display: block;
	}

	.shader-landing__intro {
		left: 20px;
		max-width: 68vw;
		/* Always light on mobile: the backdrop panel above is always dark,
		   regardless of light/dark page theme, so var(--sl-text) (which is
		   dark in light mode) would be illegible here. */
		color: #f5f5f7;
	}

	.shader-landing__intro-title {
		font-size: 22px;
	}

	.shader-landing__intro-text {
		font-size: 14px;
	}
}

/* Shader gallery (from shader-image-transition) */

.container-gallary {
	position: relative;
	width: 100%;
	height: 100%;
}

/*
 * Dot grid, drawn on a <canvas> (see drawDotGrid/updateDotGrid in
 * shader-landing.js) instead of a CSS radial-gradient background, so
 * individual dots can be nudged away from the cursor (magnet effect) and
 * eased back once it moves on. Same 22px pitch/colour/opacity as the
 * plain CSS version it replaces - the JS reads prefers-color-scheme
 * itself to match light/dark.
 */
.dot-grid-canvas {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
}

/*
 * Placeholder circle for the upcoming "reference logos" content: partner/
 * client logos that will each link out to their site, scattered across the
 * dot-grid background - behind the ring, cards and text, which all sit at
 * higher z-indexes (6/10/11/20). Numbered stand-ins for now.
 *
 * Only relevant to card 2 ("Referenciáink") - hidden the rest of the time,
 * toggled by updateReferenceLogosVisibility() in shader-landing.js.
 */
.reference-logos {
	position: absolute;
	inset: 0;
	/*
	 * In front of the dot-grid canvas (z-index:auto) but BEHIND the card
	 * letter (.border-outside, z-index 3) - the client wants the wandering
	 * logos passing behind it, not drawn over it. Still in front of
	 * nothing else that matters here.
	 */
	z-index: 1;
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.4s ease;
}

.reference-logos.is-visible {
	opacity: 1;
}

/*
 * .reference-logo is just the wander anchor: shader-landing.js
 * (updateReferenceLogosWander()) drives its left/top every frame at a
 * constant, uniform velocity (no easing/deceleration) - each logo picked
 * its own slow speed and heading once and keeps it forever, independent of
 * the others. One that drifts past a page edge wraps around and re-enters
 * from the opposite side rather than bouncing back. No CSS `transition`
 * here on purpose - the position must update immediately every frame to
 * stay in sync with the JS-computed x/y, not lag behind on an eased curve.
 * .reference-logo__dot is the actual visual circle, offset back to centre
 * on that anchor (negative margin) and separately scaled up on click
 * (growReferenceLogos(), Web Animations API - same reasoning as the intro
 * text reveal: a restartable CSS animation needs the "animation:none +
 * reflow + reapply" trick to replay, which forces a layout flush;
 * .animate() just starts a fresh, independent one).
 * Sized 3x the original 48px placeholder (144px) per the client's request.
 */
.reference-logo {
	position: absolute;
	top: 0;
	left: 0;
}

.reference-logo__dot {
	position: absolute;
	top: 0;
	left: 0;
	width: 144px;
	height: 144px;
	margin: -72px 0 0 -72px;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background: #2a2a2a;
	/* Dark navy per the client's request - same colour as the mobile
	   column's numbers, kept identical rather than a separate desktop
	   value so the placeholder reads consistently across breakpoints. */
	color: #0a1657;
	font-family: "Helvetica Neue LT Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	font-weight: 700;
	font-size: 54px;
	opacity: 0.75;
	/* Rest state: shrunk down to a background dot's size until the pulse
	   (played on demand from JS) grows it - see playReferenceLogosPulse(). */
	transform: scale(0.04);
	/*
	 * Re-enables hit-testing on just the dot itself - .reference-logos as a
	 * whole is pointer-events:none (and pointer-events is inherited), since
	 * it's otherwise a decorative overlay nothing should be able to click
	 * through to. Clicking a dot now selects that number to show in the
	 * ring (see selectReferenceLogo() in shader-landing.js).
	 */
	pointer-events: auto;
	cursor: pointer;
	/*
	 * Only animates opacity, not transform - the WAAPI grow-in animation
	 * (see REFERENCE_LOGO_GROW_KEYFRAMES in shader-landing.js) already
	 * drives transform independently, and mixing a CSS transition on the
	 * same property into that would be a fight, not a blend.
	 *
	 * 0.45s, not the ring's old 0.3s: on the mobile/tablet column, paging
	 * to a square that isn't already on screen also kicks off a ~480ms
	 * scroll (REFERENCE_LOGO_SCROLL_MS in shader-landing.js) to bring it
	 * into view. A 0.3s opacity fade can finish importing before that
	 * scroll does, so by the time the square is actually visible past the
	 * list's mask edge it's already sitting at full opacity - the fade
	 * itself happened, just off-screen. Matching this roughly to the
	 * scroll duration keeps the fade visibly in progress for as much of
	 * the scroll as possible.
	 */
	transition: opacity 0.45s ease;
}

/*
 * Registering the angle as a real <angle> custom property (rather than a
 * plain unregistered one) is what makes it smoothly animatable at all -
 * an unregistered custom property can't be interpolated, it would just
 * snap straight to the end value. This is what lets the animation below
 * spin the gradient's own internal angle without touching the element's
 * geometry.
 */
@property --reference-logo-ring-angle {
	syntax: '<angle>';
	inherits: false;
	initial-value: 0deg;
}


/*
 * The 3 boundaries between the ring's 4 colours (see the conic-gradient
 * below), each independently animated between its resting position and
 * +/-10 percentage points - each boundary shifting moves colour share
 * from one side to the other, so as e.g. stop2 swings later the pink
 * band widens (borrowing width from yellow) and as it swings back pink
 * returns to its base share while the shift moves on to the next
 * boundary. Same registration requirement as the angle above - plain
 * unregistered custom properties can't be smoothly interpolated.
 */
@property --reference-logo-ring-stop1 {
	syntax: '<percentage>';
	inherits: false;
	initial-value: 25%;
}
@property --reference-logo-ring-stop2 {
	syntax: '<percentage>';
	inherits: false;
	initial-value: 50%;
}
@property --reference-logo-ring-stop3 {
	syntax: '<percentage>';
	inherits: false;
	initial-value: 75%;
}

/*
 * The "is current" ring, sitting exactly ON the dot's own edge pixels
 * (inset: 0, not offset outward) rather than floating outside it - a
 * ::after rather than border/box-shadow because only a pseudo-element can
 * carry a gradient stroke: border-color can't take a gradient at all, and
 * box-shadow's spread is a flat colour. The classic gradient-ring
 * technique - a gradient-filled layer, masked down to just its own
 * outline via the content-box/exclude composite - gives a genuinely
 * multi-colour edge instead of a single flat tint, and because the mask
 * is generated from this element's own border-radius/corner-shape, the
 * ring always follows the square's actual corner curvature exactly,
 * squircle included. conic-gradient (not linear) so the colour sweep
 * genuinely follows the ring's own circular path rather than crossing it
 * diagonally.
 *
 * The slow spin animates --reference-logo-ring-angle (conic-gradient's
 * own `from` angle), NOT a transform:rotate on this element - rotating
 * the element itself would spin its geometry too (this box's own square
 * corners, which the mask is generated from), making the ring's corners
 * drift out of alignment with the dot's actual corners as it turns.
 * Animating just the gradient's internal angle keeps the ring's shape
 * perfectly static and edge-locked while only the colours travel around
 * it. Colours: RING_PALETTE's sky blue / violet / rose pink
 * (shader-landing.js) plus a lighter, slightly orange-leaning lemon
 * yellow - same aurora family as the ring itself, not an unrelated accent
 * colour. First and last stops match so the loop has no visible seam.
 */
.reference-logo__dot::after {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: inherit;
	corner-shape: inherit;
	/*
	 * 1.5px looked fine at full opacity, but at this ring's actual 0.55
	 * opacity (see .is-current::after below) the mask-composite:exclude
	 * technique's own anti-aliasing feather - which is inherently uneven
	 * around a circle, thinnest at some angles and fuller at others - could
	 * drop to near-zero coverage at the thin angles, making the ring
	 * effectively vanish there while staying clearly visible elsewhere.
	 * Confirmed live: 1.5px produced a fully invisible arc segment, 2px was
	 * faint, 2.5px still had one noticeably fainter segment, 3px reads
	 * evenly all the way around. Bumped here (not just opacity) because
	 * raising opacity would also brighten the already-fine segments past
	 * the intended subtle look.
	 */
	padding: 3px;
	background: conic-gradient(
		from var(--reference-logo-ring-angle),
		#6ab8ff 0%,
		#a78bfa var(--reference-logo-ring-stop1),
		#ff7fc0 var(--reference-logo-ring-stop2),
		#ffd166 var(--reference-logo-ring-stop3),
		#6ab8ff 100%
	);
	-webkit-mask: linear-gradient( #fff 0 0 ) content-box, linear-gradient( #fff 0 0 );
	-webkit-mask-composite: xor;
	mask-composite: exclude;
	opacity: 0;
	transition: opacity 0.45s ease; /* see the matching comment on .reference-logo__dot's own transition above */
	pointer-events: none;
}

/* Marks whichever logo is currently selected (see
   applyReferenceLogoSelection() in shader-landing.js) - full opacity plus
   the thin, slowly-spinning gradient edge ring above, both on the
   mobile/tablet column and desktop's wandering circles. */
/*
 * Registered (typed <color>) so the two custom properties below can be
 * smoothly animated by @keyframes at all - an unregistered custom
 * property can't be interpolated, it just snaps straight to the end
 * value, same reasoning as --reference-logo-ring-angle/-stop1/2/3 above.
 * Alpha is baked into each 8-digit hex value (box-shadow takes the colour
 * as-is, no separate alpha argument the way rgba() has).
 */
@property --reference-logo-glow-1 {
	syntax: '<color>';
	inherits: false;
	initial-value: #6ab8ffA8;
}
@property --reference-logo-glow-2 {
	syntax: '<color>';
	inherits: false;
	initial-value: #ff7fc06B;
}

/*
 * Same 4 colours as the ring's own conic-gradient (sky blue/violet/rose
 * pink/lemon yellow), same 7s linear duration as referenceLogoRingSpin
 * below, so the glow reads as following the ring's own colour travel
 * instead of sitting there as a fixed, unrelated two-tone light. The two
 * properties are offset a half-cycle apart (glow-2 starts where glow-1
 * is 2 stops ahead) so the inner/outer glow layers stay two DIFFERENT
 * colours from the set at any moment, matching the ring's own multi-hue
 * look rather than both glow layers matching each other.
 */
@keyframes referenceLogoGlowColor1 {
	0%, 100% { --reference-logo-glow-1: #6ab8ffA8; }
	25% { --reference-logo-glow-1: #a78bfaA8; }
	50% { --reference-logo-glow-1: #ff7fc0A8; }
	75% { --reference-logo-glow-1: #ffd166A8; }
}
@keyframes referenceLogoGlowColor2 {
	0%, 100% { --reference-logo-glow-2: #ff7fc06B; }
	25% { --reference-logo-glow-2: #ffd1666B; }
	50% { --reference-logo-glow-2: #6ab8ff6B; }
	75% { --reference-logo-glow-2: #a78bfa6B; }
}

.reference-logo__dot.is-current {
	opacity: 1;
	--reference-logo-glow-1: #6ab8ffA8;
	--reference-logo-glow-2: #ff7fc06B;
	/*
	 * Neon halo lives HERE, on the dot itself (opacity:1), not on the
	 * ::after ring below (opacity:0.55) - box-shadow is subject to its own
	 * element's opacity like everything else it paints, so a shadow placed
	 * on the 0.55-opacity ring was itself being rendered at effectively
	 * ~0.55 * its own rgba alpha, capping how vivid it could ever look no
	 * matter how high the rgba alpha or blur/spread were pushed - confirmed
	 * live (boosting the ::after shadow's alpha ~20% produced no visible
	 * change). Placed on the full-opacity dot instead, it isn't clipped by
	 * the ring's own mask/opacity and reads as intended. Scaled down for
	 * the smaller mobile/tablet squares in the media query below (blur/
	 * spread only - the animated colour custom properties are shared).
	 */
	animation:
		referenceLogoGlowColor1 7s linear infinite,
		referenceLogoGlowColor2 7s linear infinite;
	box-shadow:
		0 0 10px 1.2px var(--reference-logo-glow-1),
		0 0 22px 5px var(--reference-logo-glow-2);
}

.reference-logo__dot.is-current::after {
	opacity: 0.55;
	/*
	 * The three stop animations share a duration but start at staggered
	 * negative delays (a third of the cycle apart) so their peaks land
	 * one after another rather than all at once - that's what reads as
	 * "each colour takes its turn" rather than everything pulsing in
	 * unison.
	 */
	animation:
		referenceLogoRingSpin 7s linear infinite,
		referenceLogoRingStop1 6s ease-in-out infinite,
		referenceLogoRingStop2 6s ease-in-out infinite -2s,
		referenceLogoRingStop3 6s ease-in-out infinite -4s;
}

@keyframes referenceLogoRingSpin {
	to {
		--reference-logo-ring-angle: 360deg;
	}
}

@keyframes referenceLogoRingStop1 {
	0%, 100% {
		--reference-logo-ring-stop1: 25%;
	}
	50% {
		--reference-logo-ring-stop1: 35%;
	}
}

@keyframes referenceLogoRingStop2 {
	0%, 100% {
		--reference-logo-ring-stop2: 50%;
	}
	50% {
		--reference-logo-ring-stop2: 60%;
	}
}

@keyframes referenceLogoRingStop3 {
	0%, 100% {
		--reference-logo-ring-stop3: 75%;
	}
	50% {
		--reference-logo-ring-stop3: 85%;
	}
}

/*
 * Paging control for the mobile/tablet reference-logos column - same "◀"
 * glyph and styling as .thumbnails-scroll-prev, just rotated to point down
 * since it advances a vertical list instead of a horizontal row (see
 * initReferenceLogosScroll() in shader-landing.js). Desktop has no use for
 * it (the dots wander freely there, nothing to page through), hence hidden
 * by default and only shown in the mobile media query below.
 */
.reference-logos-scroll-next {
	display: none;
}

/*
 * Time-of-day gradient - first pass, colours copied 1:1 from stripe.com's
 * stats section (.stats-animation-gradient) as a reference implementation.
 * TODO: replace with our own aurora palette + an original graphic once the
 * behaviour (JS period detection + crossfade) is confirmed working - see
 * updateStatsGradient() in shader-landing.js.
 */
.stats-animation-gradient {
	position: absolute;
	inset: 0;
	overflow: hidden;
	opacity: 0.55; /* lets the dot grid underneath still peek through */
	pointer-events: none;
}

.stats-animation-gradient__gradient {
	position: absolute;
	inset: 0;
	opacity: 0;
	transition: opacity 1.2s ease;
}

.stats-animation-gradient__gradient--active {
	opacity: 1;
}

.stats-animation-gradient__gradient--pre-dawn {
	background: radial-gradient(1653px 1092px at 50% calc(100% + 28px), #486ffd 0, #7f81f3 9.84%, #c489ff 20.83%, #dac0ff 34.13%, #eadcff 44.86%, #f9f6ff 58.59%, #f8fafd 100%);
}

.stats-animation-gradient__gradient--sunrise {
	background: radial-gradient(1653px 1055px at 50% 104.6%, #cb83ff 0, #ff90b9 15.77%, #ffc977 30.62%, #ffd79b 38.04%, #fff1dc 50.11%, #fff 63.1%, #fcfdfe 77.95%, #f8fafd 98.81%);
}

.stats-animation-gradient__gradient--daytime {
	background: radial-gradient(1656px 1117px at 50% calc(100% + 53px), #0071c1 1.33%, #60a8e2 15.71%, #b4d8ff 33.15%, #d9ebff 45%, #f8fafd 60%);
}

.stats-animation-gradient__gradient--dusk {
	background: radial-gradient(1656px 1098px at 50% calc(100% + 48px), #ffb451 0, #efc680 16.73%, #b4d8ff 33.03%, #d2e8ff 43.38%, #fafdff 59.16%, #fdfeff 76.24%, #f8fafd 100%);
}

.stats-animation-gradient__gradient--sunset {
	background: radial-gradient(1660px 1064px at 50% 100%, #ffa577 0, #ff90a1 15.52%, #ddadff 30.09%, #ecd8ff 45.72%, #f5eaff 54.96%, #f8fafd 88.16%);
}

.stats-animation-gradient__gradient--night {
	background: radial-gradient(1655px 1133px at 50% calc(100% + 69px), #fcfdfe 1.11%, #6763e4 28.73%, #453bb3 45.76%, #29227d 63.37%, #1e2064 78.67%, #141e4b 100%);
}

/*
 * Plain positioning wrapper for the big letter display (.card-letter) -
 * used to carry the aurora ring/WebGL photo effect (see
 * shader_landing_js_animation_gotchas memory / git history), removed at
 * the client's request in favour of just the card's own initial letter,
 * no background/shape/glow at all, on both desktop and mobile/tablet.
 * Kept as an element (not replaced with the plain text node directly
 * inside .container-gallary) purely so initSwipeNavigation() in
 * shader-landing.js still has a stable, appropriately-sized hit area to
 * bind touch events to, and so .reference-logos (z-index 1) still has an
 * explicit sibling z-index to stay behind - see that class's own comment.
 */
.border-outside {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 450px;
	height: 450px;
	max-width: 70vmin;
	max-height: 70vmin;
	transform: translate(-50%, -50%);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 3;
}

.card-letter {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	font-weight: 700;
	font-size: clamp(96px, 30vmin, 220px);
	line-height: 1;
	color: var(--sl-text);
	user-select: none;
}

/*
 * .thumbnails-row is the fixed-position anchor (bottom-right); the
 * "◀" button inside it stays put while only .thumbnails itself
 * scrolls, so the row can grow past 5 cards later without needing a
 * layout change here - see createThumbnails()/initThumbnailScroll() in
 * shader-landing.js.
 */
.thumbnails-row {
	position: absolute;
	bottom: 20px;
	right: 20px;
	z-index: 10;
	display: flex;
	align-items: center;
	gap: 8px;
	max-width: calc(100% - 40px);
}

.thumbnails-scroll-prev {
	flex-shrink: 0;
	width: 26px;
	height: 26px;
	padding: 0;
	border: none;
	background: none;
	color: var(--sl-text);
	font-size: 10px;
	line-height: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	opacity: 0.75;
	transition: opacity 0.3s ease-in-out;
}

.thumbnails-scroll-prev:hover {
	opacity: 0.95;
}

.thumbnails {
	display: flex;
	flex-direction: row;
	gap: 10px;
	overflow-x: auto;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}

.thumbnails::-webkit-scrollbar {
	display: none;
}

/*
 * Card silhouette (rounded corners + the top-centre notch) traced pixel-
 * for-pixel from the real thumbnail photos' own alpha channel (they ship
 * as pre-cut transparent webp, not plain rectangles - see card-Oraniths-1.
 * webp). Applying the exact same clip-path here, on the shared .thumbnail
 * container rather than per-image, means every card - real photo or the
 * placeholder rect - is cut identically instead of relying on each source
 * asset to carry its own matching cutout.
 */
.thumbnail {
	display: flex;
	justify-content: center;
	align-items: center;
	position: relative;
	width: 63px;
	height: 76px;
	cursor: pointer;
	opacity: 0.6;
	overflow: hidden;
	flex-shrink: 0;
	transition: all 0.4s ease;
	clip-path: polygon(
		2.3% 0%, 18.9% 0%, 21.2% 1.6%, 22% 3.2%, 23.5% 4.7%, 24.2% 6.3%,
		25.8% 7.9%, 26.5% 9.5%, 28% 11.1%, 28.8% 12.6%, 31.1% 14.2%,
		68.9% 14.2%, 71.2% 12.6%, 72.7% 11.1%, 73.5% 9.5%, 74.2% 7.9%,
		75.8% 6.3%, 76.5% 4.7%, 77.3% 3.2%, 78% 1.6%, 80.3% 0%, 97.7% 0%,
		100% 1.6%, 100% 98.4%, 97.7% 100%, 2.3% 100%, 0% 98.4%, 0% 1.6%
	);
}

/*
 * First and last of the currently visible cards sit at half opacity - a
 * deliberate "there's more to either side" cue for the scrollable row,
 * separate from the ordinary hover/active opacity below. `:first-child`/
 * `:last-child` (not fixed `nth-child(1)`/`nth-child(5)`) so this keeps
 * working automatically if cards are ever added or removed again.
 */
.thumbnail:first-child,
.thumbnail:last-child {
	opacity: 0.5;
}

/*
 * Corner-bracket frame graphic, inlined as a data URI instead of linking
 * out to the Webflow/website-files CDN. This is the exact original SVG
 * (two small L-shaped brackets, top-left + bottom-right), just re-hosted
 * locally rather than redrawn - see shader-landing.js's placeholderCardImage()
 * for the same "own the asset, don't hotlink" approach applied to the cards.
 *
 * Lives as a sibling overlay (#thumbnailsFrames), not a child of .thumbnail:
 * .thumbnail carries the card-shape clip-path, which clips every descendant
 * to the card outline - a child element can never bleed outside that shape.
 * Since the brackets are meant to float outside the card, shader-landing.js
 * (syncThumbnailFrames()) sizes/positions each .thumbnail-frame to exactly
 * match its card's rect expanded by THUMBNAIL_FRAME_GAP on every side, and
 * both background layers sit flush with THAT enlarged box's own corners
 * (position 0 0 / right 0 bottom 0) - which lands them precisely
 * THUMBNAIL_FRAME_GAP px outside the real card edge.
 */
.thumbnails-frames {
	position: absolute;
	inset: 0;
	pointer-events: none;
	z-index: 11;
}

.thumbnail-frame {
	position: absolute;
	background-image:
		url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2270%22%20height%3D%2299%22%20fill%3D%22none%22%20viewBox%3D%220%200%2070%2099%22%3E%3Cpath%20fill%3D%22%23131313%22%20d%3D%22M9.5%200a.5.5%200%200%201%200%201h-4A4.5%204.5%200%200%200%201%205.5v7a.5.5%200%200%201-1%200V1a1%201%200%200%201%201-1h8.5Z%22%2F%3E%3C%2Fsvg%3E"),
		url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2270%22%20height%3D%2299%22%20fill%3D%22none%22%20viewBox%3D%220%200%2070%2099%22%3E%3Cpath%20fill%3D%22%23131313%22%20d%3D%22M60.5%2099a.5.5%200%200%201%200-1h4a4.5%204.5%200%200%200%204.5-4.5v-7a.5.5%200%200%201%201%200V98a1%201%200%200%201-1%201h-8.5Z%22%2F%3E%3C%2Fsvg%3E");
	background-position: 0 0, right 0 bottom 0;
	background-size: 70px 99px, 70px 99px; /* native SVG size - the original, undistorted bracket scale */
	background-repeat: no-repeat, no-repeat;
	opacity: 0;
	transition: opacity 0.4s ease;
}

.thumbnail-frame.is-visible {
	opacity: 1;
}

.thumbnail.active {
	opacity: 1;
}

.thumbnail img {
	width: 55px;
	height: 70px;
	object-fit: cover;
}

@media (max-width: 1024px) {
	.thumbnails-row {
		bottom: 12px;
		right: 12px;
		max-width: calc(100% - 24px);
	}

	.thumbnail {
		width: 46px;
		height: 55px;
	}

	.thumbnail img {
		width: 38px;
		height: 49px;
	}

	/*
	 * Below 1024px the reference logos give up the full-screen wander
	 * (see updateReferenceLogosWander()/syncReferenceLogosLayoutMode() in
	 * shader-landing.js, which stops writing inline left/top here) and sit
	 * in a static row directly under the site logo instead - squares with
	 * squircle corners rather than the desktop circles, sized down to fit
	 * a phone/tablet header instead of the 144px desktop placeholder.
	 */
	.reference-logos {
		top: 76px; /* logo: 24px top + 40px height + 12px gap */
		/*
		 * Not the same 24px as the logo's own right offset: the logo SVG's
		 * intrinsic aspect ratio (507:571) renders at ~35.52px wide for its
		 * fixed 40px height, narrower than these 40px-wide squares. Right-
		 * aligning both at an identical 24px would leave the squares'
		 * horizontal centre ~2.24px left of the logo's - nudge right by
		 * that half-difference so the column centres under the logo mark
		 * instead of merely sharing its right edge.
		 */
		right: 21.76px;
		left: auto;
		bottom: auto;
		/*
		 * Above the card letter (.border-outside, z-index 3) in case its
		 * centred position ever overlaps this corner - matches the site
		 * logo's own z-index since both live in the same top-right header
		 * area.
		 */
		z-index: 10;
		display: flex;
		flex-direction: column;
		align-items: flex-end;
		gap: 8px;
		width: auto;
	}

	/*
	 * Only ~3 squares' worth of height is shown at once (136px), the rest
	 * reachable by scrolling - same "row that can grow past what fits"
	 * design as .thumbnails, just on the vertical axis. Hidden scrollbar
	 * because the visible reveal is the .reference-logos-scroll-next button
	 * below, not a native scrollbar.
	 */
	.reference-logos-list {
		display: flex;
		flex-direction: column;
		align-items: flex-end;
		gap: 8px;
		max-height: 136px;
		overflow-y: auto;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
		/*
		 * Soft fade at the BOTTOM edge only, right where the next square
		 * scrolls in above the "▼" button - not the top, which stays a
		 * hard clip. -webkit-mask-image is still required by Safari even
		 * on recent versions, not just older ones.
		 */
		mask-image: linear-gradient(to bottom, black 0, black calc(100% - 20px), transparent 100%);
		-webkit-mask-image: linear-gradient(to bottom, black 0, black calc(100% - 20px), transparent 100%);
	}

	.reference-logos-list::-webkit-scrollbar {
		display: none;
	}

	.reference-logos-scroll-next {
		display: flex;
		flex-shrink: 0;
		width: 40px;
		height: 26px;
		padding: 0;
		border: none;
		background: none;
		color: #f2f2f2;
		font-size: 10px;
		line-height: 1;
		align-items: center;
		justify-content: center;
		cursor: pointer;
		opacity: 0.75;
		transition: opacity 0.3s ease-in-out;
		/*
		 * "◀" pointing down instead of left. The extra translateY(1.4px)
		 * (applied pre-rotation, so it becomes a horizontal nudge post-
		 * rotation) corrects for the glyph's own ink sitting flush against
		 * the font's baseline rather than centred in its line-box - plain
		 * flex centring leaves it ~1.4px off pixel-centre under the square
		 * above it once rotated; measured empirically against the live
		 * glyph, not derived from generic font-metric assumptions.
		 */
		transform: rotate(-90deg) translateY(1.4px);
		/* .reference-logos as a whole is pointer-events:none (inherited) -
		   without this override the button, like the dots, would be
		   unclickable: elementFromPoint would skip straight past it to
		   whatever is painted underneath (the mobile bento photo card). */
		pointer-events: auto;
	}

	.reference-logos-scroll-next:hover {
		opacity: 0.95;
	}

	.reference-logo {
		position: static;
	}

	/* Same 40px height as .shader-landing__logo-img, so the column reads as
	   proportionally matched to the "C" logo mark sitting above it. */
	.reference-logo__dot {
		/* relative, not static - the .is-current ring is a ::after
		   positioned absolute against this element; static would leave it
		   positioning against some ancestor further up instead. */
		position: relative;
		width: 40px;
		height: 40px;
		margin: 0;
		border-radius: 13px;
		corner-shape: squircle;
		font-size: 15px;
		/*
		 * Overrides the desktop rest state (transform: scale(0.04)), which
		 * is normally only grown to full size by growReferenceLogos()'s
		 * Web Animations API call in shader-landing.js. That "materialise
		 * out of the background dot grid" effect only makes sense for the
		 * desktop version, which is literally laid over the dot grid - this
		 * static list isn't, so it doesn't need the grow-in animation, and
		 * skipping it means the icons are always full-size on their own
		 * via plain CSS, with no dependency on that JS/WAAPI call actually
		 * running to be visible at all.
		 */
		transform: none;
	}

	/* Thinner ring stroke than desktop's 1.5px - at this column's much
	   smaller 40px square size, 1.5px reads as visually heavier relative to
	   the shape than it does on the 144px desktop circles. */
	.reference-logo__dot::after {
		padding: 1px;
	}

	/* Scaled-down version of the desktop neon halo (see the base
	   .reference-logo__dot.is-current rule - lives on the dot itself, not
	   the ::after ring, so it isn't dimmed by the ring's own opacity:0.55)
	   - the 40px squares are roughly 3.5x smaller than the 144px desktop
	   circles, so the same glow radii would swamp the shape instead of
	   rimming it. Colour comes from the same animated
	   --reference-logo-glow-1/2 custom properties as desktop (already
	   cycling via the base rule's own animation) - only the blur/spread
	   numbers are overridden here. */
	.reference-logo__dot.is-current {
		box-shadow:
			0 0 3px 0.5px var(--reference-logo-glow-1),
			0 0 6px 1.5px var(--reference-logo-glow-2);
	}
}
