@charset "UTF-8";

/* ============================================================
   TURNTABLE VIEWER
   turntable-viewer.css

   Styles for the scroll-driven 3D turntable (engine:
   js/turntable-viewer.js, renderer: a trimmed three.js build in
   vendor/three/). It sits in the media column of an Image + Text
   50/50 module:

     <div class="split-layout image-text">
       <div class="split-layout-media ttv-widget grid-paper interactive-frame">
         <img class="ttv-poster" src="…" alt="">   still, shows first
         <canvas class="ttv-canvas"></canvas>      live 3D, fades in over it
         <div class="ttv-loader"> cube loader </div>   only if no poster
       </div>
       <div class="split-layout-text text-block-container"> … </div>
     </div>

   .grid-paper (component-layout.css) draws the site grid behind the
   transparent canvas; .interactive-frame adds the 1px frame every 3D
   scene on the site gets. There are no controls: the only interaction
   is scrolling the page, which spins the model.
   ============================================================ */

.ttv-widget {
	/* Placeholder colours (read by JS when the scene is built) */
	--ttv-placeholder-fill: var(--color-background-subtle);
	--ttv-placeholder-edge: var(--color-border-strong);

	position: relative;
	overflow: hidden;
	isolation: isolate;
	/* Above the stacking breakpoint the panel matches the text column's
	   height (the 50/50 default); this floor keeps it from getting
	   too squat if the copy is ever short. */
	min-height: clamp(320px, calc(30 * var(--vw)), 640px);
}

/* Stacked (≤1024px): square, like the site's other 3D and code
   experiences below their wide layouts, rather than the 50/50
   modules' usual 16:9. */
/* breakpoint (max-width: 1024px): applied by class from js/ui-prefs.js (effective
   width = window width / UI scale); the @media copy is the no-JS fallback. */
html.bp-max-1024 .split-layout-media.ttv-widget {
		aspect-ratio: 1 / 1;
		min-height: 0;
	}
@media (max-width: 1024px) {
	html:not(.dfui) .split-layout-media.ttv-widget {
		aspect-ratio: 1 / 1;
		min-height: 0;
	}
}

/* Purely visual: never takes pointer or touch input, so scrolling
   over it always scrolls the page. */
.ttv-canvas {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	display: block;
	opacity: 0;
	pointer-events: none;
	transition: opacity var(--duration-slow) var(--easing-default);
}

.ttv-widget.is-ready .ttv-canvas {
	opacity: 1;
}

/* Poster: a still of the model at the start angle, rendered with the
   same framing, so the swap to live 3D is a cross-fade in place. It
   stays for Data Saver / reduced motion (.is-static) and if the 3D
   fails (.is-failed). */
.ttv-poster {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: contain;
	pointer-events: none;
	user-select: none;
	opacity: 1;
	transition: opacity var(--duration-slow) var(--easing-default);
}

.ttv-widget.is-ready .ttv-poster {
	opacity: 0;
}

/* Loader: the site's cube loader (page-loader__* classes from
   film-and-animation.css, animated by js/cube-loader.js), on the grid. */
.ttv-loader {
	position: absolute;
	inset: 0;
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: center;
	opacity: 1;
	visibility: visible;
	/* JS shows it (only when there's no poster) */
	transition: opacity var(--duration-base) var(--easing-default),
		visibility 0s linear 0s;
}

.ttv-loader.is-hidden {
	opacity: 0;
	visibility: hidden;
	transition: opacity var(--duration-base) var(--easing-default),
		visibility 0s linear var(--duration-base);
}

.ttv-loader .page-loader__progress-fill {
	transition: width 0.25s var(--easing-default);
}
