/* front-page.css */
/* ==========================================================================
   PEPT — front page sections

   Only the Landing's own content. The site shell (stage, spine, bar, nav,
   footer, page primitives) lives in shell.css and wraps every page.

   Built entirely on the design system tokens in assets/hn/.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
.pf__hero {
	width: min(100% - 2 * var(--pf-gutter), var(--content-wide));
	margin: 0 auto;
	padding: var(--sp-xxl) 0 0;
	display: grid;
	gap: var(--sp-xl);
	align-items: center;
}
.pf__hero-copy { display: grid; gap: var(--sp-md); justify-items: start; }
.pf__hero-figure { display: grid; justify-items: center; padding: var(--sp-md) 0 var(--sp-lg); }
/* --------------------------------------------------------------------------
   The figure

   The neutral master, inlined so its layers belong to this stylesheet rather
   than sitting beside it in an <img>. The body takes the system's body tokens,
   the anatomy lines take its accent, and both follow the energy of whichever
   section is in view - so the figure is lit by the same thing that lights the
   vessels, and warms and cools as the page is scrolled.

   The asset is 2:3, so the width sets the height. The band below stays as the
   ground it stands on.
   -------------------------------------------------------------------------- */
.pf__hero-figure .pf__figure {
	width: 250px;
	height: auto;
	display: block;
}
/* The body ramp comes from the asset's own fallbacks. A near-black against the
   navy stage is what makes the figure read as a silhouette rather than as
   another blue object. */
