/*
 * Mnememory, the website.
 *
 * One stylesheet for thirty eight pages, the blog, the error page and the app
 * shell. Everything is built from the section primitives in
 * class-mnems-sections.php, so a new page cannot invent a new look by accident.
 *
 *  1. tokens
 *  2. reset and base
 *  3. layout and bands
 *  4. type
 *  5. buttons and pills
 *  6. header, mobile menu, footer
 *  7. sections
 *  8. the product objects: hook card, curve, reader, ladder
 *  9. forms
 * 10. the app shell
 * 11. the loud sections: marquee, statement, sequence, bento, timeline, peek
 * 12. motion
 * 13. responsive
 *
 * Section 12 is the only place in this file that animates anything, and every
 * rule in it sits inside a reduced motion guard. That is not a style choice:
 * the accessibility page makes the claim in writing and the test suite checks
 * that no keyframe and no transition escapes the guard, then loads the site
 * with the preference set and checks that nothing on the page is moving.
 *
 * A handful of rules outside section 12 exist because of it and do not move:
 * the travelling focus ring's box, the progress line scaled to nothing, and
 * the look of a button that has been pressed and is waiting. Those are states
 * rather than animations, and a state has to be there for everybody.
 *
 * Section 1 holds the motion tokens. Nothing below it writes a duration, a
 * curve or a distance of its own, which is what makes retiming the whole site
 * one edit, and the test suite fails the build if an exception appears.
 */

/* ------------------------------------------------------------------ */
/* 1. Tokens                                                           */
/* ------------------------------------------------------------------ */

/*
 * The palette is bright on purpose and every value in it was measured, not
 * picked by eye. Two rules run through the whole thing:
 *
 *  1. A colour is either a FILL or a TEXT colour, never assumed to be both.
 *     Electric yellow on white is 1.42 to 1 and unreadable; the same yellow
 *     behind deep grape text is 12.5 to 1 and the loudest thing on the page.
 *     Every bright hue below therefore comes in a pair: the vivid one for
 *     fills, and a deepened one that is safe as small text on a light band.
 *  2. Any band that is not light overrides --text, --text-2 and --faint for
 *     itself, so a component dropped into it inherits readable copy without
 *     knowing which band it landed in.
 *
 * The numbers in the comments are contrast ratios against the band named,
 * and tests/site-test.py re-measures the important ones in a real browser on
 * every run so none of this can quietly rot.
 */

:root {
	/* Light bands. Tones alternate down a page for rhythm. */
	--paper: #FFFDF9;
	--sand: #F1EBFF;   /* pale violet */
	--cream: #FFF4D9;  /* pale butter */
	--mint: #E3FAF0;
	--sky: #E2F1FF;
	--blush: #FFE9F1;

	/* Dark bands. A deep grape rather than a black, so the brights sing. */
	--ink: #17093A;
	--ink-2: #221049;
	--ink-3: #2D1560;

	--text: #1A1033;   /* 17.8 on paper */
	--text-2: #57487D; /* 7.9 on paper, 6.9 on sand */
	--faint: #6E6091;  /* 5.5 on paper, 4.9 on sand, 5.2 on cream */

	--line: #E7DEFB;
	--line-2: #D3C4F5;
	--line-ink: #3A2470;

	/* The brand. --accent is the fill, --accent-deep is the same hue taken
	   down far enough to be small text anywhere light: 7.4 on paper. */
	--accent: #6C3BFF;
	--accent-deep: #5424E8;
	--accent-ink: #FFFFFF;

	/* The brights. -hi is the fill, the bare name is the text-safe version. */
	--pop: #FFD23F;       /* fill only: 1.4 on paper, 12.5 behind ink text */
	--pop-2: #FF3FA4;     /* fill only */
	--good-hi: #00D68F;
	--bad-hi: #FF4D3D;
	--cool-hi: #2AA7FF;
	--violet-hi: #9B6BFF;

	--good: #0E7A52;   /* 5.3 on paper */
	--warn: #9A5B00;   /* 5.4 on paper */
	--bad: #C42A18;    /* 5.6 on paper */
	--cool: #0B5FBF;   /* 6.1 on paper */
	--violet: #6A2FD6; /* 6.9 on paper */

	--r: 16px;
	--r-sm: 10px;
	--r-lg: 24px;

	--wrap: 1180px;
	--gut: 28px;

	--font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
	--display: "Fraunces", ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;

	--shadow: 0 1px 2px rgba(35, 16, 73, .05), 0 8px 28px rgba(35, 16, 73, .07);
	--shadow-lg: 0 2px 6px rgba(35, 16, 73, .07), 0 26px 60px rgba(35, 16, 73, .16);
	--shadow-pop: 6px 6px 0 var(--ink);

	/* ---- Motion ---------------------------------------------------- */

	/*
	 * Every duration, curve and distance the site moves by is named here and
	 * nowhere else. Nothing further down the file writes a number: a rule asks
	 * for var(--mo-enter) or var(--mo-rise), so the whole site can be retimed
	 * or re-tuned from this block alone. tests/site-test.py refuses a literal
	 * duration or a literal cubic-bezier anywhere else in the file, which is
	 * what stops that promise rotting one convenient exception at a time.
	 *
	 * The names say what the value is for rather than how long it is. A
	 * hover is a hover whether it takes 180ms or 220ms, and naming it
	 * --mo-200 would mean renaming every use of it the first time somebody
	 * decided it was slightly too quick.
	 */

	/* How long. Arrivals are slower than departures, always: a thing coming
	   towards you is worth watching and a thing leaving is in the way. */
	--mo-press: 90ms;      /* the moment a finger or a cursor lands */
	--mo-hover: 200ms;     /* a pointer arriving on something, or leaving it */
	--mo-exit: 170ms;      /* anything on its way out */
	--mo-enter: 520ms;     /* anything arriving on screen */
	--mo-lead: 640ms;      /* the heading that leads a band, a beat longer */
	--mo-cross: 420ms;     /* one panel handing over to the next */
	--mo-sweep: 620ms;     /* the band of light crossing a button */
	--mo-draw: 1500ms;     /* a line drawing itself */
	--mo-count: 1100ms;    /* a number counting up to its real value */
	--mo-page: 190ms;      /* this page handing over to the next route */
	--mo-spin: 800ms;      /* one turn of a pending spinner */
	--mo-ambient: 19s;     /* the slowest drift of the light behind a hero */
	--mo-ticker: 46s;      /* one full pass of the word ticker */

	/* The gap between one child of a group arriving and the next, and the
	   number of steps after which the gap stops growing. Without the cap a
	   twenty row table would still be arriving eight seconds later. */
	--mo-stagger: 58ms;
	--mo-stagger-tight: 34ms;
	--mo-stagger-cap: 9;

	/*
	 * The curves. Four, each with a job, rather than one all-purpose ease.
	 *
	 *  enter  leaves fast and lands slow, so a thing arriving decelerates
	 *         into place the way a physical object does.
	 *  exit   the mirror of it: slow to leave is hesitation, so this one
	 *         accelerates away and is over before you look for it.
	 *  press  overshoots slightly and settles. Only for something a person
	 *         pressed, because an overshoot nobody asked for reads as a bug.
	 *  drift  symmetrical and gentle, for the ambient things that never stop.
	 */
	--mo-ease-enter: cubic-bezier(.22, 1, .36, 1);
	--mo-ease-exit: cubic-bezier(.5, 0, .9, .4);
	--mo-ease-press: cubic-bezier(.34, 1.42, .64, 1);
	--mo-ease-drift: cubic-bezier(.4, 0, .2, 1);

	/* How far. Small numbers on purpose: a reveal is a hint that something
	   arrived, not a journey across the screen. */
	--mo-rise: 26px;       /* the standard arrival, from below */
	--mo-rise-sm: 13px;    /* small items, and anything in a long list */
	--mo-slide: 28px;      /* a column arriving from the side */
	--mo-lift: -5px;       /* a card under the pointer */
	--mo-lift-sm: -2px;    /* a button under the pointer, which is smaller */
	--mo-nudge: 5px;       /* an arrow leaning towards where it goes */
	--mo-sink: 1px;        /* a button taking the press */
	--mo-scale-in: .965;   /* an arrival that grows rather than rises */
	--mo-scale-press: .975;
	--mo-swell: 1.015;     /* the swell of something worth pressing */
	--mo-turn: 10deg;      /* the tilt of a card or a mark landing */

	/*
	 * How early a reveal fires, and how far back it catches up.
	 *
	 * The first is negative, so a band has crossed a little way into the window
	 * before it starts rather than arriving on the strength of one pixel. The
	 * second is the top edge of the same watched area, pushed a long way up the
	 * page: anything a person has already scrolled past has, by definition,
	 * arrived. Without it, a jump to the middle of a page, a deep link or a
	 * fast flick leaves every band above the landing point sitting at zero
	 * opacity, waiting for an intersection that has already happened.
	 */
	--mo-preroll: -5%;
	--mo-catchup: 250%;
}

/* ------------------------------------------------------------------ */
/* 2. Reset and base                                                   */
/* ------------------------------------------------------------------ */

body.mnems {
	margin: 0;
	background: var(--paper);
	color: var(--text);
	font-family: var(--font);
	font-size: 17px;
	line-height: 1.62;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	overflow-x: hidden;
}

.mnems *, .mnems *::before, .mnems *::after { box-sizing: border-box; }

.mnems img, .mnems svg { max-width: 100%; }
.mnems img { height: auto; display: block; }

/*
 * Written inside :where() on purpose, so it carries no specificity at all.
 *
 * The obvious version, .mnems a { color: inherit }, weighs more than a single
 * class, which means every component that colours its own link silently loses
 * and a primary button renders dark text on a dark pill. This is the second
 * time that exact bug has appeared in this codebase, so the reset is now
 * written at zero weight and the button colours are measured on every test run.
 */
:where(.mnems a) { color: inherit; }
.mnems a { text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); text-underline-offset: 3px; }
.mnems a:is(:hover, :focus-visible) { text-decoration-color: currentColor; }

.mnems :focus-visible {
	outline: 3px solid var(--accent-deep);
	outline-offset: 3px;
	border-radius: 4px;
}

