/*
 * Concept portal (components/concept-portal/concept-portal.php).
 *
 * Layer 1 — panels: a normal, readable section (no JS, search engines,
 * screen readers).
 * Layer 2 — dialog: a full-screen native <dialog> opened by
 * concept-portal.js. Phases (data-gb-phase on the dialog):
 *   charge → the brand arrow "charges": frame + arrow draw, an orange energy
 *            dash races up the shaft, a short flash;
 *   reveal → the concept content rises in; when the concept has a scene
 *            (components/concept-portal/scenes/, Stage 3 revision 2) the
 *            arrow gives way to it in the same cell and the scene plays
 *            (concept-portal/scene*.css).
 *
 * Like the intro, every charge keyframe declares only its START frame: the end
 * frame is the element's normal resting style (fully drawn mark), so the
 * portal always ends readable even if a timer or animation is cut short.
 * Durations come from tokens per experience level; STATIC / reduced motion
 * resolve to 0 ms, i.e. no animation at all.
 */

/* ---------- Layer 1: panels ---------- */

.gb-portal {
	/* Dark scene: the light focus ring stays visible. */
	--gb-focus: var(--gb-focus-inverse);
}

.gb-portal__panels {
	padding-block: var(--gb-space-xl) var(--gb-space-l);
}

.gb-portal__heading {
	margin-block-end: var(--gb-space-m);
	font-family: var(--gb-font-display);
	font-size: var(--gb-step-3);
	color: var(--gb-scene-text);
}

.gb-portal__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
	gap: var(--gb-space-s);
}

.gb-portal__panel {
	display: grid;
	gap: var(--gb-space-2xs);
	align-content: start;
	padding: var(--gb-space-m);
	border: 1px solid var(--gb-root-color);
	border-radius: var(--gb-radius-l);
	/* Anchor jumps (no-JS) land with breathing room above the panel. */
	scroll-margin-block-start: var(--gb-space-xl);
}

.gb-portal__panel:target {
	border-color: var(--gb-primary);
}

.gb-portal__count {
	font-size: var(--gb-step--1);
	color: var(--gb-primary);
}

.gb-portal__title {
	font-family: var(--gb-font-display);
	font-size: var(--gb-step-2);
	color: var(--gb-scene-text);
}

.gb-portal__text {
	color: var(--gb-scene-text-muted);
}

/* Enhanced: the dialog replaces the panels section (concept-portal.js sets this). */
.gb-portal[data-gb-portal="ready"] .gb-portal__panels {
	display: none;
}

/* ---------- Layer 2: dialog ---------- */

.gb-portal-dialog {
	--gb-focus: var(--gb-focus-inverse);
	--gb-charge: var(--gb-duration-charge);
	/* Going in through the pressed word (revision 10): calm, about a second. */
	--gb-dive: 1100ms;

	position: fixed;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	max-inline-size: none;
	max-block-size: none;
	margin: 0;
	padding: 0;
	border: 0;
	overflow: auto;
	background-color: var(--gb-scene-bg);
	/* Static glow, centred (direction-neutral). */
	background-image: radial-gradient(50% 45% at 50% 38%, var(--gb-color-turquoise-a20), transparent 70%);
	color: var(--gb-scene-text);
}

.gb-portal-dialog::backdrop {
	background-color: var(--gb-scene-bg);
}

/*
 * The page behind must not scroll while the portal is open (it would also move
 * the scrubbed scene). Its scroll bar's room is kept, so the drawing behind
 * does not jump sideways while the portal opens over it — painted in the
 * portal's night, so no light strip shows there.
 */
html:has(.gb-portal-dialog[open]) {
	overflow: hidden;
	scrollbar-gutter: stable;
	background-color: var(--gb-scene-bg);
}

/* Per-level charge budget. Anything without a motion level (STATIC, no JS level) gets none. */
:root[data-gb-experience="OPTIMIZED"] .gb-portal-dialog {
	--gb-charge: var(--gb-duration-charge-optimized);
	--gb-dive: 900ms;
}

