/**
 * The Wicked — project overlay (tw-project-modal).
 *
 * A project page opened over the Work grid, with the grid still visible behind it.
 *
 * ── THESE VALUES ARE NOT INVENTED — THEY ARE POPUP 668'S, READ OUT OF ITS SETTINGS ────────────
 * Hares: *"I want them to be exactly the same as the Popups."* So every number here is lifted
 * from 668's `_elementor_page_settings` and its Custom CSS rather than eyeballed:
 *
 *     width                 min(1760px, 94vw)
 *     height                92vh
 *     overlay               rgba(4, 4, 5, 0.82)   + backdrop-filter: blur(10px)
 *     panel                 #101013, 1px solid var(--tw-line), radius 0, no shadow
 *     panel scroll          overflow-y:auto; overscroll-behavior:contain
 *     close button          44px circle, top/right 20px, rgba(0,0,0,.5),
 *                           1px solid var(--tw-line2), hover border var(--tw-tan)
 *     entrance/exit         fadeIn / fadeOut, 0.5s
 *     page behind           does not scroll (668: prevent_scroll = yes)
 *
 * ⚠️ If 668's shell is ever retuned, this file does NOT follow automatically. That is the cost of
 * not being an Elementor popup, and it is written here so the divergence is noticed rather than
 * discovered.
 *
 * ⛔ THE OVERLAY SITS OUTSIDE `.tw-scope`, so `--tw-*` tokens do not resolve here (§6c, learned on
 * `popup-chrome.css`). Every custom property below therefore carries a hard-coded fallback that is
 * the real value — the `var()` is an upgrade when the token happens to be in scope, never a
 * dependency.
 */

.tw-pm {
	position: fixed;
	inset: 0;
	z-index: 100000; /* Above the sticky header (263), which is the only thing near this range. */
	display: flex;
	align-items: center;
	justify-content: center;

	background-color: rgba( 4, 4, 5, 0.82 );
	backdrop-filter: blur( 10px );
	-webkit-backdrop-filter: blur( 10px );

	opacity: 0;
	visibility: hidden;
	transition: opacity 0.5s ease, visibility 0s linear 0.5s;
}

.tw-pm.is-open {
	opacity: 1;
	visibility: visible;
	transition: opacity 0.5s ease, visibility 0s linear 0s;
}

/* ⚠️ `visibility` rather than `display` so the fade can run in BOTH directions — a display flip
 * cancels the transition and the exit animation never plays. */

.tw-pm__panel {
	position: relative;
	width: min( 1760px, 94vw );
	height: 92vh;
	background-color: #101013;
	border: 1px solid var( --tw-line, rgba( 255, 255, 255, 0.09 ) );
	border-radius: 0;
	box-shadow: none;

	overflow-y: auto;
	overflow-x: hidden;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
}

/* ⛔⛔ EVERY STATE IS LISTED, AND THAT IS NOT BELT-AND-BRACES — IT IS THE ONLY THING THAT WORKS.
 * Hello Elementor's `reset.css` paints EVERY `<button>` a pink `#c36` fill on `:hover` and
 * `:focus`, at (0,1,1). A plain `.tw-pm__close` is (0,1,0) and LOSES. `shared.css` already
 * neutralises this — but only inside `.tw-scope`, and this overlay is deliberately outside it
 * (same boundary that stops `--tw-*` tokens resolving here).
 *
 * ⚠️ MEASURED, and it shipped visibly wrong for one screenshot: the JS focuses this button when the
 * overlay opens, for keyboard users, so `button:focus` matched IMMEDIATELY and the close control
 * rendered as a **magenta circle** on first paint. Computed `rgb(204, 51, 102)`. Nineteen passing
 * assertions did not see it, because none of them read this element's colour.
 *
 * `.tw-pm .tw-pm__close:hover` is (0,3,0) and beats the theme in every state. */
