/* ==========================================================================
   Hero greeting.

   In the page, at the top of the hero — not an overlay. It animates once on
   load and then stays put as permanent content, so it has to read well as
   static text with the motion stripped away.
   ========================================================================== */

/* Full viewport height: the welcome owns the screen until the reader scrolls
   past it. Not fixed and not an overlay — it is the first section of the
   document, so it scrolls away naturally and stays in the page. */
/* Breaks out of the container's inset so the background spans the viewport,
   then re-applies the inset to its own content. This is what gives the fluid
   animation the full width of the section. */
.greet {
	margin-inline: calc(var(--content-inset) * -1) calc(var(--gutter) * -1);
	padding-inline: var(--content-inset) var(--gutter);
	min-height: 100vh;
	min-height: 100svh;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--s-4);
	position: relative;
	/* Above the background canvas, which Marvy gives z-index 1. */
	z-index: 2;
	max-width: none;
	margin-block-end: 0;
	scroll-margin-top: 0;
}

.greet[hidden] { display: none; }

.greet__line {
	margin: 0;
	max-width: none;
	font-family: var(--font-display);
	font-weight: 600;
	letter-spacing: var(--track-display);
	line-height: 1.15;
	color: var(--ink);
}

.greet__line--1 {
	font-size: clamp(1.75rem, 6vw, 4.5rem);
	margin-block-end: var(--s-4);
	text-wrap: balance;
}

.greet__line--2 {
	font-size: clamp(1rem, 2vw, 1.375rem);
	font-family: var(--font-data);
	font-weight: 400;
	letter-spacing: 0;
	color: var(--muted);
}

.greet__word { display: inline-block; }

/* Tabular figures while the glyphs are still noise, or the line width jumps
   on every animation frame. */
.greet__word,
.greet__line--2 span { font-variant-numeric: tabular-nums; }

/* The correction. The strike has to be visible or the joke does not land. */
.greet__strike { position: relative; color: var(--muted); }

.greet__strike::after {
	content: "";
	position: absolute;
	inset-inline: -0.06em;
	inset-block-start: 52%;
	height: 0.07em;
	background: var(--alert);
	transform: scaleX(1);
	transform-origin: left center;
}

/* Animated entrance only when JS drives it; static state is the default, so
   no-JS and reduced-motion visitors get finished text. */
.js .greet__strike::after { transform: scaleX(0); transition: transform 380ms var(--ease-out-back); }
.js .greet__strike.is-struck::after { transform: scaleX(1); }

.greet__fix { display: inline-block; color: var(--signal); }

.js .greet__fix {
	opacity: 0;
	transform: translateY(8px);
	transition: opacity 420ms var(--ease-out-back), transform 420ms var(--ease-out-back);
}
.js .greet__fix.is-in { opacity: 1; transform: none; }

/* While the boot overlay is up the greeting has not begun; keep line one from
   showing its finished state and then re-scrambling when it does. */
.js.is-booting .greet__line--1 { visibility: hidden; }

.js .greet__line--2 {
	opacity: 0;
	transform: translateY(8px);
	transition: opacity 520ms var(--ease-out-back), transform 520ms var(--ease-out-back);
}
.js .greet__line--2.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
	.greet__strike::after,
	.js .greet__strike::after { transform: scaleX(1) !important; transition: none !important; }
	.greet__fix,
	.js .greet__fix,
	.greet__line--2,
	/* While the boot overlay is up the greeting has not begun; keep line one from
   showing its finished state and then re-scrambling when it does. */
.js.is-booting .greet__line--1 { visibility: hidden; }

.js .greet__line--2 {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
}

/* Scroll cue, pinned to the bottom of the welcome screen. It is the only
   instruction on the page, so it stays quiet. */
/* The cue is absolutely positioned inside .greet, which now breaks out to the
   viewport edge — so inset-inline-start: 0 put it underneath the floating
   rail. It has to carry the same inset as the text it belongs to. */
.greet__scroll {
	position: absolute;
	inset-block-end: var(--s-7);
	inset-inline-start: var(--content-inset);
	margin: 0;
	color: var(--rule);
	text-transform: lowercase;
	pointer-events: none;
}

.js .greet__scroll::after {
	content: "";
	display: block;
	width: 1px;
	height: var(--s-6);
	margin-block-start: var(--s-2);
	background: linear-gradient(var(--rule), transparent);
}

@media (max-width: 47.99em) {
	.greet { min-height: 88svh; margin-block-end: var(--s-7); }
	.greet__scroll { inset-block-end: var(--s-6); }
}


/* ==========================================================================
   Greeting over the fluid animation.

   Marvy's fluid canvas is opaque: TRANSPARENT is false and it clears to
   clearColor(0,0,0,1) every frame. The section background is therefore never
   visible while the effect runs, in either theme — so the type has to be
   light, and light in both themes.

   Scoped to the data attribute Marvy writes server-side rather than to the
   section itself, so turning the effect off returns the greeting to the normal
   token colours with no other change.
   ========================================================================== */

