@charset "UTF-8";

/* ============================================================
   DIGITAL TWIN VIEWER
   digital-twin-viewer.css

   Styles for the interactive 3D viewport (engine:
   js/digital-twin-viewer.js, renderer: Babylon.js, self-hosted in
   vendor/babylon/). It uses the shared Interactive Module layout
   (css/component-layout.css), like the Exponential Possibility
   Architecture canvas:

     <div class="interactive-module interactive-frame dtv-widget">
       <div class="interactive-module__stage">
         <div class="dtv-stage"> canvas, start screen, loader, error </div>
       </div>
       <div class="interactive-module__bar"> controls | how it works </div>
     </div>

   so the module owns the bleed, the 16:9 stage, the frame and the
   controls/guide bar. This file only styles the viewer-specific parts.

   THEME
   The 3D scene's colours are custom properties on .dtv-widget,
   pointed at the design-system tokens in css/tokens.css. The JS reads
   them when the scene is built, so the sky, ground grid and
   placeholder follow the tokens (and dark mode, once it's on).
   ============================================================ */

.dtv-widget {
	/* ---- 3D scene (read by JS when the scene is built) ---- */
	--dtv-sky-zenith: var(--color-background-subtle);   /* top of the sky dome     */
	--dtv-sky-horizon: var(--color-background-page);    /* horizon + ground "paper" */
	/* Ground grid = the site grid (.grid-paper): major lines with
	   4 minor divisions between them, minor lines half the weight. */
	--dtv-grid-color: var(--color-border-subtle);        /* minor lines             */
	--dtv-grid-major-color: var(--color-border-subtle);  /* major lines             */
	--dtv-grid-size: 10;                                 /* metres between major
	                                                        lines (unitless, JS)    */
	--dtv-grid-divisions: 4;                             /* minor squares per major */
	--dtv-placeholder-fill: var(--color-background-subtle); /* placeholder block    */
	--dtv-placeholder-edge: var(--color-border-strong);     /* its outline          */

	background-color: var(--color-background-page);
	/* Own stacking context: the overlays' z-index values only order
	   things inside this widget, so nothing can rise above the fixed nav. */
	isolation: isolate;
}

.dtv-stage {
	position: absolute;
	inset: 0;
	overflow: hidden;
	background: var(--color-background-page);
}

/* The canvas only takes pointer/touch input once the scene is live,
   so before that it never traps scrolling on touch screens. */
.dtv-canvas {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	display: block;
	opacity: 0;
	pointer-events: none;
	outline: none;
	cursor: grab;
	-webkit-tap-highlight-color: transparent;
	transition: opacity var(--duration-slow) var(--easing-default);
}

.dtv-widget.is-ready .dtv-canvas {
	opacity: 1;
	pointer-events: auto;
	touch-action: none;
}

.dtv-canvas:active {
	cursor: grabbing;
}

/* Keyboard focus only (see is-keyboard in the JS). */
.dtv-widget.is-keyboard .dtv-canvas:focus {
	outline: var(--stroke-bold) solid var(--color-border-strong);
	outline-offset: calc(-1 * var(--stroke-bold));
}

/* Shared show/hide for the overlays: fades out, then drops out of
   the accessibility tree and hit testing. */
.dtv-overlay {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	opacity: 1;
	visibility: visible;
	transition: opacity var(--duration-base) var(--easing-default),
		visibility 0s linear 0s;
}

.dtv-overlay.is-hidden {
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition: opacity var(--duration-base) var(--easing-default),
		visibility 0s linear var(--duration-base);
}

/* ------------------------------------------------------------
   Start screen: the site grid (.grid-paper, in the markup) with a
   centred Primary button. Nothing 3D is downloaded until it's pressed.
   ------------------------------------------------------------ */
.dtv-start {
	z-index: 10;
	gap: var(--space-16);
}

/* Primary button on the mesh needs a solid fill (the default
   .nav-pill is transparent). */
.dtv-start .nav-pill {
	align-self: center;
	background-color: var(--color-background-page);
}

.dtv-note {
	font-family: "Nunito Sans", sans-serif;
	font-size: clamp(0.656rem, calc(0.31 * var(--vw)) + 0.594rem, 0.844rem); /* small tier */
	line-height: 140%;
	color: var(--color-text-secondary);
	margin: 0;
	padding: 0 var(--space-8);
	background-color: var(--color-background-page);
	text-align: center;
}

/* ------------------------------------------------------------
   Loader: the site's page-loader (cube animation, progress bar
   and "loading..." line — the page-loader__* classes come from
   film-and-animation.css, js/cube-loader.js animates the SVG).
   ------------------------------------------------------------ */