.tw-pm .tw-pm__close,
.tw-pm .tw-pm__close:hover,
.tw-pm .tw-pm__close:focus,
.tw-pm .tw-pm__close:focus-visible,
.tw-pm .tw-pm__close:active {
	position: absolute;
	top: 20px;
	right: 20px;
	z-index: 2;

	width: 44px;
	height: 44px;
	border-radius: 50%;
	background: rgba( 0, 0, 0, 0.5 );
	border: 1px solid var( --tw-line2, rgba( 255, 255, 255, 0.14 ) );
	color: var( --tw-ink, #f6f4f0 );

	font-size: 18px;
	line-height: 1;
	cursor: pointer;
	opacity: 1;
	transition: border-color 0.2s ease;
}

/* Only the border reacts — 668's close button does exactly this. */
.tw-pm .tw-pm__close:hover,
.tw-pm .tw-pm__close:focus-visible {
	border-color: var( --tw-tan, #B18051 );
}

/* The fetched document's own body padding must not come with it. */
.tw-pm__content > .elementor {
	margin: 0;
}

/* ⛔ THE PAGE BEHIND MUST NOT SCROLL, AND `overflow:hidden` ON `body` ALONE DOES NOT DO IT ON iOS.
 * The JS pins the scroll position and this class holds it; both halves are required.
 *
 * ⛔⛔ `overflow-y: scroll` ON THE ROOT, **NOT** `overflow: hidden` — AND THAT IS THE WHOLE FIX FOR
 * A BUG HARES SAW IMMEDIATELY: *"When I click on it, the background moves slightly before opening
 * the Popup."* Hiding the root's overflow REMOVES THE SCROLLBAR, the content area grows by its
 * width (~15px on Windows), and every fixed element and centred column shifts sideways in the
 * instant before the overlay covers them. Keeping `scroll` reserves the gutter, so nothing moves.
 * ⚠️ It still cannot scroll: `position:fixed` on the body leaves the document with no scrollable
 * height, so the track is present but inert. The gutter is the point, not the scrolling. */
.tw-pm-locked {
	overflow-y: scroll;
}

.tw-pm-locked body {
	overflow: hidden;
	position: fixed;
	width: 100%;
}

/* ══ THE WAITING STATE ═══════════════════════════════════════════════════════════════════════════
 *
 * The overlay now opens at 0ms and fills when the document arrives ~630ms later (`project-modal.js`
 * measured the split). Two things make that read as progress rather than as a hang:
 *
 * ⚠️ 1. THE DIM AND THE BLUR ALREADY EXISTED — `rgba(4,4,5,.82)` + `backdrop-filter: blur(10px)`
 *    at the top of this file, copied from popup 668. They were simply never SEEN during loading,
 *    because the overlay did not exist until loading had finished. Opening the shell first is what
 *    makes them visible, and it is the entire answer to Hares asking for a blurred, darkened
 *    background behind the loader: it was already built, just not yet reachable.
 *
 * ⛔ 2. THE LOADER IS DELAYED BY 150ms IN JS, NOT HERE. Do not add a CSS `animation-delay` to
 *    "help" — a fast open removes `is-waiting` before it is ever added, so the loader must not be
 *    part of the initial paint at all. With the warm cache most opens never reach this state.
 */
.tw-pm__loader {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 22px;

	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition: opacity 0.35s ease, visibility 0s linear 0.35s;
}

.tw-pm.is-waiting .tw-pm__loader {
	opacity: 1;
	visibility: visible;
	transition: opacity 0.35s ease, visibility 0s linear 0s;
}

/* ⚠️ The header's own wordmark, already in cache — see the note in build(). Held well under its
 * natural size: at ~2.45:1 a 168px cap is about 69px tall, which sits comfortably inside the panel
 * at every viewport this site supports without needing a media query. */
.tw-pm__loader-mark {
	width: 168px;
	max-width: 42vw;
	height: auto;
	opacity: 0.42;
	user-select: none;
}

/* The rule is the site's own gesture — `tw-eyebrow` opens with exactly this hairline — rather than
 * a spinner, which would be the one piece of generic software UI on the whole site. */
.tw-pm__loader-rule {
	position: relative;
	display: block;
	width: 120px;
	height: 1px;
	overflow: hidden;
	background-color: rgba( 255, 255, 255, 0.09 );
}

.tw-pm__loader-rule::after {
	content: '';
	position: absolute;
	top: 0;
	left: 0;
	width: 40%;
	height: 100%;
	background-color: var( --tw-tan, #B18051 );
	/* ⚠️ `transform` and `opacity` only. Animating `left` or `width` would lay out and paint on
	 * every frame, and this runs while the browser is parsing a 50KB document and adopting
	 * stylesheets — the one moment on this site where the main thread is genuinely busy. */
	animation: tw-pm-sweep 1.35s cubic-bezier( 0.45, 0, 0.55, 1 ) infinite;
}

@keyframes tw-pm-sweep {
	0%   { transform: translateX( -100% ); }
	100% { transform: translateX( 350% ); }
}

/* The content fades in under the loader rather than appearing. On a first open there is nothing to
 * fade; on a cue-row hop this is the old film dimming out while the new one is fetched, which is
 * what stops the panel flicking between two projects. */
.tw-pm__content {
	transition: opacity 0.3s ease;
}

.tw-pm.is-loading .tw-pm__content {
	opacity: 0;
}

/* ── THE HOP'S OWN TIMINGS, AND WHY THEY ARE NOT THE RULE ABOVE ───────────────────────────────
 *
 * ⛔ THE FADE ABOVE EXISTED FOR MONTHS AND NEVER PLAYED. Measured on a Studio -> overlay -> Next
 * hop: `is-loading` was added and removed inside a single frame — the warm cache (`warm()` on
 * hover) resolves a hop in single-digit milliseconds — so the transition never got a frame to run
 * in. The DOM swapped 38ms after the click and the lowest opacity reached during the whole hop was
 * **1**. The mechanism was right; nothing ever held the faded-out state open for it.
 * The hold is in `project-modal.js` (see `fadeOutDone()`), because only JS can wait.
 *
 * ⛔ `is-hop` RATHER THAN RETUNING `.tw-pm__content` — deliberately. That rule also governs the
 * FIRST open, where the shell itself is fading in from nothing over 0.5s and the visitor is
 * already waiting on a fetch. Slowing it there would buy nothing and cost the thing Hares asked
 * for in 0.4.31, which is that a click looks acknowledged at once.
 *
 * 180ms out / 220ms in. Under ~120ms a fade reads as a flicker rather than a dissolve, so 180 is
 * the shortest that removes the snap; the in-leg is longer because it runs UNDER content that is
 * already there and blocks nothing, while every millisecond of the out-leg is time the visitor
 * waits. The pair replaces 0.3+0.3 = 600ms of dead time, which on a single-digit-millisecond hop
 * would have read as the overlay being slow. */
.tw-pm.is-hop .tw-pm__content {
	transition: opacity 220ms ease;
}

.tw-pm.is-hop.is-loading .tw-pm__content {
	transition: opacity 180ms ease;
}

/* A visitor who has asked for less motion gets the overlay without the fade.
 *
 * ⛔ THE TWO `is-hop` SELECTORS ARE LISTED HERE BECAUSE THEY OUTRANK THE BARE ONE. `.tw-pm.is-hop
 * .tw-pm__content` is (0,2,0) and `.tw-pm__content` is (0,1,0) — being later in the file does not
 * save it (`docs/CONVENTIONS.md` §6b: a `transition:` shorthand in a later block silently cancels a
 * reduced-motion rule). `project-modal.js` never adds `is-hop` under reduced motion, so today this
 * is unreachable; that is a fact about the JS, not about this stylesheet, and the day someone adds
 * the class for another reason the preference would break with nothing reporting it. */
@media ( prefers-reduced-motion: reduce ) {
	.tw-pm,
	.tw-pm.is-open,
	.tw-pm__content,
	.tw-pm.is-hop .tw-pm__content,
	.tw-pm.is-hop.is-loading .tw-pm__content,
	.tw-pm__loader {
		transition: none;
	}

	/* ⛔ The sweep is removed, not slowed. But the rule must still say "working" — a motionless
	 * hairline is indistinguishable from a decorative one — so the accent fills it statically. */
	.tw-pm__loader-rule::after {
		animation: none;
		width: 100%;
		opacity: 0.5;
	}
}