:root[data-gb-experience="LIGHT"] .gb-portal-dialog {
	--gb-charge: var(--gb-duration-charge-light);
	--gb-dive: 650ms;
}

:root:not([data-gb-experience="FULL"], [data-gb-experience="OPTIMIZED"], [data-gb-experience="LIGHT"]) .gb-portal-dialog {
	--gb-charge: 0ms;
	--gb-dive: 0ms;
}

@media (prefers-reduced-motion: reduce) {
	.gb-portal-dialog {
		--gb-dive: 0ms;
	}
}

/*
 * Going in (revision 10, concept-portal.js sets data-gb-dive): the portal's
 * night opens as a circle out of the pressed word (--gb-dive-x / -y) while
 * the drawing behind leans in (script) and the stage comes from far to near;
 * coming out is the same, backwards, into the word. The page shows around
 * the circle meanwhile (no backdrop).
 */
.gb-portal-dialog[data-gb-dive]::backdrop {
	background-color: transparent;
}

.gb-portal-dialog[data-gb-dive="in"] {
	animation: gb-dive-open var(--gb-dive) var(--gb-ease-in-out) both;
}

.gb-portal-dialog[data-gb-dive="in"] .gb-portal-dialog__stage {
	transform-origin: var(--gb-dive-x, 50%) var(--gb-dive-y, 50%);
	animation: gb-dive-near var(--gb-dive) var(--gb-ease-out) both;
}

.gb-portal-dialog[data-gb-dive="out"] {
	animation: gb-dive-close var(--gb-dive-out, 700ms) var(--gb-ease-in-out) both;
}

.gb-portal-dialog[data-gb-dive="out"] .gb-portal-dialog__stage {
	transform-origin: var(--gb-dive-x, 50%) var(--gb-dive-y, 50%);
	animation: gb-dive-far var(--gb-dive-out, 700ms) var(--gb-ease-in-out) both;
}

@keyframes gb-dive-open {
	from {
		clip-path: circle(1.4rem at var(--gb-dive-x, 50%) var(--gb-dive-y, 50%));
	}

	to {
		clip-path: circle(150vmax at var(--gb-dive-x, 50%) var(--gb-dive-y, 50%));
	}
}

@keyframes gb-dive-close {
	from {
		clip-path: circle(150vmax at var(--gb-dive-x, 50%) var(--gb-dive-y, 50%));
	}

	to {
		clip-path: circle(1.4rem at var(--gb-dive-x, 50%) var(--gb-dive-y, 50%));
		opacity: 0;
	}
}

@keyframes gb-dive-near {
	from {
		opacity: 0;
		scale: 0.3;
	}

	30% {
		opacity: 0;
	}
}

@keyframes gb-dive-far {
	to {
		opacity: 0;
		scale: 0.3;
	}
}

.gb-portal-dialog__stage {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--gb-space-l);
	min-block-size: 100%;
	padding-block: var(--gb-space-2xl);
	padding-inline: var(--gb-gutter);
	text-align: center;
}

.gb-portal-dialog__close {
	position: absolute;
	inset-block-start: var(--gb-space-m);
	inset-inline-end: var(--gb-gutter);
	display: grid;
	place-items: center;
	inline-size: 2.75rem;
	block-size: 2.75rem;
	padding: 0;
	border: 1px solid var(--gb-scene-text-muted);
	border-radius: 50%;
	background-color: transparent;
	color: var(--gb-scene-text);
	cursor: pointer;
}

.gb-portal-dialog__close:hover {
	border-color: var(--gb-scene-text);
}

.gb-portal-dialog__close svg {
	inline-size: 1.1rem;
	block-size: 1.1rem;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
}

/*
 * The visual cell: the charging mark and the concept's scene are stacked in
 * one grid cell, so the hand-over from one to the other never moves the
 * text below. The scene (440×200) sets the cell's size.
 */
