@charset "utf-8";

/* ==========================================================================
   CamTalk desktop 3D scroll section
   Scoped to .ct3d only, except the clearly marked header block at the end.
   Desktop only - the project breakpoint is 1000px (see common_mobile.css).
   ========================================================================== */

.ct3d {
	display: none;
}

/* Shared by both profiles: this is the section and the counter's own
   surfaces, and the phone uses the same ones. The values that really do
   depend on the width are overridden in the mobile block at the end. */
@media screen {

	.ct3d {
		display: block;
		position: relative;
		z-index: 0; /* stays under .gnb (z-index:1) and .submenu (z-index:10) */
		--ct3d-bg: #050506;
		--ct3d-safe: 0px;      /* height of the site header currently covering us */
		background-color: var(--ct3d-bg);
		color: #fff;
		font-size: 15px;
		line-height: 1.5;
		isolation: isolate;
	}

	.ct3d__track {
		position: relative;
		/* real height is set in px from CONFIG.scrollDistancePx; this is only
		   the pre-script fallback */
		height: 100vh;
	}

	.ct3d__stage {
		position: sticky;
		top: 0;
		height: 100vh;
		overflow: hidden;
		background-color: var(--ct3d-bg);
	}

	/* --- background layers (behind the canvas) --- */

	.ct3d__bg {
		position: absolute;
		inset: 0;
		overflow: hidden;
		background:
			radial-gradient(120% 90% at 50% 50%, rgba(255, 255, 255, 0.035) 0%, rgba(0, 0, 0, 0) 60%),
			var(--ct3d-bg);
	}

	/* No transition on this opacity, and none on any other the scene writes:
	   the value is sampled from a scroll progress that has already been
	   smoothed once, in the scene's own follower. Easing it a second time in
	   CSS makes it lag behind everything else in the same frame, which is what
	   a scrub backwards reads as a wobble. */
	.ct3d__blobs {
		position: absolute;
		inset: -15%;
		opacity: 0;
		will-change: opacity;
	}

	.ct3d__blob {
		position: absolute;
		border-radius: 50%;
		filter: blur(90px);
		opacity: 0.5;
		mix-blend-mode: screen;
	}

	.ct3d__blob--a {
		left: 26%;
		top: 34%;
		width: 26vw;
		height: 26vw;
		background: radial-gradient(circle, rgba(204, 12, 12, 0.55) 0%, rgba(204, 12, 12, 0) 70%);
		animation: ct3d-drift-a 26s ease-in-out infinite alternate;
	}

	.ct3d__blob--b {
		right: 24%;
		top: 26%;
		width: 30vw;
		height: 30vw;
		background: radial-gradient(circle, rgba(86, 140, 255, 0.42) 0%, rgba(86, 140, 255, 0) 70%);
		animation: ct3d-drift-b 32s ease-in-out infinite alternate;
	}

	.ct3d__blob--c {
		left: 40%;
		bottom: 14%;
		width: 34vw;
		height: 22vw;
		background: radial-gradient(circle, rgba(255, 196, 130, 0.35) 0%, rgba(255, 196, 130, 0) 70%);
		animation: ct3d-drift-c 38s ease-in-out infinite alternate;
	}

	@keyframes ct3d-drift-a {
		from { transform: translate3d(0, 0, 0) scale(1); }
		to   { transform: translate3d(4vw, -3vh, 0) scale(1.12); }
	}

	@keyframes ct3d-drift-b {
		from { transform: translate3d(0, 0, 0) scale(1.05); }
		to   { transform: translate3d(-5vw, 4vh, 0) scale(0.94); }
	}

	@keyframes ct3d-drift-c {
		from { transform: translate3d(0, 0, 0) scale(0.96); }
		to   { transform: translate3d(3vw, 3vh, 0) scale(1.1); }
	}

	/* --- canvas --- */

	.ct3d__canvas {
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		display: block;
	}

	/* hidden host for the <video> elements used as textures */
	.ct3d__media {
		position: absolute;
		left: 0;
		bottom: 0;
		width: 1px;
		height: 1px;
		overflow: hidden;
		opacity: 0;
		pointer-events: none;
	}

	.ct3d__media video {
		width: 1px;
		height: 1px;
	}

	/* ======================================================================
	   Overlay: brand block on the left, flip counter on the right.
	   `--ct3d-safe` keeps both of them (and the 3D framing, which the script
	   offsets by the same amount) clear of the site header.
	   ====================================================================== */

	.ct3d__overlay {
		position: absolute;
		inset: 0;
		padding: var(--ct3d-safe) 3.2vw 0 3.2vw;
		display: flex;
		align-items: center;
		justify-content: space-between;
		pointer-events: none;
	}

	/* the left-hand brand block is real geometry inside the canvas now, so the
	   overlay only has to place the counter */
	.ct3d__overlay--right {
		justify-content: flex-end;
	}

	.ct3d__side {
		opacity: 0;
		will-change: opacity, transform;
	}

	/* --- right: mechanical flip counter -------------------------------------
	   Built to the reference: separate tall near-black flaps with rounded
	   corners and small gaps, big white digits filling most of each flap,
	   a thin per-flap seam, and a layered glossy pink housing.
	   ---------------------------------------------------------------------- */

	.ct3d__side--right {
		position: relative;
		display: flex;
		align-items: center;
		gap: calc(var(--ct3d-cell) * 0.38);
		/* one variable drives every dimension below */
		--ct3d-cell: clamp(24px, 1.75vw, 36px);
		--ct3d-cell-h: calc(var(--ct3d-cell) * 1.42);
		--ct3d-round: calc(var(--ct3d-cell) * 0.13);
	}

	/* outer housing: glow + outer bevel */
	.ct3d__meter {
		position: relative;
		padding: calc(var(--ct3d-cell) * 0.155);
		border-radius: calc(var(--ct3d-cell) * 0.46);
		background:
			linear-gradient(163deg,
				#ffe6f2 0%, #ff8cc2 7%, #ff2f8e 17%, #e0105f 27%,
				#ff6fae 38%, #ffd3e8 46%, #ff2b8a 57%, #b8083f 70%,
				#ff7fb8 82%, #ffd9ec 92%, #e2115f 100%);
		box-shadow:
			0 0 0 1px rgba(255, 226, 240, 0.9),
			0 5px 13px rgba(150, 0, 55, 0.42),
			0 0 11px rgba(255, 45, 135, 0.30),
			0 0 26px rgba(255, 30, 120, 0.16);
	}

	/* inner bevel of the housing: bright top rail, bright bottom rail */
	.ct3d__meter::before {
		content: '';
		position: absolute;
		inset: 0;
		border-radius: inherit;
		background: linear-gradient(180deg,
			rgba(255, 255, 255, 0.92) 0%,
			rgba(255, 255, 255, 0.30) 6%,
			rgba(255, 255, 255, 0.00) 22%,
			rgba(0, 0, 0, 0.28) 55%,
			rgba(255, 255, 255, 0.22) 88%,
			rgba(255, 255, 255, 0.85) 100%);
		pointer-events: none;
		mix-blend-mode: soft-light;
	}

	/* specular streak sliding across the top rail */
	.ct3d__meter::after {
		content: '';
		position: absolute;
		left: 6%;
		right: 6%;
		top: calc(var(--ct3d-cell) * 0.04);
		height: calc(var(--ct3d-cell) * 0.13);
		border-radius: 999px;
		background: linear-gradient(90deg,
			rgba(255, 255, 255, 0) 0%,
			rgba(255, 255, 255, 0.85) 18%,
			rgba(255, 255, 255, 0.35) 42%,
			rgba(255, 255, 255, 0.9) 66%,
			rgba(255, 255, 255, 0) 100%);
		filter: blur(0.5px);
		pointer-events: none;
	}

	/* the dark well the flaps sit in */
	.ct3d__meter-inner {
		position: relative;
		display: flex;
		align-items: center;
		gap: calc(var(--ct3d-cell) * 0.085);
		padding: calc(var(--ct3d-cell) * 0.13) calc(var(--ct3d-cell) * 0.16);
		border-radius: calc(var(--ct3d-cell) * 0.32);
		background: #030001;
		box-shadow:
			inset 0 3px 10px rgba(0, 0, 0, 1),
			inset 0 -2px 6px rgba(0, 0, 0, 0.9),
			inset 0 0 0 1px rgba(255, 130, 180, 0.42);
	}

	/* one digit flap */
	.ct3d__cell {
		position: relative;
		width: var(--ct3d-cell);
		height: var(--ct3d-cell-h);
		border-radius: var(--ct3d-round);
		background:
			linear-gradient(180deg, #4a2833 0%, #24101a 46%,
				#331821 54%, #150810 100%);
		box-shadow:
			inset 0 2px 0 rgba(255, 214, 232, 0.55),
			inset 0 -2px 3px rgba(0, 0, 0, 0.95),
			inset 0 0 0 1px rgba(255, 160, 195, 0.14),
			0 0 0 1px rgba(0, 0, 0, 0.95),
			0 3px 7px rgba(0, 0, 0, 1);
		perspective: calc(var(--ct3d-cell) * 6);
		overflow: hidden;
		flex: 0 0 auto;
	}

	/* comma / plus: a narrow slot, not a full flap */
	.ct3d__cell--thin {
		width: calc(var(--ct3d-cell) * 0.44);
		background: none;
		box-shadow: none;
		border-radius: 0;
		overflow: visible;
	}

	/* the flip seam belongs to each flap */
	.ct3d__cell::after {
		content: '';
		position: absolute;
		left: 0;
		right: 0;
		top: 50%;
		height: 2px;
		margin-top: -1px;
		background: linear-gradient(180deg,
			rgba(0, 0, 0, 0.85) 0%,
			rgba(0, 0, 0, 0.95) 50%,
			rgba(255, 190, 215, 0.30) 52%,
			rgba(255, 190, 215, 0.06) 100%);
		z-index: 4;
		pointer-events: none;
	}

	.ct3d__cell--thin::after {
		display: none;
	}

	.ct3d__half,
	.ct3d__flap {
		position: absolute;
		left: 0;
		width: 100%;
		height: 50%;
		overflow: hidden;
		backface-visibility: hidden;
	}

	.ct3d__half--top,
	.ct3d__flap--front {
		top: 0;
		border-radius: var(--ct3d-round) var(--ct3d-round) 0 0;
		background: linear-gradient(180deg, #4a2833 0%, #24101a 100%);
		transform-origin: 50% 100%;
	}

	.ct3d__half--bottom,
	.ct3d__flap--back {
		bottom: 0;
		border-radius: 0 0 var(--ct3d-round) var(--ct3d-round);
		background: linear-gradient(180deg, #331821 0%, #150810 100%);
		transform-origin: 50% 0%;
	}

	.ct3d__cell--thin .ct3d__half,
	.ct3d__cell--thin .ct3d__flap {
		background: none;
		border-radius: 0;
	}

	.ct3d__cell--thin .ct3d__flap {
		display: none;
	}

	.ct3d__flap {
		z-index: 3;
	}

	.ct3d__flap--back {
		transform: rotateX(90deg);
	}

	/* the glyph is drawn full-flap inside each half and clipped by it */
	.ct3d__glyph {
		position: absolute;
		left: 0;
		width: 100%;
		height: var(--ct3d-cell-h);
		line-height: var(--ct3d-cell-h);
		text-align: center;
		font-family: "Helvetica Neue", Helvetica, Arial, "Arial Black", sans-serif;
		font-size: calc(var(--ct3d-cell) * 1.56);
		font-weight: 700;
		letter-spacing: -0.045em;
		color: #fff;
		text-shadow:
			0 0 1px rgba(255, 255, 255, 1),
			0 0 4px rgba(255, 70, 130, 0.95),
			0 0 8px rgba(255, 24, 92, 0.6),
			0 0 15px rgba(255, 10, 76, 0.3);
		font-variant-numeric: tabular-nums;
	}

	.ct3d__cell--thin .ct3d__glyph {
		font-size: calc(var(--ct3d-cell) * 0.95);
	}

	/* digits sit optically centred in the flap: the same nudge on both halves
	   keeps the two halves of a glyph in register across the seam */
	.ct3d__half--top .ct3d__glyph,
	.ct3d__flap--front .ct3d__glyph {
		top: calc(var(--ct3d-cell) * 0.035);
	}

	.ct3d__half--bottom .ct3d__glyph,
	.ct3d__flap--back .ct3d__glyph {
		bottom: calc(var(--ct3d-cell) * -0.035);
	}

	.ct3d__cell.is-flipping .ct3d__flap--front {
		animation: ct3d-flip-front calc(var(--ct3d-flip, 170ms) * 0.55)
			cubic-bezier(0.5, 0, 0.9, 0.4) forwards;
	}

	.ct3d__cell.is-flipping .ct3d__flap--back {
		animation: ct3d-flip-back calc(var(--ct3d-flip, 170ms) * 0.55)
			cubic-bezier(0.15, 0.7, 0.5, 1) calc(var(--ct3d-flip, 170ms) * 0.45) forwards;
	}

	@keyframes ct3d-flip-front {
		from { transform: rotateX(0deg); filter: brightness(1); }
		to   { transform: rotateX(-92deg); filter: brightness(0.5); }
	}

	@keyframes ct3d-flip-back {
		from { transform: rotateX(92deg); filter: brightness(0.45); }
		70%  { filter: brightness(0.9); }
		to   { transform: rotateX(0deg); filter: brightness(1); }
	}

	.ct3d__meter-unit {
		font-size: calc(var(--ct3d-cell) * 0.86);
		font-weight: 700;
		color: rgba(255, 255, 255, 0.82);
		text-shadow:
			0 0 6px rgba(255, 90, 150, 0.4),
			0 2px 5px rgba(0, 0, 0, 0.7);
		letter-spacing: 0.01em;
	}

	/* the plain number for assistive technology */
	.ct3d__counter-sr {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	/* --- static fallback: reduced motion / no WebGL --- */

	.ct3d--static .ct3d__track {
		height: auto;
	}

	.ct3d--static .ct3d__stage {
		position: relative;
		height: 82vh;
		min-height: 560px;
	}

	.ct3d--static .ct3d__canvas {
		display: none;
	}

	.ct3d--static .ct3d__blobs {
		opacity: 0.55;
	}

	.ct3d--static .ct3d__side {
		opacity: 1;
	}

	/* --- the two scene headings --------------------------------------------
	   Phone only, like the hint below: a wide window's composition already
	   says these things in the chapters under it and was signed off as it is.
	   ---------------------------------------------------------------------- */

	.ct3d__caption {
		display: none;
	}

	/* The phone's opening heading. Phone only, like the two above: the wide
	   window keeps its extruded block, which is still built there and only
	   there. */
	.ct3d__hero {
		display: none;
	}

	/* ----------------------------------------------------------------------
	   THE SCROLL HINT - one component, five scenes.

	   `.ct-hint` is everything about how it LOOKS; where it sits, and whether
	   it is laid out at all, is the host's own rule (.ct3d__hint below,
	   .ctf__hint in camtalk-final.css). Both sections drive it through the
	   same two hooks - `--a` for how present it is, `.is-live` for whether it
	   exists for the reader at all - and each section owns exactly ONE of
	   these elements, so two hints can never be on screen together.

	   The band it lives in is shared with the assistant's launcher, which is
	   right-aligned and about 150px wide at every supported width. So the
	   block is left-anchored and its measure stops short of the launcher
	   instead of being centred into it.
	   ---------------------------------------------------------------------- */

	.ct-hint {
		flex-direction: column;
		align-items: flex-start;
		gap: 2px;
		max-width: calc(100% - 168px);
		pointer-events: none;
		opacity: var(--a, 0);
		visibility: hidden;
	}

	.ct-hint.is-live {
		visibility: visible;
	}

	.ct-hint__label {
		font-size: clamp(11px, 3.1vw, 13px);
		font-weight: 600;
		line-height: 1.35;
		letter-spacing: -0.01em;
		word-break: keep-all;
		color: rgba(255, 255, 255, 0.72);
		text-shadow: 0 1px 10px rgba(0, 0, 0, 0.45);
	}

	/* The closing scene carries both labels and shows one: the phone gets the
	   brief's sentence, a wide window keeps the word its composition was
	   signed off with. */
	.ct-hint__label--narrow {
		display: none;
	}

	.ct-hint__label--wide {
		font-size: 10px;
		font-weight: 700;
		letter-spacing: 0.22em;
		color: rgba(255, 255, 255, 0.62);
	}

	@media (max-width: 1000px) {
		.ct-hint__label--wide {
			display: none;
		}

		.ct-hint__label--narrow {
			display: block;
		}
	}

	.ct-hint__arrow {
		font-size: 20px;
		line-height: 1;
		margin-left: -2px;   /* the glyph carries its own side bearing */
		color: #ff3d76;
		/* A slow breath, not a bounce: the brief rules out both a sharp pulse
		   and a chevron that keeps hopping. One 3.4s cycle of 3px, which
		   reads as alive when you look at it and disappears when you do not. */
		animation: ct-hint-drift 3.4s ease-in-out infinite;
	}

	/* The same component on a light scene. The accent keeps its colour; only
	   the words change side. */
	.ct-hint.is-on-light .ct-hint__label {
		color: rgba(24, 22, 28, 0.62);
		text-shadow: none;
	}

	@keyframes ct-hint-drift {
		0%, 100% { transform: translateY(0); opacity: 0.85; }
		50%      { transform: translateY(3px); opacity: 1; }
	}

	@media (prefers-reduced-motion: reduce) {
		.ct-hint__arrow {
			animation: none;
			opacity: 1;
		}
	}

	/* --- the scroll hint ---------------------------------------------------
	   Phone only: a wide window's opening composition was signed off without
	   it, and there is nothing below the counter there to put it in.

	   It lives in the strip between the counter's housing and the bottom
	   edge - measured at 390x844, the housing ends at y=756 and the strip is
	   88px tall - and it is kept narrow because the assistant's launcher is
	   right-aligned in that same strip on every supported width. Stacked
	   rather than laid out in a row for the same reason: 48px of label over a
	   chevron leaves the launcher untouched at 360px.
	   ---------------------------------------------------------------------- */

	.ct3d__hint {
		display: none;
	}

	/* readable stand-in for the 3D block when the scene cannot run */
	.ct3d__static-brand {
		position: absolute;
		left: 3.2vw;
		top: 50%;
		transform: translateY(-50%);
		display: none;
		flex-direction: column;
		align-items: center;
		width: clamp(240px, 21vw, 340px);
		text-align: center;
	}

	.ct3d--static .ct3d__static-brand {
		display: flex;
	}

	.ct3d__static-brand img {
		width: 42%;
		height: auto;
		filter: drop-shadow(0 10px 24px rgba(214, 20, 60, 0.45));
	}

	.ct3d__static-brand p {
		margin-top: 6%;
		font-size: clamp(38px, 3.4vw, 62px);
		line-height: 1.02;
		font-weight: 800;
		letter-spacing: -0.02em;
		color: #efe9f2;
		text-shadow: 0 3px 0 rgba(0, 0, 0, 0.55), 0 0 26px rgba(255, 86, 140, 0.3);
	}

	.ct3d__static-brand span {
		display: block;
	}

	.ct3d__static-brand .is-pink {
		color: #ff3d76;
		font-size: 1.3em;
		text-shadow: 0 3px 0 rgba(120, 0, 30, 0.6), 0 0 28px rgba(255, 60, 120, 0.45);
	}

	.ct3d__stills {
		position: absolute;
		inset: 0;
		display: none;
		align-items: center;
		justify-content: center;
		gap: 14px;
	}

	.ct3d--static .ct3d__stills {
		display: flex;
	}

	.ct3d__still {
		width: 8.5vw;
		max-width: 150px;
		aspect-ratio: 9 / 19.5;
		object-fit: cover;
		border-radius: 10px;
		opacity: 0.85;
	}

	.ct3d__still:nth-child(1),
	.ct3d__still:nth-child(5) {
		opacity: 0.4;
		transform: scale(0.86);
	}

	.ct3d__still:nth-child(2),
	.ct3d__still:nth-child(4) {
		opacity: 0.65;
		transform: scale(0.94);
	}

	/* --- disabled state: no empty height anywhere --- */

	.ct3d--off {
		display: none;
	}
}

/* ==========================================================================
   Existing site header (.gnb) over the scene.
   js/common.js adds .gnb_invert (white bar) past 150px of scroll; while the
   3D section covers the header, the body class below re-skins that same bar
   dark, and during the full-screen wall it is lifted out of the way. No
   second header, no competing scroll handler. Everything here is inert
   unless the body class is set, and this file only loads on the main page.
   ========================================================================== */

/* Light/dark state changes, so the bar never snaps between the two skins.
   `opacity` is deliberately NOT in the list for .gnb itself: the bar's own
   opacity is written every frame by the follower in js/camtalk-scroll3d.js,
   and a transition layered on top of that fights it and snaps the moment the
   written value overtakes the one the transition is still heading for. The
   parts that really do only change on a state switch keep theirs. */
.gnb {
	transition: background-color 0.35s ease, color 0.35s ease,
		border-color 0.35s ease;
}

.gnb .logo a b,
.gnb .right img {
	transition: color 0.35s ease, opacity 0.35s ease, filter 0.35s ease;
}

@media screen {

	/* ONE declaration for where the bar is and how solid it is, always
	   present. Nothing about it is attached to a class, so no class can
	   appear or disappear underneath a value that is still moving - which is
	   what used to make the bar jump the moment it had nearly arrived.
	   Both custom properties default to "in place and opaque", which is
	   exactly what every page that never sets them gets. */
	.gnb {
		transform: translate3d(0, var(--ct3d-nav-y, 0px), 0);
		opacity: var(--ct3d-nav-a, 1);
		will-change: transform, opacity;
	}

	body.ct3d-dark-nav .gnb.gnb_invert {
		background-color: rgba(6, 6, 8, 0.42);
		-webkit-backdrop-filter: blur(12px);
		backdrop-filter: blur(12px);
		border-bottom-color: rgba(255, 255, 255, 0.14);
		color: #fff;
	}

	body.ct3d-dark-nav .gnb.gnb_invert .logo a b {
		color: #fff;
	}

	body.ct3d-dark-nav .gnb.gnb_invert .right img {
		filter: none;
		opacity: 0.35;
	}

	body.ct3d-dark-nav .gnb.gnb_invert .menu .on,
	body.ct3d-dark-nav .gnb.gnb_invert .menu a:hover {
		text-decoration-color: rgba(204, 12, 12, 0.75);
	}

	/* Half-faded is not clickable: the bar stops taking clicks well before
	   it has gone, and `visibility` below only comes off once the fade has
	   actually finished - which is also what keeps its links out of the tab
	   order while it is away. */
	body.ct3d-nav-inert .gnb {
		pointer-events: none;
	}

	body.ct3d-nav-hidden .gnb {
		pointer-events: none;
		visibility: hidden;
	}
}

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

	.ct3d__blobs,
	.ct3d__blob {
		animation: none !important;
	}

	.ct3d__cell.is-flipping .ct3d__flap--front,
	.ct3d__cell.is-flipping .ct3d__flap--back {
		animation: none !important;
	}

	.gnb {
		will-change: auto;
	}
}

/* ==========================================================================
   MOBILE PROFILE

   The section, the stage and every surface of the counter are shared with the
   wide window (above). What changes on a phone is only what genuinely depends
   on the width: the stage's height unit, the size the counter is locked to,
   and the scale of the background glows. The camera, the composition and the
   scroll distance are not CSS at all - they come from CONFIG.mobile in
   js/camtalk-scroll3d.js.
   ========================================================================== */

@media screen and (max-width: 1000px) {

	/* A stable height unit on purpose. `100vh` on a phone is the height with
	   the address bar HIDDEN, so the stage would be taller than the visible
	   area until the user scrolls; `100dvh` follows the bar and would resize
	   the stage - and with it the camera and the progress - mid-animation.
	   `100svh` is the height with the bar shown: always visible, never moves.
	   The fallback above it covers browsers without the unit. */
	.ct3d--mobile .ct3d__stage {
		height: 100vh;
		height: 100svh;
	}

	/* The glows are sized in vw, which on a phone would leave them as three
	   small dots. Scaled to the narrow frame. */
	.ct3d--mobile .ct3d__blob--a {
		left: 6%;
		top: 26%;
		width: 68vw;
		height: 68vw;
	}

	.ct3d--mobile .ct3d__blob--b {
		right: 4%;
		top: 16%;
		width: 76vw;
		height: 76vw;
	}

	.ct3d--mobile .ct3d__blob--c {
		left: 12%;
		bottom: 10%;
		width: 84vw;
		height: 56vw;
	}

	.ct3d--mobile .ct3d__blob {
		filter: blur(56px);
	}

	/* The counter is locked to a share of the width rather than to the
	   desktop clamp, so the digits, the comma slots, the plus and the unit
	   all have room at 360px - including the extra digit a billion adds,
	   which the display absorbs by shrinking the cell, never the outer box. */
	.ct3d__side--right {
		--ct3d-cell: clamp(19px, 6.1vw, 27px);
	}

	/* ----------------------------------------------------------------------
	   THE OPENING HEADING, over the phone.

	   What stood here was extruded geometry: nine syllables of real bevelled
	   body plus the app icon on a plate, with two lights of its own. It read
	   as a logo lock-up rather than as a heading, and it was the single
	   largest piece of the scene's first build - on the very screen the
	   reader is looking at while it happens. This is the brief's reference
	   composition instead, in type: an eyebrow, two lines and a quiet
	   subtitle.

	   Sized as a share of the WIDTH, like everything else in this profile, so
	   the block holds the same relation to the phone under it at 360 as at
	   430. Its top follows `--ct3d-safe`, the live height of the site header,
	   so it never has to guess where the bar is.

	   Measured at 390x844 with the composition the camera track holds: the
	   block runs 70..212, the phone's top edge is at 325. At 360x800 it runs
	   66..199 against a phone top of 308.
	   ---------------------------------------------------------------------- */

	/* Both phone modes, not just the scene one: `ct3d--mobile` marks the
	   running scene, and a phone with reduced motion - or without WebGL -
	   resolves to `ct3d--static` instead, which is still a phone and still
	   has to show this heading rather than the block it replaces. */
	.ct3d--mobile .ct3d__hero,
	.ct3d--static .ct3d__hero {
		position: absolute;
		left: 0;
		right: 0;
		top: calc(var(--ct3d-safe) + 14px);
		z-index: 3;
		display: block;
		padding: 0 20px;
		text-align: center;
		pointer-events: none;
		opacity: var(--a, 0);
		visibility: hidden;
	}

	.ct3d--mobile .ct3d__hero.is-live {
		visibility: visible;
	}

	/* If the scene cannot run at all, the block is simply there - and it is
	   the composition, so it sits in the stage rather than over it. */
	.ct3d--static .ct3d__hero {
		position: relative;
		top: 0;
		--a: 1;
		visibility: visible;
		padding: 8vh 20px 0;
	}

	/* ...and then the stand-in it replaces has nothing left to stand in for */
	.ct3d--mobile .ct3d__static-brand,
	.ct3d--static .ct3d__static-brand {
		display: none;
	}

	.ct3d__hero-eyebrow {
		margin: 0;
		font-size: 11px;
		font-weight: 700;
		letter-spacing: 0.34em;
		text-indent: 0.34em;   /* the tracking would push it off centre */
		color: #ff5b87;
	}

	.ct3d__hero-title {
		margin: 6px 0 0;
		line-height: 1.04;
		letter-spacing: -0.03em;
		word-break: keep-all;
	}

	.ct3d__hero-line {
		display: block;
		font-size: clamp(30px, 8.8vw, 38px);
		font-weight: 800;
		color: #f4f1f6;
		text-shadow: 0 2px 18px rgba(0, 0, 0, 0.55);
	}

	/* The one coloured line. Same paint as the scene headings below it, so
	   the section reads as one system rather than as two pinks. */
	.ct3d__hero-line--accent {
		margin-top: 2px;
		font-size: clamp(38px, 11.2vw, 48px);
		font-weight: 900;
		background-image: linear-gradient(100deg, #ff9d7a 0%, #ff5b87 45%, #ff2f74 100%);
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
		-webkit-text-fill-color: transparent;
		text-shadow: none;
	}

	@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
		.ct3d__hero-line--accent {
			background-image: none;
			color: #ff5b87;
			-webkit-text-fill-color: #ff5b87;
		}
	}

	.ct3d__hero-sub {
		margin: 10px 0 0;
		font-size: clamp(13px, 3.7vw, 15px);
		font-weight: 500;
		line-height: 1.5;
		letter-spacing: -0.01em;
		word-break: keep-all;
		color: rgba(244, 241, 246, 0.78);
		text-shadow: 0 1px 12px rgba(0, 0, 0, 0.5);
	}

	/* ----------------------------------------------------------------------
	   The two scene headings.

	   `--ct3d-safe` is the height of the site header as it actually is at
	   this instant, written every frame by the scene, so both blocks follow
	   the bar rather than guessing where it will be: the wall heading runs
	   while the bar is lifted away and sits near the top edge, the ring
	   heading runs while the bar is back and clears it by 18px.

	   The scrim is only under the wall heading, and only because what is
	   behind it there is a full-bleed video whose brightness is not ours to
	   choose. It is a gradient to transparent over a third of the screen, so
	   it darkens the band the type is in and leaves the panels below it
	   alone. The ring heading is over the stage's own black and needs none.
	   ---------------------------------------------------------------------- */

	.ct3d--mobile .ct3d__caption {
		position: absolute;
		left: 0;
		right: 0;
		z-index: 3;
		display: block;
		padding: 0 22px;
		text-align: center;
		pointer-events: none;
		opacity: var(--a, 0);
		visibility: hidden;
	}

	.ct3d--mobile .ct3d__caption.is-live {
		visibility: visible;
	}

	/* --- A: over the video wall -------------------------------------------
	   A reserved band, not a line pinned to the top edge. The scrim is a fixed
	   28% of the stage with the type sitting a third of the way down it, so
	   what the eye reads is a zone the heading lives in - and the zone does
	   not move, because the header is lifted away for the whole of this phase
	   and `--ct3d-safe` is 0 throughout it.
	   ---------------------------------------------------------------------- */
	.ct3d--mobile .ct3d__caption--wall {
		top: calc(var(--ct3d-safe) + 9%);
	}

	/* The two headings are set from the same system, at the size their own
	   longest line allows - which is not the same size, because the lines are
	   not the same length:

	     wall   "대화로 이어져요"                   7 glyphs
	     ring   "매력적인 영상, 솔직한 스토리,"     15 glyphs

	   The wall's heading takes the larger of the two, which is what the brief
	   asks for over the video: at 7.9vw its longest line still sets inside the
	   measure at 360. Giving the ring's the same size is what broke it into
	   three lines at 390 - the explicit break plus a line that no longer fits
	   - so that one keeps the size it was approved at. */
	/* The wall's heading is the largest of the three: it is the only thing on
	   the screen while it is up, and its longest line is seven glyphs. */
	.ct3d--mobile .ct3d__caption--wall .ct3d__caption-text {
		font-size: clamp(28px, 8.6vw, 37px);
		line-height: 1.22;
	}

	/* The ring's heading: the first phrase comes up to the same weight class,
	   the second line stays where it was signed off. The first line is 15
	   glyphs, so at this size it breaks - at its own comma, into two whole
	   phrases, which is the break the brief allows and the one a reader would
	   make. Nothing is condensed: the glyphs keep their width. */
	.ct3d--mobile .ct3d__caption--ring .ct3d__caption-lead {
		display: block;
		margin-bottom: 3px;
		font-size: clamp(27px, 8.2vw, 35px);
		line-height: 1.16;
	}

	/* WHERE it breaks, not whether. Left to itself the line broke at the last
	   space that fitted - between 솔직한 and 스토리 - which splits one phrase
	   across two lines. Each phrase is a unit here, so the only place left to
	   break is the space BETWEEN them, and the two lines are the two things
	   the sentence actually names. */
	.ct3d--mobile .ct3d__caption--ring .ct3d__caption-lead b {
		white-space: nowrap;
	}

	/* The band the type is read in.

	   Still a gradient to nothing, never a plate: what is behind it is a
	   full-bleed video whose brightest frames - the red app-icon panel among
	   them - are exactly where this heading runs. It reaches a third of the
	   way down the stage and is at its densest across the two lines
	   themselves, so the type has a constant ground under it while the
	   panels below keep their own contrast. */
	/* THE BLACK BAND.

	   `height: 34%` was written to mean a third of the STAGE. It does not: a
	   percentage height on an absolutely positioned element resolves against
	   its containing block, and the containing block here is the caption -
	   a two-line text box 88px tall. So the scrim was 30px tall, not 287, and
	   what it painted was a short dark strip with a hard bottom edge straight
	   across the picture behind the first line of the heading.

	   Measured before the fix, by photographing the same paused frame with
	   the scrim and without it and subtracting: it darkened CSS rows 68..90
	   and nothing else - a 22px bar, exactly the band the brief marks in red.

	   The height is now said in the stage's own unit (the stage is 100svh,
	   with a 100vh fallback, so both lines are given), and the ramp is longer
	   and starts softer. It covers the top third of the screen and fades to
	   nothing, which is a gradient rather than a bar: there is no row where
	   it stops. */
	.ct3d--mobile .ct3d__caption--wall::before {
		content: '';
		position: absolute;
		left: 0;
		right: 0;
		top: calc(0px - var(--ct3d-safe) - 9vh);
		top: calc(0px - var(--ct3d-safe) - 9svh);
		height: 42vh;
		height: 42svh;
		background: linear-gradient(to bottom,
			rgba(4, 4, 6, 0.72) 0%,
			rgba(4, 4, 6, 0.66) 16%,
			rgba(4, 4, 6, 0.54) 34%,
			rgba(4, 4, 6, 0.38) 52%,
			rgba(4, 4, 6, 0.22) 68%,
			rgba(4, 4, 6, 0.10) 84%,
			rgba(4, 4, 6, 0) 100%);
		pointer-events: none;
		z-index: -1;
	}

	/* --- B: over the open ring ---------------------------------------------
	   Measured, not guessed: through the whole of this heading's phase
	   (0.730 .. 0.855) the ring's top edge sits between y=306 and y=334 at
	   390x844 - it barely moves. The heading used to end at y=159, leaving
	   164px of nothing between the two; its bottom edge is now ~56px above the
	   ring at its highest, which is a gap that reads as a relationship rather
	   than as a gulf.
	   ---------------------------------------------------------------------- */
	.ct3d--mobile .ct3d__caption--ring {
		top: calc(var(--ct3d-safe) + 100px);
	}

	/* One system for both headings of this section, and for the two in the
	   closing scene: same weight, same tracking, same leading, sized from the
	   width. A size up from the caption these used to be - they are the only
	   words on the screen while they are up, and at 6.8vw over a moving video
	   wall they read as a label on it rather than as its heading. */
	/* ONE TYPOGRAPHIC SYSTEM, the phone's opening heading's own: Paperozi at
	   800 - a real ExtraBold cut, not a synthesised one - with the tracking
	   pulled in and the leading closed up. The line height was 1.42, which is
	   a caption's; at 1.2 the two lines read as one heading. */
	.ct3d__caption-text {
		/* A measure, rather than the full width of the screen: two lines of
		   Korean set to the edges read as a dense caption, which is what the
		   brief calls out. */
		max-width: 19em;
		margin: 0 auto;
		font-size: clamp(23px, 6.7vw, 28px);
		font-weight: 800;
		line-height: 1.2;
		letter-spacing: -0.03em;
		word-break: keep-all;
		color: rgba(255, 255, 255, 0.94);
		text-shadow: 0 2px 16px rgba(0, 0, 0, 0.6);
		/* The arrival the brief asks for, and nothing else: a short fade with
		   a small rise under it, both driven by the SAME alpha the scene
		   samples off the scroll. No scale, no blur, no bounce - and because
		   the two lines are one block, they cannot arrive separately. There
		   is no CSS transition anywhere on it either: the value is already a
		   continuous function of the scroll, so a transition on top of it
		   would be a second animation racing the first one backwards. */
		transform: translateY(calc(12px * (1 - var(--a, 0))));
		will-change: transform;
	}

	@media (prefers-reduced-motion: reduce) {
		.ct3d__caption-text {
			transform: none;
		}
	}

	/* --- the accents -------------------------------------------------------
	   Three of them, and only three: the two things the reader looks at and
	   the thing they lead to. The rest of the line stays light, which is what
	   makes them read as accents rather than as a coloured sentence.

	   `background-clip: text` paints the gradient through the glyphs, and it
	   needs `color: transparent` to show - which also means a text-shadow
	   would be visible THROUGH the letters. So the accents carry no shadow of
	   their own and lean on the scrim behind the block instead. No blend mode
	   is involved anywhere here: the stage under this heading is the section's
	   own flat background, and the only thing between the two is that scrim.
	   ---------------------------------------------------------------------- */
	.ct3d__caption-text b {
		font-weight: 800;
		background-image: linear-gradient(100deg, #ff9d7a 0%, #ff5b87 45%, #ff2f74 100%);
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
		-webkit-text-fill-color: transparent;
		text-shadow: none;
	}

	/* If the paint cannot be clipped to the glyphs, the words still have to be
	   readable - so they fall back to a solid coral rather than to nothing. */
	@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
		.ct3d__caption-text b {
			background-image: none;
			color: #ff7a8f;
			-webkit-text-fill-color: #ff7a8f;
		}
	}

	.ct3d--mobile .ct3d__hint {
		position: absolute;
		left: 22px;
		bottom: 14px;
		z-index: 3;
		display: flex;
	}

	/* readable stand-ins, if the scene cannot run at all */
	.ct3d--static .ct3d__static-brand {
		left: 50%;
		top: 8%;
		transform: translateX(-50%);
		width: 62vw;
	}

	.ct3d--static .ct3d__stills {
		gap: 6px;
	}

	.ct3d__still {
		width: 17vw;
		max-width: none;
	}
}