.pf__hero-figure .pf__figure .body-base { fill: var(--hn-body, #080C14); }
.pf__hero-figure .pf__figure .tone { fill: var(--hn-body-mid, #0B1320); }

/* --------------------------------------------------------------------------
   The linework

   The weights are in the asset, not here, and that is deliberate. Setting a
   single stroke-width here would flatten every path to one value in a single
   declaration, and the figure would stop looking drawn and start looking
   plotted. The install writes a weight into each path instead and leaves a
   multiplier inside it - calc(0.83 * var(--hn-line-mult, 1)) - so the variation
   survives and a stressed section can still push the whole figure heavier with
   one declaration.

   The range is derived from each stroke's own length: long paths are structure,
   short ones are detail. That gives 52 distinct orange weights between 0.50 and
   1.10 units, and 31 blue between 0.45 and 0.85. At the hero's size - a
   440-unit crop at 300px, so 0.68 px per unit - the orange renders 0.34 to 0.75
   CSS px, which is 0.7 to 1.5 device px on a retina screen.

   Opacity and weight only, no filter. Three hundred stroked paths behind a
   drop-shadow is the entire figure re-rasterised every frame.
   -------------------------------------------------------------------------- */
/* By id, not by class. The two packs named their groups differently - the first
   shipped class="anatomy-lines", the relaxed pack class="anatomy_lines" - so
   every rule written against the class went dead the moment the pose was
   swapped, and the orange sat at a flat opacity with no energy response at all.
   The ids are the same in both. */
/* The glow rides the same registered number, so the shadow's alpha is a fifth
   of full through the trough and just over half at the peak - it arrives with
   the brightness rather than sitting under the figure the whole time.

   Measured, on a host with no GPU: 40fps without it, 34 with. The blur radius
   is not what costs - 2px, 3px and 6px all land within a frame of each other,
   because the price is the filtered raster, not the spread. A second stop takes
   it to 28 and is not worth it. One stop, 3px, is the tiny glow that was asked
   for and the cheapest way to draw it.

   The group, not the svg: filtering the whole figure costs 12fps against this
   6, because it pulls the 345 paths of the body and its tones into the raster
   as well. The anatomy is the only thing that needs to glow. */
.pf__hero-figure .pf__figure #anatomy_lines {
	animation: anatomy-pulse 4.4s ease-in-out infinite;
	/* The 1.35 is the overshoot, and min() is what makes the peak mean
	   something. Without it the pulse multiplied a base that is itself only .76
	   at the hero, so the brightest the figure ever got was .76 - a dip and a
	   recovery, never a peak. With it the crest reaches full and is clamped
	   there, while calmer sections still crest lower because --energy is still
	   underneath. Trough .20, peak 1.00. */
	opacity: min(1, calc((.46 + var(--energy, .55) * .54) * var(--anatomy-pulse) * 1.35));
	filter: drop-shadow(0 0 3px rgb(var(--hn-artery-rgb) / calc(var(--anatomy-pulse) * .55)));
}

/* --------------------------------------------------------------------------
   The figure breathes

   A registered number, because an unregistered custom property jumps between
   keyframes instead of interpolating. The pulse multiplies the energy response
   rather than replacing it, so the figure keeps answering the section it is in
   and breathes on top of that answer: the trough is a fifth of whatever the
   section has earned, the peak is all of it.

   One animated element, opacity only. The alternative - a pulse per path -
   would be 194 more animated elements on a figure that already runs 115, and
   the group is the same picture for none of the cost.
   -------------------------------------------------------------------------- */
@property --anatomy-pulse { syntax: "<number>"; inherits: true; initial-value: 1; }

@keyframes anatomy-pulse {
	0%, 100% { --anatomy-pulse: .2; }
	42%      { --anatomy-pulse: 1; }
}

/* Stress thickens everything together rather than levelling it */
[data-energy="stressed"] .pf__hero-figure .pf__figure,
[data-energy="overstimulated"] .pf__hero-figure .pf__figure { --hn-line-mult: 1.18; }
[data-energy="surge"] .pf__hero-figure .pf__figure { --hn-line-mult: 1.28; }
[data-energy="tired"] .pf__hero-figure .pf__figure,
[data-energy="lethargic"] .pf__hero-figure .pf__figure { --hn-line-mult: .84; }

/* --------------------------------------------------------------------------
   The nerves

   Blue is rest and orange is activation - the system's own rule - so the nerve
   layer is the counterweight: it comes up as the page cools and recedes as it
   heats, riding the bias axis the vessels already run on.

   On top of that the signal travels. Each energy path carries its own index
   from the install and takes a negative delay of thirty milliseconds times it,
   so a band of brightness runs the length of the nervous system once a cycle -
   down the throat, through the sternum, along the spine, into the legs. It is
   opacity only, so it composites; a dash travelling these paths would repaint
   every one of them every frame, and they are short segments where a dash
   reads as a flicker rather than as movement.
   -------------------------------------------------------------------------- */
@keyframes nerve-pulse {
	0%, 100% { opacity: .26; }
	44%      { opacity: 1; }
}

.pf__hero-figure .pf__figure #energy_lines {
	opacity: calc(.34 + (1 - var(--bias, 0)) * .30);
	transition: opacity var(--dur-ramp, 1200ms) var(--ease-flow, ease);
}
.pf__hero-figure .pf__figure .energy {
	animation: nerve-pulse 3.8s ease-in-out infinite;
	/* Subtracted from the path count rather than used raw: a positive delay
	   would leave the last paths still for three and a half seconds before
	   their first beat, and this way every delay is negative, so the cycle is
	   already running when the figure appears. The sign gives the direction -
	   the signal runs from the head down, which is the way a nerve signal
	   goes, where the raw index had it climbing from the feet. */
	animation-delay: calc((var(--i, 0) - 116) * 30ms);
}
/* Stopping a fade does not give you the figure back - it gives you the frame
   the fade happened to be on. Left alone, the anatomy pulse parks at 0.24, which
   is a nearly invisible figure rather than a still one, and it carries a glow
   for a peak that no longer comes. The base opacity and no filter is the honest
   still version: the same figure the page shows at rest anywhere else.

   This is the second time this exact shape has appeared here. The nerves had it
   first, parked on a wave trough at 0.42. Anything that animates opacity toward
   a trough will do this, so pin the property rather than only stopping the
   animation. */
@media (prefers-reduced-motion: reduce) {
	.pf__hero-figure .pf__figure .energy { animation: none; opacity: .8; }
	.pf__hero-figure .pf__figure #anatomy_lines {
		animation: none;
		--anatomy-pulse: 1;
		filter: none;
		opacity: calc(.46 + var(--energy, .55) * .54);
	}
}

.pf__band { width: 100%; max-width: 420px; margin-top: -28px; height: 80px; }

/* --------------------------------------------------------------------------
   Assurance marquee
   -------------------------------------------------------------------------- */
/* The ticker runs edge to edge and used to cut off mid-word at both ends, which
   reads as a fault rather than as a loop. Masked so it dissolves into the page
   over the outer 100px, and 50px on a phone.

   The mask has to sit here rather than on the track: the track is max-content
   and several times the width of the viewport, so a gradient against it would
   fade somewhere off screen. That means the two hairlines fade with the words -
   which is the better reading anyway, since a rule that stops short of the edge
   looks broken where one that dissolves looks deliberate. */
.pf__marquee {
	--marquee-fade: 100px;
	margin-top: var(--sp-xxl);
	padding: var(--sp-sm) 0;
	border-top: 1px solid var(--line);
	border-bottom: 1px solid var(--line);
	overflow: hidden;
	background: rgb(255 255 255 / .04);
	-webkit-mask-image: linear-gradient(90deg,
		transparent 0, #000 var(--marquee-fade),
		#000 calc(100% - var(--marquee-fade)), transparent 100%);
	mask-image: linear-gradient(90deg,
		transparent 0, #000 var(--marquee-fade),
		#000 calc(100% - var(--marquee-fade)), transparent 100%);
}
@media (max-width: 759px) { .pf__marquee { --marquee-fade: 50px; } }
.pf__marquee-track { display: flex; width: max-content; animation: pf-marquee 36s linear infinite; }
.pf__marquee-set { display: flex; }
.pf__marquee-set span {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-lg);
	padding: 0 var(--sp-lg);
	font: var(--type-body-sm-strong);
	color: var(--ink-charcoal);
	white-space: nowrap;
}
.pf__marquee-set i {
	width: 6px; height: 6px;
	border-radius: var(--r-full);
	background: var(--hn-artery-500);
	display: inline-block;
}
@keyframes pf-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* --------------------------------------------------------------------------
   Section furniture used only here
   -------------------------------------------------------------------------- */
.pf__head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: var(--sp-sm); margin-bottom: var(--sp-lg); }
.pf__note { margin: 0; font: var(--type-body-sm); color: var(--ink-muted); }

/* Three-up cards */
.pf__cards3 {
	display: grid;
	gap: var(--sp-md);
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}/* --------------------------------------------------------------------------
   Numbered steps
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   The three steps, as one frosted object

   The list was text and hairlines straight on the page, which is why it read
   as part of everything around it rather than as anything. It is a panel now,
   and the panel is frosted with the same --frost the header uses.

   The frost is doing a job rather than decorating. It sits over a pair of the
   world's own vessels, so the motif arrives as a soft living texture instead
   of as two drawn tubes competing with the words - and the cells keep moving
   behind it, so the surface is never quite still. Over the page's flat
   gradient the same filter would have been invisible.

   A white veil at 3.5% lifts the panel off the stage without turning it grey,
   and the hairline is inset so it reads as the panel's own edge.
   -------------------------------------------------------------------------- */
/* The light layer is inset negative so it fills the panel's corners rather than
   stopping short of them - but a third of it ended up outside the panel, where
   the frost cannot reach it, and it read as a hard-edged rectangle of light
   sitting behind the box with a visible corner at the top left.

   Clipped to the panel's own shape, radius included. The light is only ever
   meant to be seen through the glass; outside it there is nothing to see. */
.pf__steps-block {
	position: relative;
	isolation: isolate;
	margin-top: var(--sp-lg);
	overflow: hidden;
	border-radius: var(--r-lg);
}

.pf__steps-under {
	position: absolute;
	inset: -14% -8%;
	z-index: 0;
	pointer-events: none;
	/* Diffuse light only. Two wide, soft fields at different corners give the
	   panel an interior that is not one flat value - which is what makes a
	   translucent surface read as glass - without putting any shape behind it
	   for the eye to try and resolve. */
	background:
		radial-gradient(58% 78% at 14% 12%, rgb(var(--hn-artery-rgb) / .30), transparent 72%),
		radial-gradient(66% 84% at 88% 92%, rgb(var(--hn-vein-rgb) / .26), transparent 74%),
		radial-gradient(120% 90% at 50% 50%, rgb(23 63 134 / .30), transparent 80%);
}

.pf__steps-panel {
	position: relative;
	z-index: 1;
	border-radius: var(--r-lg);
	overflow: hidden;
	backdrop-filter: blur(16px) saturate(1.15);
	-webkit-backdrop-filter: blur(16px) saturate(1.15);
	/* The header backs its frost with the stage colour at 72%; a white veil at
	   3.5% held nothing, so the vessels behind showed straight through and the
	   copy ended up white on light blue. Same idea, slightly less opaque than
	   the header because this panel has more room to let the texture read. */
	/* Lighter at the top than the bottom, the way a sheet of glass catches the
	   light above it. Without the gradient it is a flat translucent rectangle;
	   with it the surface has a direction and reads as a material. */
	background:
		linear-gradient(180deg,
			rgb(255 255 255 / .055) 0%,
			rgb(255 255 255 / .015) 42%,
			rgb(4 14 34 / .30) 100%),
		rgb(4 14 34 / .52);
	box-shadow:
		inset 0 0 0 1px var(--line),
		inset 0 1px 0 rgb(255 255 255 / .10),
		0 30px 70px -40px rgb(0 0 0 / .85);
	padding: var(--sp-xs) var(--sp-xl);
}

.pf__steps { list-style: none; margin: 0; padding: 0; display: grid; }
.pf__steps li {
	display: grid;
	grid-template-columns: 40px 1fr;
	gap: var(--sp-md);
	padding: var(--sp-lg) 0;
	border-top: 1px solid var(--line);
}
.pf__steps li:last-child { border-bottom: 1px solid var(--line); }
/* The system's .hn-numeral renders a gradient-clipped, drop-shadowed object.
   That is illustration, and the craft floor bans gradient text: emphasis comes
   from size and weight. Solid, tabular, at display scale. */
.pf__stepnum {
	font: 500 40px/1 var(--font-display);
	font-variant-numeric: tabular-nums;
	color: var(--hn-artery-400);
	transition: opacity var(--dur-ramp, 600ms) var(--ease-arrive, ease-out);
}
/* Armed by the script, so with no script the numbers are simply lit. Only once
   something is watching does it make sense to hold them back. */
.pf__steps-block.is-watched:not(.is-visible) .pf__stepnum { opacity: .24; }
/* One after the other, because the section is about there being an order. The
   delay is what makes it read as a sequence rather than as a fade. */
.pf__steps-block.is-visible li:nth-child(1) .pf__stepnum { transition-delay: 140ms; }
.pf__steps-block.is-visible li:nth-child(2) .pf__stepnum { transition-delay: 420ms; }
.pf__steps-block.is-visible li:nth-child(3) .pf__stepnum { transition-delay: 700ms; }

@media (prefers-reduced-motion: reduce) {
	.pf__steps-block.is-watched:not(.is-visible) .pf__stepnum { opacity: 1; }
	.pf__steps-block.is-visible li .pf__stepnum { transition-delay: 0ms; }
}
.pf__steps h3 { margin: 0 0 var(--sp-xxs); font: var(--type-heading-sm); color: var(--ink); }
.pf__steps p { margin: 0; font: var(--type-body-sm); color: var(--ink-muted); }/* --------------------------------------------------------------------------
   Testimonials
   -------------------------------------------------------------------------- */
.pf__quotes { display: grid; gap: var(--sp-lg); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); margin-top: var(--sp-lg); }