[data-marvy_enable_fluid_animation="true"] .greet__line,
[data-marvy_enable_fluid_animation="true"] .greet__word {
	color: #FFFFFF;
}

[data-marvy_enable_fluid_animation="true"] .greet__line--2 {
	/* Not pure white: the supporting line should sit behind the greeting, and
	   full white on black at this size halates. */
	color: #C9CFCC;
}

[data-marvy_enable_fluid_animation="true"] .greet__strike {
	color: #9BA3A0;
}

[data-marvy_enable_fluid_animation="true"] .greet__strike::after {
	/* --alert is tuned for a light ground; it muddies against black. */
	background: #E0705C;
}

[data-marvy_enable_fluid_animation="true"] .greet__fix {
	/* The dark-theme signal, which is what reads on a black field. */
	color: #4FB295;
}

[data-marvy_enable_fluid_animation="true"] .greet__scroll {
	color: #6F7876;
}

[data-marvy_enable_fluid_animation="true"] .js .greet__scroll::after,
[data-marvy_enable_fluid_animation="true"] .greet__scroll::after {
	background: linear-gradient(#6F7876, transparent);
}

/* The fluid is decoration behind type. A soft scrim keeps the greeting legible
   when a bright plume passes under it, without dimming the effect overall. */
[data-marvy_enable_fluid_animation="true"] .greet::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background: radial-gradient(
		ellipse at 30% 45%,
		rgba(0, 0, 0, 0.55) 0%,
		rgba(0, 0, 0, 0.28) 38%,
		transparent 72%
	);
}

/* ==========================================================================
   Scroll cue — ASCII spinner plus a typed label.

   The spinner is the /-\| cycle every CLI tool uses while it waits, which is
   exactly what this element is doing: waiting for the reader to scroll.
   ========================================================================== */

.greet__scroll {
	display: flex;
	align-items: baseline;
	gap: 0.9ch;
}

/* Fixed width and tabular figures: the four glyphs have different advances,
   and without this the label jitters sideways on every frame. */
.greet__spin {
	display: inline-block;
	width: 1ch;
	flex: 0 0 1ch;
	text-align: center;
	color: var(--signal);
	font-variant-numeric: tabular-nums;
}

.greet__spin::before { content: "/"; }

/* The spinner only starts once the cue is on screen — a spinner running behind
   an invisible element is just wasted frames. */
.js .greet.is-ready .greet__spin::before,
html:not(.js) .greet__spin::before {
	animation: greet-spin 0.72s steps(1, end) infinite;
}

@keyframes greet-spin {
	0%   { content: "/"; }
	25%  { content: "-"; }
	50%  { content: "\\"; }
	75%  { content: "|"; }
	100% { content: "/"; }
}

/* Typed once on arrival.

   clip-path rather than an animated width: .label adds letter-spacing, so a
   width in `ch` is always short of the real rendered width and the last
   characters get cut. clip-path works off the element's actual box, so the
   reveal lands exactly on the final glyph in any language.

   --chars comes from PHP and is the real string length. */
/* Hidden until greeting.js signals the sequence is done. */
.js .greet__scroll { opacity: 0; }
.js .greet.is-ready .greet__scroll { opacity: 1; }

.js .greet__scroll-text {
	display: inline-block;
	white-space: nowrap;
	clip-path: inset(0 100% 0 0);
}

.js .greet.is-ready .greet__scroll-text {
	animation: greet-type 900ms steps(var(--chars, 6), end) both;
}

@keyframes greet-type {
	from { clip-path: inset(0 100% 0 0); }
	to   { clip-path: inset(0 0 0 0); }
}

/* Arrow ---------------------------------------------------------------------
   Appears after the label finishes typing, then bobs to say "keep going". */
.greet__arrow {
	display: inline-block;
	color: var(--signal);
	line-height: 1;
}

.js .greet__arrow { opacity: 0; }

.js .greet.is-ready .greet__arrow {
	animation:
		greet-arrow-in 320ms var(--ease-out-back) 950ms forwards,
		greet-arrow-bob 1.9s ease-in-out 1300ms infinite;
}

@keyframes greet-arrow-in {
	from { opacity: 0; transform: translateY(-4px); }
	to   { opacity: 1; transform: translateY(0); }
}

@keyframes greet-arrow-bob {
	0%, 100% { transform: translateY(0); }
	50%      { transform: translateY(3px); }
}

/* The vertical tick is redundant now the arrow carries the direction. */
.js .greet__scroll::after { display: none; }

/* A spinner that never stops is precisely what reduced-motion is for. The
   label is simply present, and the glyph settles on one frame. */
@media (prefers-reduced-motion: reduce) {
	.greet__spin::before { animation: none; content: "|"; }
	.js .greet__scroll,
	.js .greet.is-ready .greet__scroll { opacity: 1; }
	.js .greet__scroll-text,
	.js .greet.is-ready .greet__scroll-text { animation: none; clip-path: none; }
	.js .greet__arrow,
	.js .greet.is-ready .greet__arrow { animation: none; opacity: 1; transform: none; }
}