.gb-portal-dialog__visual {
	display: grid;
	place-items: center;
	inline-size: min(100%, 34rem);
}

.gb-portal-dialog__visual > * {
	grid-area: 1 / 1;
}

.gb-portal-dialog__scene {
	inline-size: 100%;
}

.gb-portal-dialog__scene:empty {
	display: none;
}

/* The charging mark + its energy overlay share one square box. */
.gb-portal-dialog__charge {
	position: relative;
	inline-size: clamp(8rem, 16vw, 12rem);
	aspect-ratio: 1;
}

.gb-portal-dialog__charge > svg {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	overflow: visible;
}

/* Soft flash behind the mark at the end of the charge (gradient, no blur). */
.gb-portal-dialog__charge::before {
	content: "";
	position: absolute;
	inset: -35%;
	border-radius: 50%;
	background-image: radial-gradient(closest-side, var(--gb-color-turquoise-a35), transparent);
	opacity: 0;
}

.gb-portal-dialog__energy-path {
	fill: none;
	stroke: var(--gb-energy);
	/* Narrower than the 19.8 band, so the energy reads as travelling INSIDE the shaft. */
	stroke-width: 11px;
	stroke-linecap: round;
	/* A short dash on a pathLength="1" path: moving the offset slides it along the shaft. */
	stroke-dasharray: 0.16 1.2;
	stroke-dashoffset: 0.16;
	opacity: 0;
}

.gb-portal-dialog__content {
	display: grid;
	gap: var(--gb-space-xs);
	max-inline-size: 38rem;
}

/* Cloned panel, restyled for the cinematic view. */
.gb-portal-dialog .gb-portal__panel {
	padding: 0;
	border: 0;
	gap: var(--gb-space-xs);
}

.gb-portal-dialog .gb-portal__title {
	font-size: var(--gb-step-6);
}

.gb-portal-dialog .gb-portal__text {
	font-size: var(--gb-step-1);
}

.gb-portal-dialog__nav {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--gb-space-s);
}

.gb-portal-dialog__step {
	min-block-size: 2.75rem;
	padding-block: var(--gb-space-3xs);
	padding-inline: var(--gb-space-m);
	border: 1px solid var(--gb-root-color);
	border-radius: var(--gb-radius-pill);
	background-color: transparent;
	color: var(--gb-scene-text);
	font-family: var(--gb-font-sans);
	font-size: var(--gb-step-0);
	cursor: pointer;
}

.gb-portal-dialog__step:hover {
	border-color: var(--gb-primary);
}

/* ---------- Phase: charge ---------- */

.gb-portal-dialog[data-gb-phase="charge"] .gb-mark--charge .gb-draw {
	stroke-dasharray: 1;
}

/* Frame first… */
.gb-portal-dialog[data-gb-phase="charge"] .gb-mark--charge [data-gb-mark-part="body"] .gb-draw {
	animation: gb-charge-draw calc(var(--gb-charge) * 0.5) var(--gb-ease-out) both;
}

/* …while the arrow draws upward, then its non-stroke parts settle in. */
.gb-portal-dialog[data-gb-phase="charge"] .gb-mark--charge [data-gb-mark-part="arrow"] .gb-draw {
	animation: gb-charge-draw calc(var(--gb-charge) * 0.55) var(--gb-ease-in-out) calc(var(--gb-charge) * 0.1) both;
}

.gb-portal-dialog[data-gb-phase="charge"] .gb-mark--charge [data-gb-mark-part] > :not(.gb-draw) {
	animation: gb-charge-fade calc(var(--gb-charge) * 0.3) linear calc(var(--gb-charge) * 0.6) both;
}

/* Orange energy races up the shaft ahead of the arrowhead. */
.gb-portal-dialog[data-gb-phase="charge"] .gb-portal-dialog__energy-path {
	animation: gb-charge-energy calc(var(--gb-charge) * 0.65) var(--gb-ease-in-out) calc(var(--gb-charge) * 0.05) both;
}