/* Equal columns, unequal quotes. The cards stretch to the tallest, so without
   a column flow the attribution sits wherever its own quote happened to end
   and the two names land 28px apart across the pair. Pushing it to the floor
   is what makes them read as one row instead of two orphaned boxes. */
.pf__quotes figure {
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: var(--sp-lg);
}
/* No rule down the side. A coloured border-left on a quote is the category
   default, not a decision, and it was inheriting the browser's 4px blockquote
   edge in a colour belonging to nothing. The type carries the quote on its
   own, which is the whole reason to set it at heading size. */
.pf__quotes blockquote {
	margin: 0;
	padding: 0;
	border: 0;
	font: var(--type-heading-md);
	color: var(--ink);
	text-wrap: pretty;
}
.pf__quotes figcaption {
	margin-top: auto;
	display: flex;
	align-items: center;
	gap: var(--sp-sm);
	font: var(--type-body-sm);
	color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   FAQ
   -------------------------------------------------------------------------- */
/* The row is the surface, not just the question.
   The question was a white pill and the answer was loose text on the blue
   underneath it, starting at the pill's outer edge while the question was
   indented inside it - so the answer read as something floating between two
   buttons rather than as the rest of the one you opened. Each row is a single
   light panel now and the answer sits inside it, on the same left edge as the
   question, in the muted ink the system pairs with a light surface (4.74:1 on
   white, against the 2.9:1 the same grey managed on the blue).

   Still a pill while closed, because a closed row is one line and a pill is
   what this page uses for a thing you press. It becomes a rounded panel when
   open, because an answer inside a stadium wastes its own corners. */
.pf__faq { margin-top: var(--sp-lg); display: grid; gap: var(--sp-sm); }
.pf__faq-row {
	background: var(--invert-surface);
	color: var(--invert-ink);
	border-radius: var(--r-full);
	overflow: hidden;
}
.pf__faq-row:has(.pf__faq-q[aria-expanded="true"]) { border-radius: var(--r-lg); }

/* two classes deep because the broad .pf__main button rule is (0,1,1) and
   paints every button in main as a white pill */
.pf__faq .pf__faq-q {
	display: flex;
	width: 100%;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-md);
	padding: 14px 22px;
	border: 0;
	background: none;
	color: inherit;
	text-align: left;
	cursor: pointer;
	font: var(--type-heading-sm);
}
.pf__faq .pf__faq-q:hover { background: var(--c-surface-soft); }
.pf__faq .pf__faq-q:focus-visible { outline: 2px solid var(--hn-vein-600); outline-offset: -4px; }
.pf__faq .pf__glyph {
	font: 400 20px/1 var(--font-sans);
	color: var(--invert-ink-muted);
	flex: none;
	width: 24px;
	text-align: center;
	/* No rotation here. The script already swaps the character between + and
	   a minus when the row opens, and rotating on top of that turned the minus
	   into a bare diagonal. */
}