.s-sr {
	position: absolute;
	width: 1px; height: 1px;
	padding: 0; margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

.s-skip {
	position: absolute;
	left: -9999px;
	top: 0;
	background: var(--ink);
	color: var(--paper);
	padding: 12px 18px;
	z-index: 200;
	border-radius: 0 0 var(--r-sm) 0;
}
.s-skip:focus { left: 0; }

/* ------------------------------------------------------------------ */
/* 3. Layout and bands                                                 */
/* ------------------------------------------------------------------ */

.s-wrap {
	width: 100%;
	max-width: var(--wrap);
	margin: 0 auto;
	padding: 0 var(--gut);
}

.s-wrap-narrow { max-width: 760px; }

.s-sec { padding: 88px 0; position: relative; }
.s-sec-tight { padding: 56px 0; }

.s-tone-paper { background: var(--paper); }
.s-tone-sand { background: var(--sand); }
.s-tone-cream { background: var(--cream); }
.s-tone-mint { background: var(--mint); }
.s-tone-sky { background: var(--sky); }
.s-tone-blush { background: var(--blush); }

/* Every light tinted band gets white cards, so a card still reads as a card
   sitting on top of the band rather than a slightly different rectangle. */
.s-tone-sand, .s-tone-cream, .s-tone-mint, .s-tone-sky, .s-tone-blush { --paper: #FFFFFF; }

.s-tone-ink {
	background: var(--ink);
	color: #F7F3FF;
	--text: #F7F3FF;
	--text-2: #C9BCEC;   /* 8.8 on ink */
	--faint: #A294CC;    /* 6.7 on ink */
	--line: var(--line-ink);
	--line-2: #4B2F8C;
	--accent-deep: var(--violet-hi);
	--paper: var(--ink-2);
	--sand: var(--ink-3);
}

/*
 * The solid brand band.
 *
 * Two things here are deliberate and neither is obvious. First, the gradient
 * is written in literal hex rather than var(--accent), because this rule also
 * redefines --accent for everything inside the band and a custom property
 * redefined in a rule applies to that same element, so the gradient would eat
 * its own replacement. Second, the lightest stop is a shade below the brand
 * violet: on #6C3BFF the muted copy measures 4.19 to 1 and fails, and on
 * #6231F0 it measures 4.83 and passes. That is the whole reason for the
 * darker stop, and it is why this band is not simply --accent.
 */
.s-tone-violet {
	/*
	 * background-color is the lightest stop and background-image is the
	 * gradient over it. Two reasons, and the second is the one that matters.
	 * A browser that will not paint the gradient still gets a band rather
	 * than white. And a gradient computes as a transparent background-color,
	 * so a contrast check walking up for a background finds the page behind
	 * it and measures white on white; naming the worst stop here means the
	 * test measures the hardest case rather than a fiction.
	 */
	background-color: #6231F0;
	background-image: linear-gradient(150deg, #6231F0 0%, #4A1BD6 55%, #33109E 100%);
	color: #FFFFFF;
	--text: #FFFFFF;
	--text-2: #EDE5FF;   /* 5.4 on the lightest stop */
	--faint: #E3D8FF;    /* 4.8 on the lightest stop */
	--line: rgba(255, 255, 255, .22);
	--line-2: rgba(255, 255, 255, .4);
	--accent: var(--pop);
	/*
	 * --accent-ink has to move with --accent or the primary button on this
	 * band is white text on yellow, which measures 1.4 to 1. Caught by the
	 * button contrast check rather than by looking at it, which is exactly
	 * why that check exists.
	 */
	--accent-ink: #1A1033;
	--accent-deep: #FFFFFF;
	--paper: rgba(255, 255, 255, .1);
	--sand: rgba(255, 255, 255, .16);
}

/* The loud ones. Both are light enough that the copy stays dark grape. */
.s-tone-gold {
	/* The darkest stop, because the copy on this band is dark. */
	background-color: #FFB300;
	background-image: linear-gradient(150deg, #FFE27A 0%, var(--pop) 48%, #FFB300 100%);
	--text: #1A1033;
	--text-2: #2B1447;
	--faint: #33184A;
	--line: rgba(26, 16, 51, .2);
	--line-2: rgba(26, 16, 51, .34);
	--accent-deep: #4A1A9E;
	--paper: rgba(255, 255, 255, .55);
}

.s-tone-punch {
	background-color: #FF4D3D;
	background-image: linear-gradient(150deg, #FF7A5C 0%, #FF4D3D 55%, #FF5FA0 100%);
	--text: #1A1033;
	--text-2: #2B1447;
	--faint: #33184A;
	--line: rgba(26, 16, 51, .22);
	--line-2: rgba(26, 16, 51, .36);
	--accent-deep: #2E0B6B;
	--paper: rgba(255, 255, 255, .58);
}

.s-tone-gold, .s-tone-punch { color: #1A1033; }

.s-center { text-align: center; }
.s-center .s-row { justify-content: center; }

/* A soft rule between two bands of the same tone. */
.s-tone-paper + .s-tone-paper,
.s-tone-sand + .s-tone-sand { border-top: 1px solid var(--line); }

/* ------------------------------------------------------------------ */
/* 4. Type                                                             */
/* ------------------------------------------------------------------ */

.mnems h1, .mnems h2, .mnems h3, .mnems h4 {
	font-family: var(--display);
	font-weight: 600;
	letter-spacing: -0.018em;
	line-height: 1.12;
	margin: 0 0 14px;
	text-wrap: balance;
}

.s-h1 { font-size: clamp(38px, 5.4vw, 62px); letter-spacing: -0.028em; }
.s-h2 { font-size: clamp(28px, 3.4vw, 42px); }
.mnems h3 { font-size: 21px; letter-spacing: -0.012em; }

.s-eyebrow {
	font-family: var(--font);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .16em;
	text-transform: uppercase;
	color: var(--accent-deep);
	margin: 0 0 14px;
}

.s-lede {
	font-size: clamp(18px, 1.5vw, 21px);
	line-height: 1.58;
	color: var(--text-2);
	margin: 0 0 8px;
	max-width: 62ch;
	text-wrap: pretty;
}

.s-center .s-lede { margin-left: auto; margin-right: auto; }

.mnems p { margin: 0 0 16px; text-wrap: pretty; }
.s-muted { color: var(--faint); font-size: 15px; }
/* A link inside muted copy still has to look like a link. The zero weight
   colour reset up top hands it the muted grey, which is legible and gives no
   hint at all that it is clickable. */
.s-muted a, .s-note a, .s-prose a, .s-faq-a a { color: var(--accent-deep); font-weight: 600; }
.s-note { color: var(--faint); font-size: 15px; margin-top: 14px; }

.s-sechead { max-width: 70ch; margin-bottom: 40px; }
.s-sechead.s-center { margin-left: auto; margin-right: auto; }

/* ------------------------------------------------------------------ */
/* 5. Buttons and pills                                                */
/* ------------------------------------------------------------------ */

.s-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 9px;
	min-height: 50px;
	padding: 12px 24px;
	border-radius: 999px;
	border: 1.5px solid transparent;
	font: inherit;
	font-size: 16px;
	font-weight: 600;
	letter-spacing: -0.005em;
	text-decoration: none;
	cursor: pointer;
	white-space: nowrap;
}

.s-btn {
	position: relative;
	overflow: hidden;
	isolation: isolate;
}

/*
 * The sweep. A soft band of light crosses the button on hover, drawn with a
 * pseudo element rather than a background-position trick so it costs one
 * composited transform and nothing else. It is decorative, so it lives inside
 * the reduced motion guard at the bottom of this file and simply never appears
 * for anyone who has asked for less movement.
 */
.s-btn::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, .38) 50%, transparent 80%);
	transform: translateX(-110%);
	opacity: 0;
}

.s-btn-pri {
	background: var(--accent);
	color: var(--accent-ink);
	border-color: var(--accent);
	box-shadow: 0 3px 0 color-mix(in srgb, var(--accent) 55%, #000);
}
.s-btn-pri:is(:hover, :focus-visible) { filter: saturate(1.15) brightness(1.06); }

/* On a dark or a violet band the primary flips to the yellow, which measures
   12.5 to 1 against the grape text it carries and is the single loudest
   object we own. --accent is already redefined to the yellow inside the
   violet tone, so that band needs no rule of its own. */
.s-tone-ink .s-btn-pri,
.s-cta .s-btn-pri {
	background: var(--pop);
	color: #1A1033;
	border-color: var(--pop);
	box-shadow: 0 3px 0 #C99A00;
}

.s-tone-gold .s-btn-pri,
.s-tone-punch .s-btn-pri {
	background: var(--ink);
	color: #FFFFFF;
	border-color: var(--ink);
	box-shadow: 0 3px 0 rgba(0, 0, 0, .35);
}

.s-btn-sec {
	background: transparent;
	color: var(--text);
	border-color: var(--line-2);
}
.s-btn-sec:is(:hover, :focus-visible) { border-color: var(--accent); color: var(--accent-deep); background: color-mix(in srgb, var(--accent) 8%, transparent); }

.s-btn-ghost {
	background: transparent;
	color: inherit;
	border-color: color-mix(in srgb, currentColor 32%, transparent);
}
.s-btn-ghost:is(:hover, :focus-visible) { border-color: currentColor; }

.s-btn-quiet { background: transparent; border-color: transparent; padding: 8px 10px; min-height: 40px; }

.s-btn-sm { min-height: 42px; padding: 9px 18px; font-size: 15px; }
.s-btn-wide { width: 100%; }

/*
 * The pending state, which is deliberately not inside the motion guard.
 *
 * A form that has been sent has to look sent whatever a person's motion
 * preference is, or the only feedback for pressing the button is that nothing
 * happened. So the static half lives here: the label dims, the ring appears,
 * the pointer changes. Only the turning of the ring is movement, and that part
 * is in section 12 with everything else that moves.
 */
/*
 * The label is deliberately not dimmed.
 *
 * The obvious version of this rule set color to a mix of currentColor, which
 * reads as the button's own colour and is not: currentColor inside a color
 * declaration is the colour this element inherited, so a primary button went
 * from white on violet to dark grape on violet the moment it was pressed. The
 * spinner, the pressed position and the word beside the button are enough
 * feedback, and none of them can touch a contrast ratio.
 */
.s-btn.is-busy { cursor: progress; }
.s-btn.is-busy::before {
	content: "";
	width: 17px;
	height: 17px;
	flex: none;
	border-radius: 50%;
	border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
	border-top-color: currentColor;
}
.s-f-sending {
	font-size: 14.5px;
	font-weight: 600;
	color: var(--accent-deep);
}

@media (prefers-reduced-motion: no-preference) {
	/*
	 * A button is the thing a person presses, so it is the one place on the
	 * site that uses all three curves: the press curve on the way back up so
	 * it settles rather than stops, the exit curve going down so the press is
	 * felt immediately, and the enter curve for everything else.
	 */
	.s-btn {
		transition: transform var(--mo-hover) var(--mo-ease-press),
			box-shadow var(--mo-hover) var(--mo-ease-enter),
			filter var(--mo-hover) var(--mo-ease-enter),
			background-color var(--mo-hover) var(--mo-ease-enter),
			border-color var(--mo-hover) var(--mo-ease-enter);
	}
	.s-btn:hover, .s-btn:focus-visible { transform: translateY(var(--mo-lift-sm)) scale(var(--mo-swell)); }
	.s-btn:active {
		transform: translateY(var(--mo-sink)) scale(var(--mo-scale-press));
		transition-duration: var(--mo-press);
		transition-timing-function: var(--mo-ease-exit);
	}
	.s-btn-pri:hover, .s-btn-pri:focus-visible { box-shadow: 0 5px 0 color-mix(in srgb, var(--accent) 55%, #000); }
	.s-btn-pri:active { box-shadow: 0 1px 0 color-mix(in srgb, var(--accent) 55%, #000); }
	.s-tone-ink .s-btn-pri:hover, .s-cta .s-btn-pri:hover,
	.s-tone-ink .s-btn-pri:focus-visible, .s-cta .s-btn-pri:focus-visible { box-shadow: 0 5px 0 #C99A00; }
	.s-tone-ink .s-btn-pri:active, .s-cta .s-btn-pri:active { box-shadow: 0 1px 0 #C99A00; }
	.s-btn::after { transition: transform var(--mo-sweep) var(--mo-ease-enter), opacity var(--mo-hover) linear; }
	.s-btn:hover::after, .s-btn:focus-visible::after { transform: translateX(110%); opacity: 1; }

	/* A pressed button that is now waiting stays pressed. Lifting back up
	   while the request is in flight says the press did not take. */
	.s-btn.is-busy, .s-btn.is-busy:hover, .s-btn.is-busy:focus-visible {
		transform: translateY(var(--mo-sink)) scale(var(--mo-scale-press));
	}
	.s-btn.is-busy::after { opacity: 0; }
}

.s-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin: 26px 0 0; }

.s-pill {
	display: inline-block;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
	padding: 4px 11px;
	border-radius: 999px;
	background: color-mix(in srgb, var(--accent) 20%, transparent);
	color: var(--accent-deep);
	margin-bottom: 12px;
}

.s-ico { flex: none; }

.s-ticks { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 12px; }
.s-ticks li { display: flex; gap: 11px; align-items: flex-start; line-height: 1.5; }
.s-ticks li .s-ico { color: var(--accent-deep); margin-top: 4px; }
.s-ticks-sm { gap: 9px; font-size: 15px; }
.s-ticks-no li .s-ico { color: var(--faint); }
.s-ticks-no li span:last-child { color: var(--faint); }

.s-more {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	margin-top: auto;
	padding-top: 14px;
	font-weight: 600;
	font-size: 15px;
	color: var(--accent-deep);
}
.s-more::after { content: "\2192"; }

/* ------------------------------------------------------------------ */
/* 6. Header, mobile menu, footer                                      */
/* ------------------------------------------------------------------ */

.s-head {
	position: sticky;
	top: 0;
	z-index: 120;
	background: color-mix(in srgb, var(--paper) 88%, transparent);
	backdrop-filter: saturate(150%) blur(12px);
	-webkit-backdrop-filter: saturate(150%) blur(12px);
	border-bottom: 1px solid var(--line);
}

/*
 * The stuck state, deliberately outside the motion guard.
 *
 * Whether the page has been scrolled is information, not decoration: the
 * shadow is what separates the bar from the band sliding underneath it, and
 * somebody who asked for less movement still needs to be able to tell the two
 * apart. Only the easing of it belongs in section 12.
 */
.s-head.is-stuck {
	background: color-mix(in srgb, var(--paper) 96%, transparent);
	box-shadow: 0 6px 26px rgba(35, 16, 73, .1);
}

.s-head-in {
	display: flex;
	align-items: center;
	gap: 22px;
	min-height: 74px;
}

.s-brand { text-decoration: none; flex: none; }

.s-logo { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); }
.s-logo b { font-family: var(--display); font-size: 21px; font-weight: 600; letter-spacing: -0.02em; }
.s-tone-ink .s-logo, .s-foot .s-logo { color: currentColor; }
.s-logo svg { flex: none; }

.s-nav { display: flex; align-items: center; gap: 4px; margin-right: auto; }

.s-nav > a, .s-navbtn {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 9px 13px;
	border-radius: 999px;
	font: inherit;
	font-size: 15px;
	font-weight: 550;
	color: var(--text-2);
	background: none;
	border: 0;
	cursor: pointer;
	text-decoration: none;
	white-space: nowrap;
}

.s-nav > a:is(:hover, :focus-visible), .s-navbtn:is(:hover, :focus-visible) { color: var(--text); background: color-mix(in srgb, var(--line) 45%, transparent); }
.s-nav > a.on { color: var(--text); font-weight: 650; }

.s-caret {
	width: 7px; height: 7px;
	border-right: 1.6px solid currentColor;
	border-bottom: 1.6px solid currentColor;
	transform: rotate(45deg) translate(-2px, -2px);
	opacity: .6;
}
/* Which way the caret points says whether the menu is open, so it points the
   right way for everybody. Only the turning of it is in section 12. */
.s-navbtn[aria-expanded="true"] .s-caret { transform: rotate(225deg) translate(-2px, -2px); }

.s-drop { position: relative; }

.s-panel {
	position: absolute;
	top: calc(100% + 10px);
	left: -8px;
	width: min(460px, 88vw);
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	box-shadow: var(--shadow-lg);
	padding: 10px;
	z-index: 130;
}

.s-panel-in { display: grid; gap: 2px; }

.s-panel a {
	display: block;
	padding: 11px 13px;
	border-radius: var(--r-sm);
	text-decoration: none;
}
.s-panel a:is(:hover, :focus-visible) { background: var(--sand); }
.s-panel a .t { display: block; font-weight: 650; font-size: 16px; }
.s-panel a .b { display: block; font-size: 14px; color: var(--faint); line-height: 1.45; margin-top: 2px; }

.s-head-cta { display: flex; align-items: center; gap: 14px; flex: none; }
.s-quiet { font-size: 15px; font-weight: 550; color: var(--text-2); text-decoration: none; white-space: nowrap; }
.s-quiet:is(:hover, :focus-visible) { color: var(--text); }

.s-burger {
	display: none;
	width: 44px; height: 44px;
	border: 1px solid var(--line-2);
	background: transparent;
	border-radius: 12px;
	cursor: pointer;
	padding: 0;
	position: relative;
}
.s-burger span {
	position: absolute;
	left: 12px; right: 12px;
	height: 1.8px;
	background: var(--text);
	border-radius: 2px;
}
.s-burger span:nth-child(1) { top: 15px; }
.s-burger span:nth-child(2) { top: 21px; }
.s-burger span:nth-child(3) { top: 27px; }
.s-burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.s-burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.s-burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.s-mobile {
	position: fixed;
	inset: 74px 0 0;
	background: var(--paper);
	z-index: 119;
	overflow-y: auto;
	padding: 22px 0 40px;
	border-top: 1px solid var(--line);
}
.s-mobile a {
	display: block;
	padding: 13px 0;
	font-size: 18px;
	font-weight: 600;
	text-decoration: none;
	border-bottom: 1px solid var(--line);
}
.s-mobile a .b { display: none; }
.s-mobile-h {
	font-size: 12px;
	text-transform: uppercase;
	letter-spacing: .14em;
	font-weight: 700;
	color: var(--accent-deep);
	margin: 26px 0 4px;
}
.s-mobile-cta { display: grid; gap: 10px; margin-top: 28px; }

.s-foot {
	background: var(--ink);
	color: #F0EAFF;
	padding: 76px 0 34px;
	--text-2: #C9BCEC;
	--faint: #A294CC;
	--line: #33206B;
}

.s-foot-top {
	display: grid;
	grid-template-columns: 1.5fr repeat(4, 1fr);
	gap: 40px;
	padding-bottom: 44px;
	border-bottom: 1px solid var(--line);
}

.s-foot-brand p { color: var(--text-2); font-size: 15px; margin: 14px 0 0; }
.s-foot-brand .s-foot-blurb { font-size: 14px; color: var(--faint); max-width: 34ch; }
.s-foot-brand .s-btn { margin-top: 18px; }

.s-foot-col h3 { font-family: var(--font); font-size: 12px; text-transform: uppercase; letter-spacing: .14em; color: var(--faint); margin: 0 0 14px; }
.s-foot-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.s-foot-col a { font-size: 15px; color: #DFD6F5; text-decoration: none; }
.s-foot-col a:is(:hover, :focus-visible) { color: var(--pop); }

.s-foot-langs {
	display: flex;
	flex-wrap: wrap;
	gap: 8px 18px;
	align-items: center;
	padding: 26px 0;
	border-bottom: 1px solid var(--line);
	font-size: 14px;
}
.s-foot-langs span { color: var(--faint); text-transform: uppercase; letter-spacing: .12em; font-size: 11px; font-weight: 700; }
.s-foot-langs a { color: #D6CBF2; text-decoration: none; }
.s-foot-langs a:is(:hover, :focus-visible) { color: var(--pop); }

.s-foot-bar {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 12px;
	padding-top: 24px;
	font-size: 14px;
	color: var(--faint);
}
.s-foot-bar p { margin: 0; }
.s-foot-social { display: flex; gap: 16px; }
.s-foot-social a { text-decoration: none; }
.s-foot-social a:is(:hover, :focus-visible) { color: var(--pop); }

/* ------------------------------------------------------------------ */
/* 7. Sections                                                         */
/* ------------------------------------------------------------------ */

.s-hero { padding: 84px 0 76px; position: relative; overflow: hidden; }

/* The static glow. Every hero gets this; the ones that also carry an aurora
   get three drifting blobs on top of it. Keeping the static one means a hero
   still has depth with animation switched off. */
.s-hero::before {
	content: "";
	position: absolute;
	inset: -30% -10% auto auto;
	width: 620px; height: 620px;
	background: radial-gradient(circle, color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 68%);
	pointer-events: none;
}
.s-hero-in { z-index: 1; }

.s-hero-in {
	display: grid;
	grid-template-columns: 1.05fr .95fr;
	gap: 64px;
	align-items: center;
	position: relative;
}

.s-hero-plain .s-hero-in { grid-template-columns: 1fr; max-width: 860px; }
/* Small print inside a hero sits over drifting colour, so it takes the darker
   grey rather than the faint one. */
.s-hero .s-note, .s-hero .s-deck-note { color: var(--text-2); }
.s-hero-copy .s-lede { max-width: 54ch; }

.s-trust { background: var(--ink); color: #F3EEFF; padding: 26px 0; position: relative; overflow: hidden; }
.s-trust-in { display: flex; flex-wrap: wrap; gap: 16px 52px; align-items: baseline; justify-content: center; }
.s-trust-i { display: flex; gap: 9px; align-items: baseline; }
.s-trust-i b { font-family: var(--display); font-size: 27px; font-weight: 600; color: var(--pop); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.s-trust-i span { font-size: 14px; color: #C4B6E6; }

.s-cards { display: grid; gap: 22px; }
.s-cards-2 { grid-template-columns: repeat(2, 1fr); }
.s-cards-3 { grid-template-columns: repeat(3, 1fr); }
.s-cards-4 { grid-template-columns: repeat(4, 1fr); }

.s-card {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 26px;
	display: flex;
	flex-direction: column;
	box-shadow: var(--shadow);
}

.s-tone-ink .s-card { background: var(--ink-2); border-color: var(--line-ink); box-shadow: none; }

.s-card h3 { margin-bottom: 8px; }
.s-card p { color: var(--text-2); font-size: 15.5px; margin: 0; }
.s-card .s-ticks { margin-top: 16px; }

.s-card-ico {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 46px; height: 46px;
	border-radius: 13px;
	background: color-mix(in srgb, var(--accent) 17%, transparent);
	color: var(--accent-deep);
	margin-bottom: 18px;
}
.s-tone-ink .s-card-ico { color: var(--accent); }

/* position and overflow are needed here rather than in the motion section,
   because the pointer light is drawn by a pseudo element that has to be
   contained by the card whether or not it is allowed to fade in. */
.s-card-link { text-decoration: none; position: relative; overflow: hidden; }
.s-card-link:is(:hover, :focus-visible) { border-color: var(--accent); box-shadow: var(--shadow-lg); }

.s-split { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; align-items: center; }
.s-split-flip .s-split-copy { order: 2; }
.s-split-copy p { color: var(--text-2); }
.s-split-copy > .s-h2 { margin-bottom: 18px; }

.s-steps { list-style: none; counter-reset: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.s-steps li { display: grid; grid-template-columns: 56px 1fr; gap: 22px; padding: 24px 0; border-top: 1px solid var(--line); }
.s-steps li:last-child { border-bottom: 1px solid var(--line); }
.s-steps h3 { margin-bottom: 6px; }
.s-steps p { color: var(--text-2); margin: 0; }

.s-step-n {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px; height: 44px;
	border-radius: 50%;
	border: 1.5px solid var(--accent);
	color: var(--accent-deep);
	font-family: var(--display);
	font-size: 19px;
	font-weight: 600;
}

.s-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
.s-stat { border-left: 4px solid var(--accent); padding: 6px 0 6px 20px; }
.s-stat:nth-child(4n+2) { border-left-color: var(--pop-2); }
.s-stat:nth-child(4n+3) { border-left-color: var(--good-hi); }
.s-stat:nth-child(4n+4) { border-left-color: var(--cool-hi); }
.s-stat b { display: block; font-family: var(--display); font-size: 46px; line-height: 1; font-weight: 600; letter-spacing: -0.03em; }
.s-stat span { display: block; color: var(--text-2); font-size: 15px; margin-top: 8px; }

.s-quotes { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; }
.s-quote { margin: 0; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r); padding: 28px; position: relative; }
.s-quote-mark { color: var(--accent); display: block; margin-bottom: 10px; }
.s-quote blockquote { margin: 0 0 16px; font-size: 18px; line-height: 1.55; }
.s-quote figcaption { font-size: 14px; font-weight: 650; }
.s-quote figcaption span { display: block; font-weight: 400; color: var(--faint); }

.s-faq { display: grid; gap: 0; max-width: 860px; }
.s-faq-i { border-top: 1px solid var(--line); }
.s-faq-i:last-child { border-bottom: 1px solid var(--line); }
.s-faq-i summary {
	list-style: none;
	cursor: pointer;
	padding: 22px 44px 22px 0;
	font-size: 18px;
	font-weight: 600;
	position: relative;
	display: block;
}
.s-faq-i summary::-webkit-details-marker { display: none; }
.s-faq-x {
	position: absolute;
	right: 4px; top: 50%;
	width: 15px; height: 15px;
	margin-top: -7px;
}
.s-faq-x::before, .s-faq-x::after {
	content: "";
	position: absolute;
	background: var(--accent-deep);
	border-radius: 2px;
}
.s-faq-x::before { left: 0; right: 0; top: 6.5px; height: 2px; }
.s-faq-x::after { top: 0; bottom: 0; left: 6.5px; width: 2px; }
.s-faq-i[open] .s-faq-x::after { transform: scaleY(0); }
.s-faq-a { padding: 0 40px 24px 0; }
.s-faq-a p { color: var(--text-2); margin: 0 0 12px; }
.s-faq-a p:last-child { margin: 0; }

.s-plans { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; align-items: start; }

.s-plan {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 28px 24px;
	position: relative;
	box-shadow: var(--shadow);
}
.s-plan-best { border-color: var(--accent); border-width: 2px; box-shadow: var(--shadow-lg); }
.s-plan-flag {
	position: absolute;
	top: -13px; left: 24px;
	background: var(--accent);
	color: var(--accent-ink);
	font-size: 11.5px;
	font-weight: 800;
	letter-spacing: .08em;
	text-transform: uppercase;
	padding: 5px 12px;
	border-radius: 999px;
}
.s-plan h3 { margin-bottom: 4px; }
.s-plan-who { color: var(--faint); font-size: 14.5px; margin: 0 0 18px; min-height: 42px; }
.s-plan-price { display: flex; align-items: baseline; gap: 7px; margin: 0 0 4px; }
.s-plan-price b { font-family: var(--display); font-size: 42px; font-weight: 600; letter-spacing: -0.03em; }
.s-plan-price span { color: var(--faint); font-size: 15px; }
.s-plan-sub { color: var(--text-2); font-size: 14px; margin: 0 0 20px; min-height: 40px; }
.s-plan .s-btn { margin-bottom: 22px; }
.s-plan .s-ticks { margin-top: 0; border-top: 1px solid var(--line); padding-top: 20px; }

/*
 * contain:paint is doing real work here. Without it a wide table inside a
 * scrolling wrapper still widens the document itself in Chromium, so the page
 * reports horizontal overflow even though nothing is visibly cut off. That is
 * the difference between a page that scrolls sideways on a phone and one that
 * does not.
 */
.s-tablewrap {
	overflow-x: auto;
	contain: paint;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--paper);
	max-width: 100%;
}
.s-table { width: 100%; border-collapse: collapse; font-size: 15.5px; min-width: 640px; }
.s-table th, .s-table td { padding: 15px 18px; text-align: left; border-bottom: 1px solid var(--line); vertical-align: top; }
.s-table th { font-family: var(--font); font-size: 12px; text-transform: uppercase; letter-spacing: .1em; font-weight: 700; color: var(--faint); background: var(--sand); }
.s-table th.s-mid, .s-table td.s-mid { text-align: center; }
.s-table tbody tr:last-child td { border-bottom: 0; }
.s-table td:first-child { font-weight: 600; }
.s-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.s-yes { color: var(--good); display: inline-flex; }
.s-no { color: var(--faint); opacity: .55; display: inline-flex; }

.s-links { display: grid; gap: 10px; }
.s-link {
	display: flex;
	align-items: center;
	gap: 18px;
	padding: 18px 22px;
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r-sm);
	text-decoration: none;
}
.s-link:is(:hover, :focus-visible) { border-color: var(--accent); box-shadow: var(--shadow); }
.s-link-mark {
	flex: none;
	width: 46px; height: 38px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 8px;
	background: var(--sand);
	border: 1px solid var(--line);
	font-size: 13px;
	font-weight: 800;
	letter-spacing: .06em;
	color: var(--text-2);
}
.s-link-body { flex: 1 1 auto; min-width: 0; }
.s-link-body b { display: block; font-size: 17px; font-weight: 650; }
.s-link-body span { display: block; color: var(--faint); font-size: 14.5px; }
.s-link-go { flex: none; color: var(--accent-deep); }

.s-prose { font-size: 17.5px; }
.s-prose h2 { font-size: 30px; margin: 44px 0 14px; }
.s-prose h3 { font-size: 21px; margin: 34px 0 10px; }
.s-prose p { color: var(--text-2); }
.s-prose ul, .s-prose ol { color: var(--text-2); padding-left: 22px; margin: 0 0 20px; display: grid; gap: 9px; }
.s-prose li { line-height: 1.55; }
.s-prose > *:first-child { margin-top: 0; }

.s-callout {
	background: var(--sand);
	border-left: 3px solid var(--accent);
	border-radius: 0 var(--r-sm) var(--r-sm) 0;
	padding: 18px 22px;
	margin: 26px 0;
	color: var(--text);
	font-size: 16.5px;
}

.s-cta { background: var(--ink); color: #F7F3FF; padding: 76px 0; position: relative; overflow: hidden; }
.s-cta-in { display: flex; flex-wrap: wrap; gap: 32px; align-items: center; justify-content: space-between; }
.s-cta .s-h2 { margin-bottom: 10px; max-width: 22ch; }
.s-cta .s-lede { color: #C9BCEC; max-width: 46ch; margin: 0; }
.s-cta-btns { display: flex; flex-wrap: wrap; gap: 12px; }
.s-cta .s-btn-sec { color: #F7F3FF; border-color: #4B2F8C; }
.s-cta .s-btn-sec:is(:hover, :focus-visible) { color: #FFFFFF; border-color: var(--pop); background: rgba(255, 255, 255, .08); }

.s-article { padding: 72px 0 40px; }
.s-article .s-h1 { margin-bottom: 26px; }
.s-article-back { margin-top: 44px; }
.s-pager { margin-top: 40px; }
.s-pager .nav-links { display: flex; gap: 8px; flex-wrap: wrap; }
.s-pager .page-numbers {
	display: inline-flex; align-items: center; justify-content: center;
	min-width: 42px; height: 42px; padding: 0 12px;
	border: 1px solid var(--line); border-radius: 999px; text-decoration: none;
}
.s-pager .page-numbers.current { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ------------------------------------------------------------------ */
/* 8. Product objects                                                  */
/* ------------------------------------------------------------------ */

.s-hook {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	padding: 26px 26px 22px;
	margin: 0;
	box-shadow: var(--shadow-lg);
	position: relative;
}
.s-tone-ink .s-hook { background: var(--ink-2); border-color: var(--line-ink); }

.s-hook-top { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.s-hook-lang {
	font-size: 11px; font-weight: 800; letter-spacing: .13em; text-transform: uppercase;
	color: var(--accent-deep);
}
.s-hook-g {
	margin-left: auto;
	font-size: 11px; font-weight: 700; letter-spacing: .05em;
	padding: 3px 10px; border-radius: 999px;
}
.s-hook-g-m { background: color-mix(in srgb, var(--bad-hi) 24%, transparent); color: var(--bad); }
.s-hook-g-f { background: color-mix(in srgb, var(--cool-hi) 26%, transparent); color: var(--cool); }
.s-hook-g-n { background: color-mix(in srgb, var(--violet-hi) 26%, transparent); color: var(--violet); }
.s-hook-g-pl { background: color-mix(in srgb, var(--good-hi) 26%, transparent); color: var(--good); }

.s-hook-term {
	font-family: var(--display);
	font-size: clamp(34px, 3.6vw, 46px);
	font-weight: 600;
	letter-spacing: -0.03em;
	line-height: 1.05;
	margin: 0;
}
.s-hook-term.rtl { direction: rtl; }
.s-hook-read { color: var(--accent-deep); font-size: 17px; font-weight: 600; margin: 4px 0 0; }
.s-hook-tr { color: var(--faint); font-size: 13.5px; letter-spacing: .05em; margin: 2px 0 0; }
.s-hook-gloss { font-size: 19px; font-weight: 600; margin: 8px 0 0; }

.s-hook-body {
	margin-top: 18px;
	padding: 16px 18px;
	border-radius: var(--r-sm);
	background: color-mix(in srgb, var(--accent) 11%, transparent);
	border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
}
.s-hook-kw { margin: 0 0 7px; font-size: 14px; color: var(--text-2); }
.s-hook-kw b { color: var(--accent-deep); font-size: 15px; }
.s-hook-img { margin: 0; font-size: 16px; line-height: 1.5; }

.s-hook-ex { margin-top: 14px; font-size: 15px; }
.s-hook-ex span { display: block; font-weight: 600; }
.s-hook-ex em { display: block; color: var(--faint); font-style: normal; font-size: 14px; }

.s-hook-sm { padding: 20px; box-shadow: var(--shadow); border-radius: var(--r); }
.s-hook-sm .s-hook-term { font-size: 27px; }
.s-hook-sm .s-hook-gloss { font-size: 16px; }
.s-hook-sm .s-hook-img { font-size: 15px; }
.s-hook-sm .s-hook-ex { display: none; }

.s-deck { position: relative; }
.s-deck::before, .s-deck::after {
	content: "";
	position: absolute;
	inset: 0 0 auto;
	height: 100%;
	border-radius: var(--r-lg);
	border: 1px solid var(--line);
	background: var(--paper);
	z-index: -1;
}
.s-deck::before { transform: rotate(-2deg) translateY(6px); }
.s-deck::after { transform: rotate(2.4deg) translateY(12px); opacity: .7; }

.s-deck-bar { display: flex; align-items: center; gap: 14px; margin-top: 18px; justify-content: center; }
.s-deck-btn {
	width: 40px; height: 40px;
	border-radius: 50%;
	border: 1px solid var(--line-2);
	background: var(--paper);
	color: var(--text);
	font-size: 20px;
	line-height: 1;
	cursor: pointer;
}
.s-deck-btn:is(:hover, :focus-visible) { border-color: var(--text); }
.s-deck-dots { display: flex; gap: 6px; }
.s-deck-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--line-2); display: block; }
.s-deck-dots i.on { background: var(--accent); }
.s-deck-note { font-size: 13.5px; color: var(--faint); text-align: center; margin: 12px 0 0; }

.s-hookgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
.s-hookcell { display: flex; }
.s-hookcell .s-hook { width: 100%; }
.s-hookcell[hidden] { display: none; }

.s-filter { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 28px; }
.s-filter button {
	font: inherit;
	font-size: 14.5px;
	font-weight: 600;
	padding: 8px 16px;
	border-radius: 999px;
	border: 1px solid var(--line-2);
	background: transparent;
	color: var(--text-2);
	cursor: pointer;
}
.s-filter button:is(:hover, :focus-visible) { border-color: var(--text); color: var(--text); }
.s-filter button.on { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.s-curve {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 22px;
	box-shadow: var(--shadow);
}
.s-tone-ink .s-curve { background: var(--ink-2); border-color: var(--line-ink); }
.s-curve svg { width: 100%; height: auto; display: block; }
.s-crv-grid { stroke: var(--line); stroke-width: 1; }
.s-crv-lab { fill: var(--faint); font-size: 11px; font-family: var(--font); }
.s-crv-line { stroke: var(--accent-deep); stroke-width: 2.6; fill: none; stroke-linecap: round; }
.s-crv-fill { fill: color-mix(in srgb, var(--accent) 16%, transparent); stroke: none; }
.s-crv-mark { stroke: var(--text); stroke-width: 1.2; stroke-dasharray: 3 4; opacity: .5; }
.s-crv-dot { fill: var(--pop-2); stroke: var(--paper); stroke-width: 2.5; }
.s-curve-cap { font-size: 14px; color: var(--faint); margin: 14px 0 0; }

.s-ladder { list-style: none; margin: 0 auto; padding: 0; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; max-width: 820px; }
.s-ladder li {
	display: flex; align-items: center; gap: 10px;
	padding: 12px 18px;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.s-ladder-n { color: var(--faint); font-size: 12px; font-weight: 700; }
.s-ladder-g { font-family: var(--display); font-size: 20px; font-weight: 600; }
.s-tone-ink .s-ladder-g { color: var(--pop); }

.s-genders { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 16px; }
.s-gender { padding: 22px; border-radius: var(--r); border: 1px solid var(--line); }
.s-gender b { display: block; font-size: 18px; margin-bottom: 5px; }
.s-gender span { color: var(--text-2); font-size: 15.5px; }
/*
 * These three have to read as three different things at a glance, because the
 * whole point of the section is that the cue never varies. At nine percent
 * they were technically tinted and practically identical.
 */
.s-gender b { display: flex; align-items: center; gap: 9px; }
.s-gender b::before { content: ""; width: 11px; height: 11px; border-radius: 3px; flex: none; }
.s-gender-m { background: color-mix(in srgb, var(--bad-hi) 20%, transparent); border-color: color-mix(in srgb, var(--bad-hi) 60%, transparent); }
.s-gender-f { background: color-mix(in srgb, var(--cool-hi) 22%, transparent); border-color: color-mix(in srgb, var(--cool-hi) 62%, transparent); }
.s-gender-n { background: color-mix(in srgb, var(--violet-hi) 22%, transparent); border-color: color-mix(in srgb, var(--violet-hi) 62%, transparent); }
.s-gender-m b::before { background: var(--bad-hi); }
.s-gender-f b::before { background: var(--cool-hi); }
.s-gender-n b::before { background: var(--violet-hi); }

.s-reader {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 30px 32px;
	max-width: 720px;
	margin: 0 auto;
	text-align: left;
	box-shadow: var(--shadow);
}
.s-reader-h { display: flex; align-items: baseline; gap: 12px; margin: 0 0 18px; }
.s-reader-h b { font-family: var(--display); font-size: 22px; font-weight: 600; }
.s-reader-h span { font-size: 11px; font-weight: 800; letter-spacing: .1em; color: var(--accent-deep); border: 1px solid var(--line-2); padding: 2px 8px; border-radius: 999px; }
.s-reader-body { font-size: 19px; line-height: 1.9; white-space: pre-wrap; }
.s-reader-note { font-size: 13.5px; color: var(--faint); margin: 20px 0 0; }

.s-tok {
	font: inherit;
	background: none;
	border: 0;
	padding: 0 1px;
	cursor: pointer;
	color: inherit;
	border-bottom: 1.5px solid color-mix(in srgb, var(--accent) 34%, transparent);
	border-radius: 3px;
}
.s-tok:is(:hover, :focus-visible), .s-tok.on { background: color-mix(in srgb, var(--accent) 24%, transparent); border-bottom-color: var(--accent-deep); }

.s-gloss-pop {
	position: absolute;
	z-index: 140;
	background: var(--ink);
	color: var(--paper);
	border-radius: var(--r-sm);
	padding: 9px 13px;
	font-size: 14.5px;
	max-width: 260px;
	box-shadow: var(--shadow-lg);
	pointer-events: none;
}
.s-gloss-pop b { display: block; font-size: 13px; color: var(--pop); }

.s-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
.s-strip-i { border: 1px solid var(--line); border-radius: var(--r-sm); padding: 14px 16px; background: var(--paper); }
.s-strip-i b { display: block; font-family: var(--display); font-size: 22px; font-weight: 600; }
.s-strip-i span { display: block; color: var(--text-2); font-size: 14px; }
.s-strip-i em { display: block; color: var(--accent-deep); font-style: normal; font-size: 13px; margin-top: 4px; }

.s-shot { margin: 0; }
.s-shot img { border: 1px solid var(--line); border-radius: var(--r); box-shadow: var(--shadow-lg); }
.s-shot figcaption { font-size: 14px; color: var(--faint); margin-top: 12px; }

/* ------------------------------------------------------------------ */
/* 9. Forms                                                            */
/* ------------------------------------------------------------------ */

.s-form {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 32px;
	max-width: 820px;
	box-shadow: var(--shadow);
}

.s-form-h { margin-bottom: 6px; }
.s-form-l { color: var(--text-2); }

.s-fs { border: 0; padding: 0; margin: 0 0 28px; }
.s-fs legend {
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .13em;
	text-transform: uppercase;
	color: var(--accent-deep);
	padding: 0;
	margin-bottom: 16px;
}

.s-fgrid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; }
.s-fgrid-inline { grid-template-columns: 1fr 1.6fr; }
.s-f-full { grid-column: 1 / -1; }
.s-fchecks { display: grid; gap: 10px; margin: 20px 0; }

.s-f label { display: block; font-size: 14px; font-weight: 650; margin-bottom: 7px; }
.s-req { font-weight: 400; color: var(--faint); font-size: 12px; text-transform: uppercase; letter-spacing: .08em; }

/* Every input type the forms actually use. A password field left out of this
   list renders at the browser default width, which on the signup form put a
   narrow box next to a full width one and looked like a bug because it was. */
.s-form input[type="text"],
.s-form input[type="email"],
.s-form input[type="password"],
.s-form input[type="tel"],
.s-form input[type="number"],
.s-form input[type="url"],
.s-form input[type="date"],
.s-form select,
.s-form textarea {
	width: 100%;
	font: inherit;
	font-size: 16px;
	padding: 12px 14px;
	border: 1.5px solid var(--line-2);
	border-radius: var(--r-sm);
	background: #FFF;
	color: var(--text);
	min-height: 50px;
}
.s-form textarea { min-height: 130px; resize: vertical; line-height: 1.5; }
.s-form input:focus, .s-form select:focus, .s-form textarea:focus { border-color: var(--accent-deep); outline: none; }

.s-f-check label { display: flex; gap: 11px; align-items: flex-start; font-weight: 500; font-size: 15.5px; cursor: pointer; }
.s-f-check input { width: 20px; height: 20px; margin-top: 2px; flex: none; accent-color: var(--accent-deep); }

.s-hint { display: block; font-size: 13.5px; color: var(--faint); margin-top: 6px; }
.s-f-actions { margin-top: 26px; }

.s-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.s-formdone {
	background: color-mix(in srgb, var(--good-hi) 20%, transparent);
	border: 1px solid color-mix(in srgb, var(--good-hi) 55%, transparent);
	border-radius: var(--r-sm);
	padding: 18px 22px;
	margin-bottom: 26px;
	max-width: 820px;
}
.s-formdone b { display: block; font-size: 17px; }
.s-formdone span { color: var(--text-2); }

/* ------------------------------------------------------------------ */
/* 10. The app shell                                                   */
/* ------------------------------------------------------------------ */

.s-main-app { background: var(--sand); }

.s-appbar { background: var(--ink); color: #F3EEFF; padding: 12px 0; }
.s-appbar .s-wrap { display: flex; align-items: center; gap: 18px; }
.s-appbar-t { margin: 0; font-size: 13px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--pop); }
.s-appbar-l { margin: 0 0 0 auto; display: flex; gap: 18px; font-size: 14px; }
.s-appbar-l a { color: #D6CBF2; text-decoration: none; }
.s-appbar-l a:is(:hover, :focus-visible) { color: #FFF; }

/* The app is a workspace, not a page of prose, so it gets the window rather
   than the 1180px reading column the marketing pages use. A word list, a
   gradebook and a stats table all have columns that were being squeezed into
   a third of a wide screen for no reason. The gutter stays so nothing touches
   the edge of the glass. */
.s-appwrap {
	padding: 28px 0 60px;
	max-width: none;
}

.s-appbar .s-wrap { max-width: none; }

/* The app draws its own dark surface, so give it room rather than styling
   over it. Only the outer frame belongs to the site. */
.s-appwrap #mnem-root { box-shadow: var(--shadow-lg); }

/* ------------------------------------------------------------------ */
/* 11. The loud sections                                               */
/* ------------------------------------------------------------------ */

/*
 * The aurora. Three blurred blobs behind a hero or a closing band. They are
 * decorative, so they are inert to the pointer, they carry no content, and
 * they sit behind everything. The blur is what makes them look like light
 * rather than three circles, and it is also the expensive part, so there are
 * three of them and not thirty.
 */
.s-aurora {
	position: absolute;
	inset: 0;
	overflow: hidden;
	pointer-events: none;
	z-index: 0;
}
/*
 * There is deliberately no will-change here.
 *
 * A blurred blob that is promised a compositor layer for the whole life of the
 * page holds that memory whether the band is on screen or not, and a page with
 * a hero and a closing band has six of them. The script hands the hint out
 * when the band comes into view and takes it back when it leaves, which is the
 * only honest use of the property: a warning about something imminent, not a
 * permanent label.
 */
.s-aurora i {
	position: absolute;
	display: block;
	border-radius: 50%;
	filter: blur(70px);
	opacity: .55;
}
/*
 * The opacities are as high as they can go and still leave the copy above
 * AA. A blurred yellow at half strength over the cream band lifts the ground
 * to the point where the faint grey underneath the buttons measures 4.5, and
 * the aurora drifts, so there is no fixed worst case to design around. These
 * numbers keep roughly a stop in hand, and the hero's own small print uses
 * the darker of the two greys for the same reason.
 */
.s-aurora i:nth-child(1) { width: 44vw; height: 44vw; max-width: 620px; max-height: 620px; top: -18%; right: -8%; background: var(--accent); opacity: .5; }
.s-aurora i:nth-child(2) { width: 34vw; height: 34vw; max-width: 460px; max-height: 460px; bottom: -22%; left: -6%; background: var(--pop); opacity: .32; }
.s-aurora i:nth-child(3) { width: 28vw; height: 28vw; max-width: 380px; max-height: 380px; top: 24%; left: 42%; background: var(--pop-2); opacity: .22; }
/* Much weaker on a dark band. Three bright blurs at full strength over a
   deep ground do not read as light, they read as a stain, and they eat the
   contrast the copy above them was measured against. */
.s-tone-ink .s-aurora i, .s-cta .s-aurora i, .s-trust .s-aurora i { opacity: .16; filter: blur(90px); }

/*
 * The two loudest bands get no aurora at all. They are already a gradient of
 * three bright colours, so the blobs add nothing to look at, and they take
 * something away: a violet blob at half strength over the coral band drops
 * the dark copy on it from 5.5 to 3.7 against the ground, which is a fail.
 * The pixel level contrast check in the test suite is what found that, and it
 * is the reason this rule exists rather than an opacity tweak.
 */
.s-tone-gold .s-aurora, .s-tone-punch .s-aurora { display: none; }

.s-hero > .s-wrap, .s-cta > .s-wrap, .s-trust > .s-wrap { position: relative; z-index: 1; }

/* Grain. One tiny inline SVG turbulence tiled over the whole page at very low
   opacity. It stops the big flat colour fields from banding on a cheap panel
   and it is the difference between "bright" and "unfinished". */
.s-grain {
	position: fixed;
	inset: 0;
	z-index: 300;
	pointer-events: none;
	opacity: .035;
	background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/*
 * The scroll progress line.
 *
 * Full width and scaled down to nothing, rather than a bar whose width grows.
 * Both look identical and only one of them is free: a width is laid out, and
 * this is repainted on every frame of every scroll on every page. A scaleX is
 * handed to the compositor and costs the main thread nothing at all.
 *
 * It starts at zero scale, so with no script it is not a broken bar sitting at
 * a hundred percent, it is no bar. The script also hides it outright on a page
 * too short to have any progress worth reporting.
 */
.s-prog {
	position: fixed;
	top: 0; left: 0;
	height: 3px;
	width: 100%;
	transform: scaleX(0);
	transform-origin: 0 50%;
	z-index: 210;
	background: linear-gradient(90deg, var(--accent), var(--pop-2) 45%, var(--pop));
	pointer-events: none;
}
.s-prog.is-off { opacity: 0; }

/*
 * The travelling focus ring.
 *
 * A hollow rounded box that follows whatever the keyboard has landed on, drawn
 * outside the browser's own focus outline rather than instead of it. That is
 * the whole design constraint: the native outline is the accessible one and it
 * is never touched, so if this element is never created, or the script fails
 * halfway, nothing has been taken away. The centre is transparent and it never
 * receives a pointer event, so it cannot cover or catch the thing it is
 * pointing at.
 *
 * Created by the script, which is why there is no element for it in any PHP.
 */
.s-ring {
	position: fixed;
	top: 0; left: 0;
	z-index: 205;
	pointer-events: none;
	opacity: 0;
	border-radius: 10px;
	box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 45%, transparent),
		0 0 0 6px color-mix(in srgb, var(--accent) 14%, transparent);
}

/* ---- Marquee ------------------------------------------------------ */

/*
 * A kinetic ticker of real words from the library. Two identical tracks sit
 * side by side and each slides left by exactly its own width, so the moment
 * the first one leaves the second is already sitting where it started. That
 * is the whole trick, and it is why the duplicate must be an exact copy.
 *
 * The duplicate is aria-hidden and the strip as a whole is decorative, so a
 * screen reader hears the words once, not twice.
 */
.s-marquee {
	display: flex;
	overflow: hidden;
	contain: paint;
	max-width: 100%;
	padding: 20px 0;
	border-top: 1px solid var(--line);
	border-bottom: 1px solid var(--line);
	background: var(--paper);
	user-select: none;
	/* The words fade out at both ends instead of being sliced off by the edge
	   of the window. Static, so it is there with or without movement: a word
	   cut in half by a hard edge looks like a bug either way. */
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
	mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.s-marquee-loud {
	background: var(--ink);
	border-color: var(--line-ink);
	--text: #F7F3FF;
	--faint: #A294CC;
	color: #F7F3FF;
}
.s-mq-track { display: flex; flex: 0 0 auto; align-items: center; }
.s-mq-i {
	display: inline-flex;
	align-items: baseline;
	gap: 10px;
	padding: 0 26px;
	white-space: nowrap;
	font-family: var(--display);
	font-size: 26px;
	font-weight: 600;
	letter-spacing: -0.02em;
}
.s-mq-i i { font-family: var(--font); font-style: normal; font-size: 14px; font-weight: 500; color: var(--faint); letter-spacing: 0; }
.s-mq-i::before {
	content: "";
	align-self: center;
	width: 9px; height: 9px;
	border-radius: 50%;
	margin-right: 18px;
	background: var(--accent);
}
.s-mq-i:nth-child(5n+2)::before { background: var(--pop-2); }
.s-mq-i:nth-child(5n+3)::before { background: var(--good-hi); }
.s-mq-i:nth-child(5n+4)::before { background: var(--pop); }
.s-mq-i:nth-child(5n+5)::before { background: var(--cool-hi); }

/* ---- Statement ---------------------------------------------------- */

.s-statement { text-align: center; }
.s-st-h {
	font-family: var(--display);
	font-weight: 600;
	font-size: clamp(32px, 5.6vw, 68px);
	line-height: 1.08;
	letter-spacing: -0.03em;
	margin: 0 auto;
	max-width: 20ch;
	text-wrap: balance;
}
.s-st-line { display: block; }
/*
 * isolation:isolate is load bearing. The stroke is a pseudo element at
 * z-index -1 so it sits behind the letters, and without a stacking context of
 * its own that -1 sends it behind the section background instead, where it is
 * simply invisible. This is the same class of mistake as the specificity bugs
 * further up: the rule looks right and renders nothing.
 */
.s-mark { position: relative; display: inline-block; white-space: nowrap; isolation: isolate; }
.s-mark::before {
	content: "";
	position: absolute;
	left: -.14em; right: -.14em;
	bottom: .07em;
	height: .38em;
	border-radius: .22em;
	background: var(--pop);
	z-index: -1;
	transform-origin: left center;
}
.s-mark-2::before { background: var(--good-hi); }
.s-mark-3::before { background: var(--pop-2); opacity: .55; }
.s-st-body { margin: 30px auto 0; max-width: 58ch; font-size: clamp(17px, 1.4vw, 20px); color: var(--text-2); }
.s-st-foot { margin-top: 30px; }

/* ---- The sticky sequence ------------------------------------------ */

/*
 * The signature piece: four steps scroll past a stage that swaps its visual
 * as each one arrives.
 *
 * The fallback matters more than the effect. With JavaScript off, or on a
 * phone, the stage stops being sticky and every panel simply renders in a
 * column, so the reader still gets all four visuals in order and nothing is
 * hidden behind a scroll position that never happens.
 */
.s-seq-in { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: start; }
.s-seq-steps { list-style: none; margin: 0; padding: 0; }
.s-seq-step { padding: 26px 0 26px 26px; border-left: 2px solid var(--line); }
.s-seq-step h3 { font-size: 24px; margin-bottom: 8px; }
.s-seq-step p { color: var(--text-2); margin: 0; }
.s-seq-n {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 34px; height: 34px;
	border-radius: 50%;
	background: var(--accent);
	color: #FFFFFF;
	font-size: 15px;
	font-weight: 800;
	margin-bottom: 14px;
}
.s-seq-step:nth-child(2) .s-seq-n { background: var(--pop-2); }
.s-seq-step:nth-child(3) .s-seq-n { background: var(--good); }
.s-seq-step:nth-child(4) .s-seq-n { background: var(--cool); }
.s-seq-stage { display: grid; gap: 30px; }
.s-seq-panel { min-width: 0; }
.s-seq-cap { font-size: 13.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-deep); margin: 0 0 12px; }

@media (min-width: 901px) {
	.s-seq-step { min-height: 58vh; display: flex; flex-direction: column; justify-content: center; }
	.s-seq-stage { position: sticky; top: 108px; }
	/*
	 * The panels only stack on top of each other once the script is running.
	 * No script means a readable column of four visuals rather than one
	 * visible panel and three ghosts underneath it.
	 *
	 * The inactive ones are hidden with opacity and not with visibility, on
	 * purpose. visibility:hidden takes text out of innerText, which would
	 * quietly exempt this section from the checks that read every page
	 * looking for a competitor's name or an em dash. Nothing in a panel is
	 * focusable, so opacity plus pointer-events is enough.
	 */
	.js-motion .s-seq-stage { gap: 0; }
	.js-motion .s-seq-panel { grid-area: 1 / 1; opacity: 0; pointer-events: none; }
	.js-motion .s-seq-panel.is-on { opacity: 1; pointer-events: auto; }
	.js-motion .s-seq-step { opacity: .42; }
	.js-motion .s-seq-step.is-on { opacity: 1; border-left-color: var(--accent); }
}

@media (max-width: 900px) {
	.s-seq-in { grid-template-columns: 1fr; gap: 34px; }
	.s-seq-stage { gap: 30px; grid-auto-rows: auto; }
	.s-seq-panel { grid-area: auto; }
}

/* ---- Bento -------------------------------------------------------- */

.s-bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: 18px; }
.s-bento-i {
	grid-column: span 2;
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 24px;
	position: relative;
	overflow: hidden;
	display: flex;
	flex-direction: column;
}
.s-bento-i h3 { font-size: 19px; margin-bottom: 6px; }
.s-bento-i p { color: var(--text-2); font-size: 15px; margin: 0; }
.s-bento-w3 { grid-column: span 3; }
.s-bento-w4 { grid-column: span 4; }
.s-bento-w6 { grid-column: span 6; }
.s-bento-ico {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 42px; height: 42px;
	border-radius: 12px;
	margin-bottom: 16px;
	background: var(--accent);
	color: #FFFFFF;
}
.s-bento-i:nth-child(6n+2) .s-bento-ico { background: var(--pop-2); }
.s-bento-i:nth-child(6n+3) .s-bento-ico { background: var(--good); }
.s-bento-i:nth-child(6n+4) .s-bento-ico { background: var(--cool); }
.s-bento-i:nth-child(6n+5) .s-bento-ico { background: var(--warn); }
.s-bento-i:nth-child(6n+6) .s-bento-ico { background: var(--violet); }
.s-bento-loud {
	background: var(--ink);
	border-color: var(--line-ink);
	color: #F7F3FF;
	--text: #F7F3FF;
	--text-2: #C9BCEC;
}
.s-bento-tag {
	align-self: flex-start;
	margin-top: 16px;
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .08em;
	text-transform: uppercase;
	padding: 4px 11px;
	border-radius: 999px;
	background: color-mix(in srgb, var(--accent) 16%, transparent);
	color: var(--accent-deep);
}
.s-bento-loud .s-bento-tag { background: rgba(255, 255, 255, .14); color: var(--pop); }

/* ---- Timeline ----------------------------------------------------- */

.s-time { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 0; }
.s-time-i { padding: 26px 22px 26px 0; border-top: 3px solid var(--line); position: relative; }
.s-time-i::before {
	content: "";
	position: absolute;
	top: -8px; left: 0;
	width: 13px; height: 13px;
	border-radius: 50%;
	background: var(--accent);
	border: 3px solid var(--paper);
}
.s-time-i:nth-child(4n+2)::before { background: var(--pop-2); }
.s-time-i:nth-child(4n+3)::before { background: var(--good-hi); }
.s-time-i:nth-child(4n+4)::before { background: var(--cool-hi); }
.s-time-d { font-size: 12px; font-weight: 800; letter-spacing: .13em; text-transform: uppercase; color: var(--accent-deep); margin: 0 0 6px; }
.s-time-i h3 { font-size: 19px; margin-bottom: 6px; }
.s-time-i p { color: var(--text-2); font-size: 15px; margin: 0; }

/* ---- Plan peek ---------------------------------------------------- */

.s-peek { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.s-pk {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 24px;
	display: flex;
	flex-direction: column;
	gap: 4px;
}
.s-pk-best { border: 2px solid var(--accent); box-shadow: var(--shadow-lg); }
.s-pk h3 { margin: 0; font-size: 20px; }
.s-pk-p { font-family: var(--display); font-size: 38px; font-weight: 600; letter-spacing: -0.03em; margin: 6px 0 0; }
.s-pk-p span { font-family: var(--font); font-size: 14px; font-weight: 400; color: var(--faint); letter-spacing: 0; }
.s-pk-w { color: var(--text-2); font-size: 15px; margin: 6px 0 14px; }
.s-pk .s-btn { margin-top: auto; }

/* ---- Artwork ------------------------------------------------------ */

/*
 * Every picture on the site is vector, drawn in class-mnems-art.php from the
 * palette tokens. That is why there is no image sizing here worth speaking of:
 * an SVG that inherits its colours from custom properties needs a box and
 * nothing else, and it is correct at every screen density for free.
 */
.s-art { display: block; width: 100%; height: auto; }

.s-card-art { padding: 0; overflow: hidden; }
.s-card-cover {
	display: block;
	aspect-ratio: 16 / 10;
	overflow: hidden;
	border-bottom: 1px solid var(--line);
}
.s-card-cover .s-art { height: 100%; }
.s-card-art > *:not(.s-card-cover) { margin-left: 24px; margin-right: 24px; }
.s-card-art > h3 { margin-top: 22px; }
.s-card-art > .s-pill { margin-top: 20px; align-self: flex-start; }
.s-card-art > .s-more { margin-bottom: 22px; }

.s-postart { border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-lg); }
.s-postart .s-art { aspect-ratio: 8 / 5; }

.s-art-spec { border-radius: var(--r); }

.s-specgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 14px; }
.s-specgrid figure { margin: 0; }
.s-specgrid figcaption { font-size: 13.5px; color: var(--text-2); margin-top: 8px; text-align: center; }
.s-specgrid a { text-decoration: none; display: block; }

/* The keyword diagram: two circles, and the picture that lives in the
   overlap. Labels sit under the circles rather than inside them, because a
   word set inside a coloured disc is a contrast problem waiting to happen. */
.s-fuse { margin: 0 auto; max-width: 520px; }
.s-fuse-mid {
	margin: 4px auto 0;
	max-width: 46ch;
	text-align: center;
	font-size: 16px;
	color: var(--text-2);
	border-top: 1px solid var(--line);
	padding-top: 16px;
}

.s-art-cohort { max-width: 400px; }
.s-cohort-key { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 14px; font-size: 14px; color: var(--text-2); }
.s-cohort-key span { display: inline-flex; align-items: center; gap: 8px; }
.s-cohort-key i { width: 13px; height: 13px; border-radius: 50%; display: block; }

/* ---- The signup form ---------------------------------------------- */

.s-form-signup { max-width: 720px; }
.s-form-signup .s-f-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }

.s-formbad {
	background: color-mix(in srgb, var(--bad-hi) 18%, transparent);
	border: 1px solid color-mix(in srgb, var(--bad-hi) 55%, transparent);
	border-left-width: 5px;
	border-radius: var(--r-sm);
	padding: 16px 20px;
	margin-bottom: 22px;
	max-width: 720px;
}
.s-formbad b { display: block; font-size: 17px; }
.s-formbad span { color: var(--text-2); }

/* ------------------------------------------------------------------ */
/* 12. Motion                                                          */
/* ------------------------------------------------------------------ */

/*
 * Everything on this site that moves is below this line, and all of it is
 * inside one reduced motion guard. Nothing above this point animates,
 * transitions or transforms on its own.
 *
 * Four rules hold the system together, and the last two are the ones that
 * usually get skipped:
 *
 *  - Every duration, curve and distance comes from the --mo tokens in section
 *    1. There is not one literal time and not one literal curve below here, so
 *    the site retimes from a single block, and tests/site-test.py fails the
 *    build the moment one creeps back in.
 *  - Nothing that runs on a frame touches anything but transform and opacity.
 *    No width, no height, no top or left, in any transition or any keyframe
 *    anywhere in this file. That is a test rather than an intention too.
 *  - The hiding half of every reveal is scoped to html.js-motion, a class the
 *    script only adds when the browser reports no motion preference. No
 *    script, or a stated preference for less movement, and the page is simply
 *    the page: nothing starts invisible, so nothing can stay invisible. That
 *    is the promise the accessibility statement makes in writing, so it is
 *    checked from both ends: by reading this file, and by loading the site in
 *    a browser that asks for less movement and insisting that not one element
 *    on it has a running animation, a transition or a hidden inch.
 *  - Nothing can hide something a person has just reached with a keyboard.
 *    Every rule that starts an element at zero opacity is backed by the
 *    :focus-within escape in 12.9 and by a focus handler in the script.
 *
 * Arrivals triggered by a class are transitions out of a hidden state;
 * arrivals triggered by page load are keyframes with backwards fill. Both
 * leave every element at its authored style when animation is switched off,
 * and the authored style is the visible one.
 */

@media (prefers-reduced-motion: no-preference) {

	/* ---- 12.1 The arrival engine ----------------------------------- */

	/*
	 * Three classes and no more. s-rv hides, is-in arrives, and the variant
	 * class between them decides what the arrival means. The script picks
	 * which elements get which; nothing in the PHP knows about any of it,
	 * which is what makes the no-script fallback free: markup that was never
	 * marked cannot be left hidden.
	 */
	.js-motion .s-rv {
		opacity: 0;
		transform: translateY(var(--mo-rise));
	}
	.js-motion .s-rv.is-in {
		opacity: 1;
		transform: none;
		transition: opacity var(--mo-enter) var(--mo-ease-enter),
			transform var(--mo-enter) var(--mo-ease-enter);
	}

	/*
	 * A group is watched as one element and arrives as a sequence.
	 *
	 * The obvious way round is to watch every card, which costs forty observer
	 * entries on the home page and an inline delay written onto each one by
	 * the script. This way the grid is the only thing watched, each child's
	 * position arrives as a plain number in a custom property, and the
	 * stylesheet does the arithmetic. No inline styles, no per card
	 * bookkeeping, one watched element per grid instead of one per tile.
	 *
	 * It has a second payoff. Because every delay below is arithmetic over
	 * these tokens, redefining them on one element retimes that element and
	 * everything inside it, which is exactly what the script does to a band
	 * somebody has already scrolled most of the way past.
	 */
	.js-motion .s-rv-group { --mo-step: var(--mo-stagger); }
	.js-motion .s-rv-tight { --mo-step: var(--mo-stagger-tight); }

	/* The index carrier. Everything on this site that staggers, staggers by
	   these ten rules: a group of children, and the mobile menu. */
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > * {
		--mo-i: 0;
		--mo-d: calc(var(--mo-step, var(--mo-stagger)) * var(--mo-i));
	}
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(2) { --mo-i: 1; }
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(3) { --mo-i: 2; }
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(4) { --mo-i: 3; }
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(5) { --mo-i: 4; }
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(6) { --mo-i: 5; }
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(7) { --mo-i: 6; }
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(8) { --mo-i: 7; }
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(9) { --mo-i: 8; }
	/* And the cap. Past this the gap stops growing, or a twenty row table
	   would still be arriving a second and a half after its first row. */
	.js-motion :is(.s-rv-group, .s-mobile .s-wrap) > *:nth-child(n+10) { --mo-i: var(--mo-stagger-cap); }

	/* A group does not move itself. Its children do, or the whole grid slides
	   up underneath cards that are also sliding up. */
	.js-motion .s-rv-group {
		opacity: 1;
		transform: none;
	}
	.js-motion .s-rv-group > * {
		opacity: 0;
		transform: translateY(var(--mo-rise-sm));
	}
	.js-motion .s-rv-group.is-in > * {
		opacity: 1;
		transform: none;
		transition: opacity var(--mo-enter) var(--mo-ease-enter) var(--mo-d),
			transform var(--mo-enter) var(--mo-ease-enter) var(--mo-d);
	}

	/* The plain variants, for the things that arrive as one piece. */
	.js-motion .s-rv-s { transform: scale(var(--mo-scale-in)); }
	.js-motion .s-rv-read { transform: translateY(var(--mo-rise-sm)); }
	.js-motion .s-rv-lead { transform: translateY(var(--mo-rise)); }
	.js-motion .s-rv-lead.is-in { transition-duration: var(--mo-lead); }

	/*
	 * The sideways pair only exists where there is room for it. A split column
	 * shifted 28px on a 390px screen pushes the document wider than the
	 * window, which is a horizontal scrollbar on a phone caused by an
	 * animation that means nothing in one column anyway. Below the split
	 * breakpoint they rise like everything else.
	 */
	.js-motion .s-rv-l,
	.js-motion .s-rv-r { transform: translateY(var(--mo-rise)); }
	@media (min-width: 901px) {
		.js-motion .s-rv-l { transform: translateX(calc(var(--mo-slide) * -1)); }
		.js-motion .s-rv-r { transform: translateX(var(--mo-slide)); }
	}

	/* ---- 12.2 A heading leads -------------------------------------- */

	/*
	 * The block that opens most sections. The eyebrow is a label rather than a
	 * line of argument, so it arrives first and quickly; the heading takes the
	 * longer curve because it is the sentence the band is about; and the lede
	 * follows the heading rather than landing with it.
	 */
	.js-motion .s-rv-head > :is(h1, h2, h3) { transform: translateY(var(--mo-rise)); }
	.js-motion .s-rv-head.is-in > :is(h1, h2, h3) { transition-duration: var(--mo-lead); }

	/* ---- 12.3 One motion per section type -------------------------- */

	/* Cards. An object arrives as an object: up and forward together, never a
	   flat fade. The arrow on a card that leads somewhere settles last, so the
	   card reads as landing and then offering. */
	.js-motion .s-rv-cards > * { transform: translateY(var(--mo-rise)) scale(var(--mo-scale-in)); }
	.js-motion .s-rv-cards > * .s-more { opacity: 0; }
	.js-motion .s-rv-cards.is-in > * .s-more {
		opacity: 1;
		transition: opacity var(--mo-enter) var(--mo-ease-enter) calc(var(--mo-d) + var(--mo-stagger));
	}

	/* The bento grid. Tiles come in four widths, so they grow into place
	   rather than rising: a wide tile rising the same distance as a narrow one
	   reads as two different speeds. The coloured badge lands after its own
	   tile on the press curve, which turns a sheet of colour sliding up into a
	   grid filling in. */
	.js-motion .s-rv-tiles > * { transform: translateY(var(--mo-rise-sm)) scale(var(--mo-scale-in)); }
	.js-motion .s-rv-tiles > * .s-bento-ico {
		opacity: 0;
		transform: scale(0);
	}
	.js-motion .s-rv-tiles.is-in > * .s-bento-ico {
		opacity: 1;
		transform: none;
		transition: transform var(--mo-enter) var(--mo-ease-press) calc(var(--mo-d) + var(--mo-stagger)),
			opacity var(--mo-exit) linear calc(var(--mo-d) + var(--mo-stagger));
	}

	/*
	 * Numbered steps. The rule above each step draws across as the step
	 * arrives and the number lands on the press curve, because a count should
	 * arrive rather than drift in. The bar is absolutely positioned, which is
	 * what keeps it out of the row's two column grid: an ordinary pseudo
	 * element here would become a third grid item and break the layout.
	 */
	.js-motion .s-rv-steps > * {
		position: relative;
		transform: translateY(var(--mo-rise-sm));
	}
	.js-motion .s-rv-steps > *::after {
		content: "";
		position: absolute;
		left: 0;
		right: 0;
		top: -1px; /* sits on the 1px rule the row already has */
		height: 2px;
		background: var(--accent);
		transform: scaleX(0);
		transform-origin: 0 50%;
		opacity: 0;
	}
	.js-motion .s-rv-steps.is-in > *::after {
		animation: s-wipe var(--mo-lead) var(--mo-ease-enter) var(--mo-d) backwards;
	}

	/*
	 * A wipe rather than a stroke that stays.
	 *
	 * The first version of this drew an accent bar along the rule and left it
	 * there, which meant the page with movement on it ended up a different
	 * design from the page without: pale grey rules for one person and violet
	 * ones for the other. The fill is backwards rather than forwards, so when
	 * the animation finishes the bar returns to the invisible state it was
	 * authored in and what is left is the rule that was always there.
	 */
	@keyframes s-wipe {
		from { transform: scaleX(0); opacity: 1; }
		65% { transform: scaleX(1); opacity: 1; }
		to { transform: scaleX(1); opacity: 0; }
	}
	.js-motion .s-rv-steps > * .s-step-n { transform: scale(0); }
	.js-motion .s-rv-steps.is-in > * .s-step-n {
		transform: none;
		transition: transform var(--mo-enter) var(--mo-ease-press) calc(var(--mo-d) + var(--mo-stagger-tight));
	}

	/* The statistic band. The number comes from deeper than its own tile and
	   takes the longer curve, and the label underneath waits for it, so the
	   figure is what the eye lands on. The digits are counting up while this
	   happens, which the script does from the real number on the page. */
	.js-motion .s-rv-stats > * { transform: translateY(var(--mo-rise)); }
	.js-motion .s-rv-stats > * b { transform: translateY(var(--mo-rise-sm)); }
	.js-motion .s-rv-stats.is-in > * b {
		transform: none;
		transition: transform var(--mo-lead) var(--mo-ease-enter) var(--mo-d);
	}
	.js-motion .s-rv-stats > * span { opacity: 0; }
	.js-motion .s-rv-stats.is-in > * span {
		opacity: 1;
		transition: opacity var(--mo-enter) var(--mo-ease-enter) calc(var(--mo-d) + var(--mo-stagger));
	}

	/* The timeline. The dot on the line lands first and the line draws out
	   from under it, left to right, which is the direction a timeline is read
	   in. Below the breakpoint the line is vertical and a bar drawn across
	   would be drawn across nothing, so only the dots and the copy move. */
	.js-motion .s-rv-time > * { transform: translateY(var(--mo-rise-sm)); }
	.js-motion .s-rv-time > *::before { transform: scale(0); }
	.js-motion .s-rv-time.is-in > *::before {
		transform: none;
		transition: transform var(--mo-enter) var(--mo-ease-press) calc(var(--mo-d) + var(--mo-stagger-tight));
	}
	@media (min-width: 661px) {
		.js-motion .s-rv-time > *::after {
			content: "";
			position: absolute;
			left: 0;
			right: 0;
			top: -3px; /* sits on the 3px rule the item already has */
			height: 3px;
			background: var(--accent);
			transform: scaleX(0);
			transform-origin: 0 50%;
			opacity: 0;
		}
		.js-motion .s-rv-time.is-in > *::after {
			animation: s-wipe var(--mo-lead) var(--mo-ease-enter) var(--mo-d) backwards;
		}
	}

	/* Quotes. The mark turns in rather than fading, because it is a piece of
	   punctuation and not a photograph, and the attribution arrives after the
	   words it belongs to. */
	.js-motion .s-rv-quotes > * { transform: translateY(var(--mo-rise)) scale(var(--mo-scale-in)); }
	.js-motion .s-rv-quotes > * .s-quote-mark {
		opacity: 0;
		transform: scale(var(--mo-scale-in)) rotate(calc(var(--mo-turn) * -1));
	}
	.js-motion .s-rv-quotes.is-in > * .s-quote-mark {
		opacity: 1;
		transform: none;
		transition: transform var(--mo-lead) var(--mo-ease-press) calc(var(--mo-d) + var(--mo-stagger-tight)),
			opacity var(--mo-enter) linear calc(var(--mo-d) + var(--mo-stagger-tight));
	}
	.js-motion .s-rv-quotes > * figcaption { opacity: 0; }
	.js-motion .s-rv-quotes.is-in > * figcaption {
		opacity: 1;
		transition: opacity var(--mo-enter) var(--mo-ease-enter) calc(var(--mo-d) + var(--mo-stagger));
	}

	/* The questions. A long list, so the tight stagger and a short distance,
	   and nothing at all on an answer until somebody opens it. */
	.js-motion .s-rv-ask > * { transform: translateY(var(--mo-rise-sm)); }

	/*
	 * Opening a question. A details element has no animatable height, and
	 * animating one would be a layout on every frame of it even if it did, so
	 * the answer moves inside the box the browser has already opened. The plus
	 * turns a quarter circle onto the minus rather than one stroke vanishing.
	 */
	.js-motion .s-faq-i[open] .s-faq-a {
		animation: s-answer var(--mo-cross) var(--mo-ease-enter) backwards;
	}
	@keyframes s-answer {
		from {
			opacity: 0;
			transform: translateY(calc(var(--mo-rise-sm) * -1));
		}
	}
	.js-motion .s-faq-x::before,
	.js-motion .s-faq-x::after { transition: transform var(--mo-cross) var(--mo-ease-press); }
	.js-motion .s-faq-i[open] .s-faq-x::after { transform: rotate(90deg); }

	/*
	 * Pricing. The plan most schools pick arrives last, on the press curve, so
	 * it settles into place a beat after the others have stopped. It is the
	 * only piece of choreography on the site that breaks document order, and
	 * it is the only one making an argument.
	 */
	.js-motion .s-rv-plans > *,
	.js-motion .s-rv-peek > * { transform: translateY(var(--mo-rise)) scale(var(--mo-scale-in)); }
	.js-motion .s-rv-plans.is-in > .s-plan-best,
	.js-motion .s-rv-peek.is-in > .s-pk-best {
		transition-duration: var(--mo-lead);
		transition-timing-function: var(--mo-ease-press);
		transition-delay: calc(var(--mo-d) + var(--mo-stagger));
	}
	.js-motion .s-rv-plans > * .s-plan-flag { transform: scale(0); }
	.js-motion .s-rv-plans.is-in > * .s-plan-flag {
		transform: none;
		transition: transform var(--mo-cross) var(--mo-ease-press) calc(var(--mo-d) + var(--mo-cross));
	}

	/*
	 * The comparison table. The frame is there from the start and the rows
	 * fill it in from the top on the tight stagger, because a comparison is a
	 * list of claims and claims read better one at a time than as a block.
	 * Opacity only: a table row that is also being moved drags its own borders
	 * out of line with its neighbours on the way.
	 */
	.js-motion .s-rv-rows > * { transform: none; }

	/* A row of links. The mark on the left lands on the press curve and the
	   arrow on the right leans in from behind itself, so the row reads as
	   something to go through rather than a line of text. */
	.js-motion .s-rv-links > * { transform: translateY(var(--mo-rise-sm)); }
	.js-motion .s-rv-links > * .s-link-mark {
		opacity: 0;
		transform: scale(var(--mo-scale-in));
	}
	.js-motion .s-rv-links.is-in > * .s-link-mark {
		opacity: 1;
		transform: none;
		transition: transform var(--mo-enter) var(--mo-ease-press) calc(var(--mo-d) + var(--mo-stagger-tight)),
			opacity var(--mo-exit) linear var(--mo-d);
	}
	.js-motion .s-rv-links > * .s-link-go {
		opacity: 0;
		transform: translateX(calc(var(--mo-nudge) * -1));
	}
	.js-motion .s-rv-links.is-in > * .s-link-go {
		opacity: 1;
		transform: none;
		transition: transform var(--mo-enter) var(--mo-ease-enter) calc(var(--mo-d) + var(--mo-stagger)),
			opacity var(--mo-enter) linear calc(var(--mo-d) + var(--mo-stagger));
	}

	/* The small stuff: the proof strip under a hero, the three gender cards,
	   the ladder of levels, the per language counts. Little tiles in a row, so
	   a short distance and the tight stagger, and they grow rather than rise. */
	.js-motion .s-rv-fan > * { transform: translateY(var(--mo-rise-sm)) scale(var(--mo-scale-in)); }

	/* One sentence set large. It takes the longest curve on the site, and the
	   highlighter stroke draws under it once it has landed. */
	.js-motion .s-rv-say { transform: translateY(var(--mo-rise)); }
	.js-motion .s-rv-say.is-in { transition-duration: var(--mo-lead); }

	/*
	 * The highlighter stroke behind a marked word. Only marks inside something
	 * that reveals start at zero width: the hero never reveals on scroll, so a
	 * blanket rule here would leave the hero's own highlight collapsed
	 * forever. The hero gets its own load animation in 12.6.
	 */
	.js-motion .s-rv .s-mark::before { transform: scaleX(0); }
	.js-motion .s-rv.is-in .s-mark::before {
		transform: scaleX(1);
		transition: transform var(--mo-lead) var(--mo-ease-enter) var(--mo-stagger);
	}

	/* The closing band. The argument arrives, then the buttons come up under
	   it on the press curve, because they are the only thing on the band
	   anybody is meant to touch. */
	.js-motion .s-rv-cta { transform: translateY(var(--mo-rise)); }
	.js-motion .s-rv-cta:last-child { transform: translateY(var(--mo-rise)) scale(var(--mo-scale-in)); }
	/*
	 * transform: none is repeated here and it is not redundant.
	 *
	 * Every other hiding rule in this section is two classes deep and loses to
	 * .s-rv.is-in, which is three. This one adds a pseudo class to reach for
	 * the buttons, which makes it three as well, and it is further down the
	 * file, so it went on winning after the band had arrived: the buttons on
	 * the closing band of every page on the site stayed pushed down and
	 * shrunk. Found by the check that walks each band after it has arrived and
	 * insists that nothing in it is still moved or still faded.
	 */
	.js-motion .s-rv-cta:last-child.is-in {
		transform: none;
		transition-duration: var(--mo-lead);
		transition-timing-function: var(--mo-ease-press);
		transition-delay: var(--mo-stagger);
	}

	/* The ticker fades in where it is and does not rise: it is full bleed, and
	   a strip that slides up leaves a gap under the band above it. */
	.js-motion .s-rv-mq { transform: none; }

	/* The four sticky steps rise on the tight stagger, and the stage beside
	   them grows in as one piece. */
	.js-motion .s-rv-seq > * { transform: translateY(var(--mo-rise-sm)); }

	/* ---- 12.4 The curve, and the sticky sequence ------------------- */

	/* The forgetting curve draws itself. The dash length comes from the real
	   path length, measured by the script, because it depends on the numbers
	   the scheduler's own formula produced and no stylesheet can know them. */
	.js-motion .s-crv-line { stroke-dashoffset: var(--len, 0); }
	.js-motion .is-in .s-crv-line {
		stroke-dashoffset: 0;
		transition: stroke-dashoffset var(--mo-draw) var(--mo-ease-drift);
	}
	.js-motion .s-crv-dot,
	.js-motion .s-crv-fill { opacity: 0; }
	.js-motion .is-in .s-crv-dot { opacity: 1; transition: opacity var(--mo-enter) linear var(--mo-draw); }
	.js-motion .is-in .s-crv-fill { opacity: 1; transition: opacity var(--mo-draw) linear var(--mo-cross); }

	/*
	 * The sticky sequence hands over rather than cutting.
	 *
	 * Two curves, which is the whole point of having more than one: the panel
	 * on its way out takes the exit curve because it is in the way, and the
	 * panel arriving takes the enter curve because it is the answer. The base
	 * rule is the leaving state, so both fall out of one pair of declarations.
	 */
	.js-motion .s-seq-panel {
		transition: opacity var(--mo-exit) var(--mo-ease-exit),
			transform var(--mo-exit) var(--mo-ease-exit);
	}
	.js-motion .s-seq-panel.is-on {
		transition: opacity var(--mo-cross) var(--mo-ease-enter),
			transform var(--mo-cross) var(--mo-ease-enter);
	}
	.js-motion .s-seq-step {
		transition: opacity var(--mo-cross) var(--mo-ease-enter),
			border-color var(--mo-cross) var(--mo-ease-enter);
	}
	@media (min-width: 901px) {
		.js-motion .s-seq-panel { transform: translateY(var(--mo-rise-sm)) scale(var(--mo-scale-in)); }
		.js-motion .s-seq-panel.is-on { transform: none; }
		/* The rule beside the step that owns the stage grows down its edge, so
		   the handover is marked by something moving rather than by a colour
		   nobody notices changing. */
		.js-motion .s-seq-step { position: relative; }
		.js-motion .s-seq-step::before {
			content: "";
			position: absolute;
			left: -2px; /* over the 2px rule the list already draws */
			top: 0;
			bottom: 0;
			width: 2px;
			background: var(--accent);
			transform: scaleY(0);
			transform-origin: 50% 0;
			transition: transform var(--mo-cross) var(--mo-ease-enter);
		}
		.js-motion .s-seq-step.is-on::before { transform: scaleY(1); }
	}

	/* ---- 12.5 Hover, focus and the press -------------------------- */

	/*
	 * Everything worth pressing gets the same three states, and a keyboard
	 * gets all three. The static half of each one is written
	 * :is(:hover, :focus-visible) in its own section further up, and so are
	 * the lifts below. A mouse seeing something a keyboard does not is a bug,
	 * not a detail.
	 */
	.s-card, .s-bento-i, .s-pk, .s-link, .s-plan, .s-quote, .s-hook {
		transition: transform var(--mo-hover) var(--mo-ease-enter),
			box-shadow var(--mo-hover) var(--mo-ease-enter),
			border-color var(--mo-hover) var(--mo-ease-enter);
	}
	.s-card-link:is(:hover, :focus-visible),
	.s-bento-i:hover,
	.s-pk:is(:hover, :focus-within),
	.s-plan:is(:hover, :focus-within),
	.s-quote:hover { transform: translateY(var(--mo-lift)); }
	.s-link:is(:hover, :focus-visible) { transform: translateX(var(--mo-nudge)); }

	/* The press. Sharp on the way down so the finger is believed, and the
	   press curve on the way back up so it settles rather than stopping. */
	.s-card-link:active,
	.s-link:active,
	.s-pk:active,
	.s-plan:active {
		transform: scale(var(--mo-scale-press));
		transition-duration: var(--mo-press);
		transition-timing-function: var(--mo-ease-exit);
	}
	.s-deck-btn, .s-filter button, .s-faq-i summary, .s-navbtn, .s-nav > a, .s-quiet, .s-skip {
		transition: transform var(--mo-hover) var(--mo-ease-press),
			background-color var(--mo-hover) linear,
			border-color var(--mo-hover) linear,
			color var(--mo-hover) linear;
	}
	.s-deck-btn:is(:hover, :focus-visible),
	.s-filter button:is(:hover, :focus-visible) { transform: translateY(var(--mo-lift-sm)); }
	.s-deck-btn:active,
	.s-filter button:active,
	.s-tok:active,
	.s-faq-i summary:active {
		transform: scale(var(--mo-scale-press));
		transition-duration: var(--mo-press);
		transition-timing-function: var(--mo-ease-exit);
	}

	/* The light that follows the pointer across a card. Two custom properties
	   the script sets from the pointer position, which is why there is no
	   background-position anywhere near this. */
	.s-card-link::before,
	.s-bento-i::before {
		content: "";
		position: absolute;
		inset: 0;
		border-radius: inherit;
		pointer-events: none;
		opacity: 0;
		transition: opacity var(--mo-hover) var(--mo-ease-enter);
		background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 50%), color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%);
	}
	.s-card-link:is(:hover, :focus-visible)::before,
	.s-bento-i:hover::before { opacity: 1; }

	.s-more::after,
	.s-link-go { transition: transform var(--mo-hover) var(--mo-ease-enter); }
	.s-card-link:is(:hover, :focus-visible) .s-more::after,
	.s-link:is(:hover, :focus-visible) .s-link-go { transform: translateX(var(--mo-nudge)); }

	.s-table tbody tr { transition: background-color var(--mo-hover) linear; }
	.s-tok { transition: background-color var(--mo-press) linear, border-color var(--mo-press) linear, transform var(--mo-hover) var(--mo-ease-press); }
	.s-trust-i b { transition: color var(--mo-cross) linear; }
	.s-burger span { transition: transform var(--mo-hover) var(--mo-ease-press), opacity var(--mo-press) linear; }
	.s-panel a { transition: background-color var(--mo-press) linear, color var(--mo-press) linear; }
	.s-deck { transition: transform var(--mo-hover) var(--mo-ease-enter); }
	.s-deck-dots i { transition: transform var(--mo-cross) var(--mo-ease-press), background-color var(--mo-hover) linear; }
	.s-deck-dots i.on { transform: scale(1.35); }
	.s-caret { transition: transform var(--mo-cross) var(--mo-ease-press); }

	/*
	 * The travelling focus ring.
	 *
	 * Only the transform is transitioned. The size is written straight onto the
	 * element with no transition at all, because a width that eases is a
	 * layout on every frame of it, and this is the one element on the site
	 * that moves while somebody is tabbing rather than scrolling.
	 */
	.s-ring {
		transition: transform var(--mo-cross) var(--mo-ease-enter),
			opacity var(--mo-exit) linear;
	}
	.s-ring.is-on { opacity: 1; }
	/* The first ring of a visit does not fly in from the corner of the window.
	   It fades up where it is wanted. */
	.s-ring.is-fresh { transition: opacity var(--mo-exit) linear; }

	/* ---- 12.6 Load, and the handover between routes ---------------- */

	/* The hero writes itself in on load rather than on scroll, because it is
	   already on screen when the page arrives. Revealing it on scroll would
	   mean a blank hero for anybody who lands and does not immediately move. */
	@keyframes s-rise {
		from {
			opacity: 0;
			transform: translateY(var(--mo-rise-sm));
		}
	}
	@keyframes s-pop-in {
		from {
			opacity: 0;
			transform: translateY(var(--mo-rise)) scale(var(--mo-scale-in)) rotate(calc(var(--mo-turn) * -.15));
		}
	}
	.s-hero-copy > * {
		animation: s-rise var(--mo-lead) var(--mo-ease-enter) backwards;
		animation-delay: calc(var(--mo-stagger) * var(--mo-i, 0));
	}
	.s-hero-copy > *:nth-child(2) { --mo-i: 1; }
	.s-hero-copy > *:nth-child(3) { --mo-i: 2; }
	.s-hero-copy > *:nth-child(4) { --mo-i: 3; }
	.s-hero-copy > *:nth-child(5) { --mo-i: 4; }
	.s-hero-copy > *:nth-child(n+6) { --mo-i: 5; }
	.s-hero-visual {
		animation: s-pop-in var(--mo-lead) var(--mo-ease-enter) var(--mo-stagger) backwards;
	}

	/*
	 * The hero's own highlighter.
	 *
	 * Every marked word further down the page has its stroke drawn by the
	 * scroll reveal above, but the hero never reveals on scroll, so until this
	 * rule existed the one highlight at the very top of the site was the only
	 * one that never drew.
	 */
	.s-hero .s-mark::before {
		animation: s-strike var(--mo-lead) var(--mo-ease-enter) var(--mo-enter) backwards;
	}
	@keyframes s-strike {
		from { transform: scaleX(0); }
	}

	/*
	 * Leaving for another route.
	 *
	 * There is no single page router here and there is no build step, so this
	 * is the honest version of a page transition: the page eases out of the
	 * way, the line at the top sweeps to say something is on its way, and the
	 * page that lands runs its own hero choreography as the arrival. Under two
	 * tenths of a second, because it is time added to every navigation, and on
	 * the exit curve so it is gone before anybody looks at it.
	 *
	 * The script will not start this unless motion is allowed, and it puts the
	 * page back if the navigation has not happened shortly afterwards, so a
	 * blocked, cancelled or refused navigation cannot leave a faded page
	 * behind.
	 */
	.js-motion.is-leaving .s-main,
	.js-motion.is-leaving .s-head,
	.js-motion.is-leaving .s-foot {
		opacity: 0;
		transition: opacity var(--mo-page) var(--mo-ease-exit),
			transform var(--mo-page) var(--mo-ease-exit);
	}
	.js-motion.is-leaving .s-main { transform: translateY(calc(var(--mo-rise-sm) * -1)); }
	.js-motion.is-leaving .s-prog {
		opacity: 1;
		animation: s-route var(--mo-lead) var(--mo-ease-enter) infinite;
	}
	@keyframes s-route {
		from { transform: scaleX(0); }
		to { transform: scaleX(1); }
	}

	/* The header. A shadow and a firmer ground once the page has moved, the
	   wordmark settling a touch smaller, and out of the way altogether while
	   somebody is reading downwards. It comes straight back on the first
	   upward scroll, which is when it is wanted, and the script refuses to
	   move it at all while the keyboard is inside it or a menu is open. */
	.s-head {
		transition: box-shadow var(--mo-hover) var(--mo-ease-enter),
			background-color var(--mo-hover) linear,
			transform var(--mo-cross) var(--mo-ease-enter);
	}
	.s-logo { transition: transform var(--mo-cross) var(--mo-ease-enter); }
	.s-head.is-stuck .s-logo { transform: scale(.94); }
	.js-motion .s-head.is-away { transform: translateY(-100%); }

	/* The progress line is driven a frame at a time, so the only thing it
	   needs a transition for is appearing and disappearing. */
	.s-prog { transition: opacity var(--mo-cross) linear; }

	/* The menus arrive rather than appearing. Both are toggled with the hidden
	   attribute, so the keyframe runs again each time one comes back. */
	.s-panel,
	.s-mobile { animation: s-drop var(--mo-cross) var(--mo-ease-enter) backwards; }
	@keyframes s-drop {
		from {
			opacity: 0;
			transform: translateY(calc(var(--mo-rise-sm) * -1));
		}
	}
	.js-motion .s-mobile .s-wrap > * {
		animation: s-rise var(--mo-enter) var(--mo-ease-enter) backwards;
		animation-delay: calc(var(--mo-stagger-tight) * var(--mo-i));
	}

	/* The note that explains a word a reader tapped. */
	.s-gloss-pop { animation: s-note-in var(--mo-cross) var(--mo-ease-press) backwards; }
	@keyframes s-note-in {
		from {
			opacity: 0;
			transform: translateY(calc(var(--mo-nudge) * -1)) scale(var(--mo-scale-in));
		}
	}

	/* The hero deck swapping its card. The script puts is-fresh on the card
	   and takes it off when the animation ends, so the same keyframe runs
	   again on the next step rather than only the first. */
	.js-motion .s-hook-live.is-fresh { animation: s-card-in var(--mo-cross) var(--mo-ease-enter); }
	@keyframes s-card-in {
		from {
			opacity: 0;
			transform: translateY(var(--mo-rise-sm)) rotate(calc(var(--mo-turn) * -.4));
		}
	}

	/*
	 * A form that came back with an answer. Both the confirmation and the
	 * error land on the press curve rather than shaking. A shake is the one
	 * entrance that reliably makes people who are sensitive to movement feel
	 * unwell, and a stated preference for less movement is a setting, not a
	 * medical record: plenty of people who would rather not be shaken have
	 * never touched it.
	 */
	.js-motion .s-formdone,
	.js-motion .s-formbad {
		animation: s-pop-in var(--mo-lead) var(--mo-ease-press) backwards;
	}

	/* ---- 12.7 Pending ---------------------------------------------- */

	/* The turning half of the pending state. The static half, which is what
	   somebody with a motion preference sees, is up in section 5 with the
	   buttons, because a form that has been sent has to look sent either way. */
	@keyframes s-spin {
		to { transform: rotate(1turn); }
	}
	.s-btn.is-busy::before { animation: s-spin var(--mo-spin) linear infinite; }
	.s-f-sending { animation: s-rise var(--mo-cross) var(--mo-ease-enter) backwards; }

	/* ---- 12.8 Ambient, and the tab nobody is looking at ------------ */

	/*
	 * The light behind a hero drifts. Slowly, on transform alone, so it stays
	 * on the compositor and never repaints the text sitting over it. Three
	 * durations out of one token, deliberately not multiples of each other, so
	 * the three blobs never line up into a single pulse.
	 */
	@keyframes s-drift-a {
		50% { transform: translate3d(-7%, 9%, 0) scale(1.14); }
	}
	@keyframes s-drift-b {
		50% { transform: translate3d(10%, -8%, 0) scale(1.2); }
	}
	@keyframes s-drift-c {
		50% { transform: translate3d(-12%, -6%, 0) scale(.88); }
	}
	.s-aurora i:nth-child(1) { animation: s-drift-a var(--mo-ambient) var(--mo-ease-drift) infinite alternate; }
	.s-aurora i:nth-child(2) { animation: s-drift-b calc(var(--mo-ambient) * 1.21) var(--mo-ease-drift) infinite alternate; }
	.s-aurora i:nth-child(3) { animation: s-drift-c calc(var(--mo-ambient) * 1.42) var(--mo-ease-drift) infinite alternate; }

	/* The ticker. Two identical tracks, each sliding left by exactly its own
	   width, so the moment the first one leaves the second is already sitting
	   where it started. Paused on hover so a word can be read, and paused
	   while the keyboard is inside it so a focused link cannot slide out from
	   under somebody. */
	.s-mq-track { animation: s-mq var(--mo-ticker) linear infinite; }
	@keyframes s-mq {
		to { transform: translateX(-100%); }
	}
	.s-marquee:hover .s-mq-track,
	.s-marquee:focus-within .s-mq-track { animation-play-state: paused; }

	/*
	 * Nothing runs where nobody is looking.
	 *
	 * is-still goes on a band the script has watched leave the window, and
	 * is-idle goes on the document when the tab goes to the background.
	 * Between them they cover every animation on this site that would
	 * otherwise run forever, which is two: the drifting light and the word
	 * ticker. Everything else here is finite and ends on its own.
	 *
	 * will-change is handed out here and in one place in the script, and
	 * nowhere else. It goes to the two things that are genuinely animating
	 * this second, and it leaves with the class. A blurred blob promised a
	 * compositor layer for the life of the page holds that memory whether
	 * anybody can see the band it is in or not, and a page with a hero and a
	 * closing band has six of them.
	 */
	.s-aurora.is-live i,
	.s-marquee.is-live .s-mq-track { will-change: transform; }
	.s-aurora.is-still i,
	.s-marquee.is-still .s-mq-track,
	.is-idle .s-aurora i,
	.is-idle .s-mq-track,
	.is-idle .s-btn.is-busy::before { animation-play-state: paused; }

	/* ---- 12.9 The escape hatch ------------------------------------- */

	/*
	 * Why none of the above can hide something from a keyboard.
	 *
	 * Somebody tabbing down a long page reaches a link inside a band that has
	 * not arrived yet. The browser scrolls it into view and gives it focus, and
	 * without these rules focus is now sitting on an element at zero opacity:
	 * to that person the thing they were moving towards has vanished. The
	 * script also marks the band as arrived on focusin, which runs the proper
	 * choreography, so this is the braces and that is the belt.
	 *
	 * Every selector here is narrowed with :not(.is-in). It outweighs the
	 * hiding rules above without an important, and it stops applying the
	 * instant the band has arrived, so it cannot go on to cancel the hover and
	 * press states of whatever is inside the band for as long as focus stays
	 * there. That was the first version of this rule and it quietly took the
	 * lift off every button in a section a keyboard was visiting.
	 */
	.js-motion .s-rv:not(.is-in):focus-within,
	.js-motion .s-rv-group:not(.is-in):focus-within > *,
	.js-motion .s-rv-group:not(.is-in):focus-within > * *,
	.js-motion .s-seq-panel:not(.is-on):focus-within {
		opacity: 1;
		transform: none;
	}
	.js-motion .s-seq-panel:not(.is-on):focus-within { pointer-events: auto; }
}

/* ------------------------------------------------------------------ */
/* 13. Responsive                                                      */
/* ------------------------------------------------------------------ */

@media (max-width: 1080px) {
	.s-foot-top { grid-template-columns: 1fr 1fr 1fr; gap: 34px; }
	.s-foot-brand { grid-column: 1 / -1; }
	.s-plans { grid-template-columns: repeat(2, 1fr); }
	.s-cards-4 { grid-template-columns: repeat(2, 1fr); }
	.s-bento { grid-template-columns: repeat(4, 1fr); }
	.s-bento-w4, .s-bento-w6 { grid-column: span 4; }
	.s-bento-w3 { grid-column: span 2; }
}

@media (max-width: 900px) {
	.s-nav, .s-head-cta { display: none; }
	.s-burger { display: block; margin-left: auto; }
	.s-hero-in, .s-split { grid-template-columns: 1fr; gap: 44px; }
	.s-split-flip .s-split-copy { order: 0; }
	.s-cards-3, .s-cards-2 { grid-template-columns: repeat(2, 1fr); }
	.s-sec { padding: 64px 0; }
	.s-hero { padding: 56px 0 60px; }
	.s-peek { grid-template-columns: 1fr; }
	.s-mq-i { font-size: 22px; padding: 0 18px; }
	.s-time-i { padding-right: 16px; }
}

@media (max-width: 660px) {
	:root { --gut: 18px; }
	body.mnems { font-size: 16.5px; }
	.s-cards-2, .s-cards-3, .s-cards-4, .s-plans { grid-template-columns: 1fr; }
	.s-fgrid, .s-fgrid-inline { grid-template-columns: 1fr; }
	.s-form { padding: 22px; }
	.s-reader { padding: 22px; }
	.s-reader-body { font-size: 18px; }
	.s-steps li { grid-template-columns: 1fr; gap: 12px; }
	.s-foot-top { grid-template-columns: 1fr 1fr; }
	.s-cta-in { flex-direction: column; align-items: flex-start; }
	.s-trust-in { gap: 14px 28px; }
	.s-trust-i b { font-size: 22px; }
	.s-stat b { font-size: 38px; }
	.s-hookgrid { grid-template-columns: 1fr; }
	.s-mobile { inset-block-start: 66px; }
	.s-head-in { min-height: 66px; }
	.s-bento { grid-template-columns: 1fr; gap: 14px; }
	.s-bento-i, .s-bento-w3, .s-bento-w4, .s-bento-w6 { grid-column: span 1; }
	.s-time { grid-template-columns: 1fr; }
	.s-time-i { border-top: 0; border-left: 3px solid var(--line); padding: 14px 0 14px 22px; }
	.s-time-i::before { top: 16px; left: -8px; }
	.s-st-h { font-size: clamp(28px, 8vw, 40px); }
	.s-marquee { padding: 14px 0; }
	.s-mq-i { font-size: 20px; }
	.s-aurora i { filter: blur(48px); }
}

@media print {
	.s-head, .s-mobile, .s-foot, .s-cta, .s-burger, .s-marquee, .s-aurora, .s-grain, .s-prog { display: none !important; }
	body.mnems { background: #fff; }
	.s-seq-panel { opacity: 1 !important; }
}

/* The drawing on a hook card. The app owns the symbol set; the site only has
   to give it somewhere to sit and the two colour roles it draws with. */
.s-hook-art {
	margin: 10px 0 12px;
	border-radius: 12px;
	overflow: hidden;
}
.s-hook-art .mnem-art {
	--art-tint: var(--accent);
	--art-on-tint: #17093A;
	display: block;
	width: 100%;
	height: auto;
	color: var(--text);
}
.s-hook-art .mnem-art.is-m  { --art-tint: #F0663C; }
.s-hook-art .mnem-art.is-f  { --art-tint: #2E86E0; }
.s-hook-art .mnem-art.is-n  { --art-tint: #5FC2D8; }
.s-hook-art .mnem-art.is-pl { --art-tint: #8A6BE8; }
.s-hook-art .mnem-art-ground { fill: color-mix(in srgb, var(--text) 5%, transparent); }
.s-hook-art .mnem-art-halo   { fill: color-mix(in srgb, var(--art-tint) 10%, transparent); }
.s-hook-art .mnem-art-plate  { fill: color-mix(in srgb, var(--art-tint) 20%, transparent); }
.s-hook-art .mnem-art-cue-disc { fill: var(--art-tint); }
.s-hook-art .mnem-art-cue-ink  { fill: #FFFDF9; }
.s-hook-art .mnem-art-cap {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .14em;
	text-transform: uppercase;
	fill: var(--faint);
}
.s-hook-art .mnem-art-mark {
	font-size: 86px;
	font-weight: 800;
	letter-spacing: -.03em;
	fill: color-mix(in srgb, var(--text) 55%, transparent);
}