/* Flash + a small "pop" of the finished mark. */
.gb-portal-dialog[data-gb-phase="charge"] .gb-portal-dialog__charge::before {
	animation: gb-charge-flash calc(var(--gb-charge) * 0.45) var(--gb-ease-out) calc(var(--gb-charge) * 0.6) both;
}

.gb-portal-dialog[data-gb-phase="charge"] .gb-mark--charge {
	animation: gb-charge-pop calc(var(--gb-charge) * 0.35) var(--gb-ease-out) calc(var(--gb-charge) * 0.65) both;
}

/* The mark charges larger, then settles to its place as the content arrives. */
.gb-portal-dialog[data-gb-phase="charge"] .gb-portal-dialog__charge {
	scale: 1.35;
}

/* Content (and the scene) wait for the charge. */
.gb-portal-dialog[data-gb-phase="charge"] :is(.gb-portal-dialog__content, .gb-portal-dialog__nav, .gb-portal-dialog__scene) {
	opacity: 0;
}

/* ---------- Phase: reveal ---------- */

.gb-portal-dialog[data-gb-phase="reveal"] .gb-portal-dialog__charge {
	animation: gb-charge-settle var(--gb-duration-slow) var(--gb-ease-out) both;
}

.gb-portal-dialog[data-gb-phase="reveal"] .gb-portal__panel > * {
	animation: gb-charge-rise var(--gb-duration-slow) var(--gb-ease-out) both;
}

.gb-portal-dialog[data-gb-phase="reveal"] .gb-portal__panel > :nth-child(2) {
	animation-delay: calc(var(--gb-duration-fast) * 0.6);
}

.gb-portal-dialog[data-gb-phase="reveal"] .gb-portal__panel > :nth-child(3) {
	animation-delay: calc(var(--gb-duration-fast) * 1.2);
}

.gb-portal-dialog[data-gb-phase="reveal"] .gb-portal-dialog__nav {
	animation: gb-charge-fade var(--gb-duration-slow) linear calc(var(--gb-duration-fast) * 1.5) both;
}

/* With a scene: the charged arrow fades out where the scene fades in. */
.gb-portal-dialog[data-gb-scene][data-gb-phase="reveal"] .gb-portal-dialog__charge {
	visibility: hidden;
	opacity: 0;
	animation: gb-charge-handover var(--gb-duration-slow) var(--gb-ease-out) both;
}

.gb-portal-dialog[data-gb-phase="reveal"] .gb-portal-dialog__scene {
	animation: gb-charge-fade var(--gb-duration-slow) linear both;
}

/* ---------- Keyframes (start frames only, except the transient ones) ---------- */

@keyframes gb-charge-draw {
	from {
		opacity: 0;
		stroke-dashoffset: 1;
	}

	20% {
		opacity: 1;
	}

	to {
		stroke-dashoffset: 0;
	}
}

@keyframes gb-charge-fade {
	from {
		opacity: 0;
	}
}

@keyframes gb-charge-settle {
	from {
		scale: 1.35;
	}
}

@keyframes gb-charge-handover {
	from {
		visibility: visible;
		opacity: 1;
		scale: 1.35;
	}
}

@keyframes gb-charge-rise {
	from {
		opacity: 0;
		translate: 0 1rem;
	}
}

/* Transient: visible only while travelling, invisible at rest. */
@keyframes gb-charge-energy {
	0% {
		opacity: 1;
		stroke-dashoffset: 0.16;
	}

	85% {
		opacity: 1;
	}

	100% {
		opacity: 0;
		stroke-dashoffset: -1.04;
	}
}

@keyframes gb-charge-flash {
	0% {
		opacity: 0;
		scale: 0.6;
	}

	35% {
		opacity: 1;
	}

	100% {
		opacity: 0;
		scale: 1.2;
	}
}

@keyframes gb-charge-pop {
	0% {
		scale: 1;
	}

	40% {
		scale: 1.06;
	}

	100% {
		scale: 1;
	}
}