/* Sits on the site grid too (.grid-paper in the markup). */
.dtv-loader {
	z-index: 30;
}

.dtv-loader .page-loader__progress-fill {
	transition: width 0.25s var(--easing-default);
}

/* ------------------------------------------------------------
   Error message (no WebGL, model failed to load ...)
   ------------------------------------------------------------ */
.dtv-error {
	z-index: 31;
	gap: var(--space-16);
	padding: var(--content-padding);
	box-sizing: border-box;
	background: var(--color-background-page);
	text-align: center;
}

.dtv-error__text {
	max-width: 36em;
	margin: 0;
	font-family: "Open Sans", sans-serif;
	font-size: clamp(0.656rem, calc(0.31 * var(--vw)) + 0.594rem, 0.844rem);
	line-height: 140%;
	font-weight: 600;
	color: var(--color-text-primary);
}

.dtv-error .nav-pill {
	align-self: center;
}

/* ------------------------------------------------------------
   Controls: a row of round icon buttons in the Controls panel of the
   module's bar (under the scene). Disabled until the scene is live.
   ------------------------------------------------------------ */
/* Building switcher: a row of Primary pills above the controls; the
   current building is shown pressed (inverse), like auto-rotate. */
.dtv-models {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
	margin-bottom: var(--space-16);
}

.dtv-model-btn[aria-pressed="true"] {
	background: var(--color-background-inverse);
	border-color: var(--color-background-inverse);
	color: var(--color-text-inverse);
}

.dtv-controls__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
}

/* Round icon buttons: the Primary button (.nav-pill) at its own
   height, made square, so they get its liquid-fill hover, focus ring,
   pressed and disabled states for free. */
.dtv-icon-btn.nav-pill {
	width: var(--btn-height);
	padding: 0;
	align-self: center;
}

.dtv-icon-btn .material-symbols-outlined {
	font-size: 1.6em;
	line-height: 1;
}

/* Toggle (auto-rotate) when on: filled, like .nav-pill-active. */
.dtv-icon-btn[aria-pressed="true"]:not(:disabled) {
	color: var(--color-text-inverse);
}

.dtv-icon-btn[aria-pressed="true"]:not(:disabled)::before {
	transform: translateY(0%);
}

/* ------------------------------------------------------------
   Expanded: the viewer takes over the browser window like the page
   loader (.page-loader in film-and-animation.css): fixed, full viewport,
   above the nav, and still inside its section's printer-marks container
   (marks, colour/tint bars, HUD/proof layers), which is lifted out of the
   page with it. Not the Fullscreen API, so the browser chrome stays.
   The JS adds .dtv-takeover to the module's .section-container and
   .is-expanded to the widget (setExpanded in digital-twin-viewer.js).
   ------------------------------------------------------------ */

/* Page can't scroll behind it (the JS restores the scroll position
   on close, since the page reflows while the section is lifted out). */
html.dtv-is-expanded,
html.dtv-is-expanded body {
	overflow: hidden;
}

.section-container.dtv-takeover {
	position: fixed;
	inset: 0;
	z-index: 9999;
	height: calc(100 * var(--vh));
	/* same max-width + centring as everywhere else; the page colour is
	   painted out to the window edges around it */
	background-color: var(--color-background-page);
	box-shadow: 0 0 0 200vmax var(--color-background-page);
	display: flex;
	flex-direction: column;
	animation: dtv-expand-in var(--duration-base) var(--easing-default);
}

.dtv-takeover > .printer-marks-container {
	flex: 1 1 auto;
	min-height: 0;
	flex-direction: column;
	flex-wrap: nowrap;
}

.dtv-takeover > .printer-marks-container > .content-container {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
}

/* The module keeps its bleed (negative margins) and fills the space. */
.dtv-widget.is-expanded {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
}

/* The scene takes whatever height the bar leaves. */
.dtv-widget.is-expanded .interactive-module__stage {
	flex: 1 1 auto;
	min-height: 0;
	aspect-ratio: auto;
}

.dtv-widget.is-expanded .interactive-module__bar {
	flex: 0 0 auto;
}

/* Where the bar stacks to one column, drop the guide so the scene
   keeps most of the window. */
html.bp-max-1024 .dtv-widget.is-expanded .interactive-guide {
	display: none;
}
@media (max-width: 1024px) {
	html:not(.dfui) .dtv-widget.is-expanded .interactive-guide {
		display: none;
	}
}

@keyframes dtv-expand-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
	.dtv-canvas,
	.dtv-overlay {
		transition: none;
	}

	.section-container.dtv-takeover {
		animation: none;
	}
}