/* two classes deep for the same reason as the question: .pf__main p is
   (0,1,1) and sets every paragraph's colour, which on a now-white panel left
   the answer white on white at 1.00:1 */
.pf__faq .pf__faq-a {
	margin: 0;
	/* the same 22px as the question, so the answer starts under the words
	   rather than under the edge of the panel, and stops short of the glyph
	   column rather than running under it */
	padding: 0 22px 18px;
	font: var(--type-body-md);
	color: var(--invert-ink-muted);
	max-width: 68ch;
	text-wrap: pretty;
}
.pf__faq .pf__faq-a[hidden] { display: none; }/* A 36px button is a mouse target. On a phone these two are the page's primary
   action and they were the only controls on it under 44. Scoped to the hero so
   the system's own height is untouched everywhere else. */
@media (max-width: 759px) {
	.pf__cta .hn-btn { min-height: 48px; }
}

/* --------------------------------------------------------------------------
   ≥760px
   -------------------------------------------------------------------------- */
@media (min-width: 760px) {
	.pf__hero { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); padding-top: var(--sp-section); }
	.pf__hero-figure .pf__figure { width: 300px; }
}

@media (prefers-reduced-motion: reduce) {
	.pf__marquee-track { animation: none; }
	.pf__kit { transition: none; }
}


