/*
 * The Crypto Secret — the shared motion layer (Story 1.2).
 *
 * SCOPE. Only the primitives every later screen consumes: the scroll reveal,
 * the marquee pair, the button shimmer, the wordmark sweep, and the
 * .btn / .card / .row hover transitions. The page-specific animations in
 * design/canvas/_head.html — blob, tilt, ring, the orbits, twinkle, chip, cue,
 * draw, dash, grow, pulseD, scan — belong to stories 4, 6 and 9 and are
 * deliberately NOT here. `typeIn` is not here and never will be: it animates
 * `width`, which the hard limit forbids, and no class in any artboard uses it.
 *
 * `sweep` (renamed `tcs-sweep`) WAS on that excluded list until story 2.6,
 * which claims it for the closing-band wordmark — see the hard limit note
 * below for why `background-position` is the one deliberate exception to
 * opacity/transform-only.
 *
 * THE REVEAL IS 26px / 900ms. The artboards contradict each other — ten boards
 * including Main.dc.html define `rise` at translateY(26px)/900ms, while five
 * (DesignSystem, Checkout, CheckoutMobile, FAQ, HomeMobile) define `fade` at
 * 18px/800ms — so "the artboards win" cannot arbitrate on its own. 26px/900ms
 * is what Main.dc.html (the page this story serves), the majority of boards,
 * tokens.md, and the --tcs-reveal-distance / --tcs-dur-reveal tokens already
 * shipped by story 1.1 all agree on. DesignSystem.dc.html's whole motion block
 * is stale — do not build from it.
 *
 * HARD LIMIT: only opacity and transform animate. Never width, height, top or
 * left. Two documented exceptions are hover affordances, not scroll motion,
 * and are drawn exactly as the artboards draw them: .btn/.card transition
 * box-shadow, and .row:hover shifts padding-left.
 *
 * A THIRD, DELIBERATE EXCEPTION: the closing wordmark's `tcs-sweep` animates
 * `background-position`. `transform` cannot move light through a
 * `background-clip: text` gradient — the gradient is painted onto the text's
 * own background layer, and translating the ELEMENT would move the clipped
 * text with it rather than sweep the gradient across a stationary word. This
 * is the one non-opacity/transform keyframe in the file; see .tcs-wordmark
 * below (section 3.5) for where it is scoped.
 *
 * NO SCROLL LIBRARY. No AOS, no GSAP, no ScrollMagic, no Locomotive, no
 * jQuery. Reveals are IntersectionObserver only — js/reveal.js.
 *
 * Every value below comes from a --tcs-* token. Nothing is hardcoded: a
 * literal here is a value epic 2 can no longer change in one place.
 */

/* ===========================================================================
 * 1. SCROLL REVEAL
 *
 * The DEFAULT STATE IS VISIBLE. .tcs-reveal on its own paints nothing and
 * hides nothing; only the .tcs-js class — which js/reveal.js sets on <html>
 * as its very first act — arms the hidden start state.
 *
 * That inversion is the whole no-JavaScript story. If the script is blocked,
 * fails to parse, or IntersectionObserver is missing, .tcs-js is never set,
 * the rule below never matches, and every reveal target renders at full
 * opacity. Content is NEVER stranded at opacity: 0. The naive form — hiding
 * in CSS and revealing in JS — makes a broken script an invisible page.
 * ========================================================================= */

.tcs-js .tcs-reveal {
	opacity: 0;
	transform: translateY(var(--tcs-reveal-distance));
	/*
	 * Transition rather than animation: the reveal must be triggered by the
	 * observer adding a class, and it must be able to arrive at its final
	 * value with no keyframe running under prefers-reduced-motion.
	 */
	transition:
		opacity var(--tcs-dur-reveal) var(--tcs-ease-reveal),
		transform var(--tcs-dur-reveal) var(--tcs-ease-reveal);
	transition-delay: var(--tcs-reveal-delay, 0ms);
	will-change: opacity, transform;
}

/*
 * FADE-ONLY VARIANT (2026-09-05). A container of many tap targets (the FAQ
 * list) reveals with opacity alone: while a translateY transition runs on a
 * composited ancestor, children measure at 43.99994px instead of 44px, and
 * the 44px tap floor is a hard gate (verify-mobile.spec.js). No transform,
 * no artefact.
 */
.tcs-js .tcs-reveal.tcs-reveal--fade {
	transform: none;
}

.tcs-js .tcs-reveal.tcs-revealed {
	opacity: 1;
	/*
	 * `transform: none`, NOT `translateY(0)`. An element can be both a reveal
	 * target and a hover-lift card — the harness has three of them — and this
	 * rule is more specific than `.tcs-card:hover`, so a concrete
	 * translateY(0) here wins the cascade and pins the card to its resting
	 * position: the hover lift silently stops working once the element has
	 * revealed. `none` is the same visual result and leaves the hover
	 * transform free to apply.
	 */
	transform: none;
	will-change: auto;
}

/*
 * Hand the transition back to whatever else the element is.
 *
 * The reveal rule sets `transition: opacity, transform` on .tcs-reveal, which
 * REPLACES the transition .tcs-card and .tcs-btn declare — so a revealed card
 * animates its lift but snaps its shadow, and a revealed row loses its indent
 * transition entirely. Restating the combined transition for the overlap keeps
 * both behaviours instead of letting the last-declared one win.
 */
.tcs-js .tcs-reveal.tcs-card {
	transition:
		opacity var(--tcs-dur-reveal) var(--tcs-ease-reveal),
		transform var(--tcs-dur-card-hover) var(--tcs-ease-reveal),
		box-shadow var(--tcs-dur-card-hover) ease;
	/*
	 * RESTATE THE DELAY. `transition` is a shorthand, so it resets
	 * transition-delay to 0s — silently dropping the per-element stagger
	 * reveal.js wrote into --tcs-reveal-delay. The cards still revealed, all
	 * at once, and the only visible symptom was choreography that had
	 * quietly stopped happening.
	 */
	transition-delay: var(--tcs-reveal-delay, 0ms);
}

.tcs-js .tcs-reveal.tcs-btn {
	transition:
		opacity var(--tcs-dur-reveal) var(--tcs-ease-reveal),
		transform var(--tcs-dur-btn-hover) var(--tcs-ease-reveal),
		box-shadow var(--tcs-dur-btn-hover) ease,
		background-color var(--tcs-dur-btn-hover) ease,
		color var(--tcs-dur-btn-hover) ease;
	transition-delay: var(--tcs-reveal-delay, 0ms);
}

/*
 * The keyframe form of the same motion, for elements that should play on load
 * rather than on scroll (the hero). `both` holds the final state after the
 * run, so nothing snaps back. This is `rise` from _head.html:18 verbatim.
 */
@keyframes tcs-rise {

	from {
		opacity: 0;
		transform: translateY(var(--tcs-reveal-distance));
	}

	to {
		opacity: 1;
		transform: translateY(0);
	}
}

.tcs-rise {
	animation: tcs-rise var(--tcs-dur-reveal) var(--tcs-ease-reveal) both;
	animation-delay: var(--tcs-reveal-delay, 0ms);
}

/* ===========================================================================
 * 2. MARQUEE
 *
 * _head.html:31-32. The track holds its content TWICE and travels exactly
 * -50%, so the second copy lands where the first began and the loop is
 * seamless. Any other travel distance visibly jumps.
 *
 * STRUCTURE: the label sits OUTSIDE the scrolling track. A label inside the
 * loop repeats mid-scroll and reads as a rendering bug — epic-1-context.md
 * names this explicitly. .tcs-marquee is the labelled row, .tcs-marquee__view
 * the clipping viewport (the hover target that pauses), and
 * .tcs-marquee__track the only thing that moves.
 * ========================================================================= */

@keyframes tcs-marq {

	from {
		transform: translateX(0);
	}

	to {
		transform: translateX(var(--tcs-marquee-travel));
	}
}

@keyframes tcs-marq-reverse {

	from {
		transform: translateX(var(--tcs-marquee-travel));
	}

	to {
		transform: translateX(0);
	}
}

.tcs-marquee {
	display: flex;
	align-items: center;
	width: 100%;
	max-width: 100%;
}

.tcs-marquee__label {
	flex: 0 0 auto;
	font-size: var(--tcs-fs-body-sm);
	color: var(--tcs-muted);
}

.tcs-marquee__view {
	flex: 1 1 auto;
	min-width: 0; /* without this a max-content track blows the flex item out */
	overflow: hidden;
}

.tcs-marquee__track {
	display: flex;
	align-items: center;
	width: max-content;
	/*
	 * pointer-events stay live so links inside a moving track remain
	 * clickable — the track pauses on hover, so a link is reachable.
	 */
	animation: tcs-marq var(--tcs-dur-marquee-assets) linear infinite;
}

.tcs-marquee__track--reverse {
	animation-name: tcs-marq-reverse;
}

/* The three documented speeds (tokens.md, "Marquees"). */
.tcs-marquee__track--topics {
	animation-duration: var(--tcs-dur-marquee-topics);
}

.tcs-marquee__track--reviews-a {
	animation-duration: var(--tcs-dur-marquee-reviews-a);
}

.tcs-marquee__track--reviews-b {
	animation-duration: var(--tcs-dur-marquee-reviews-b);
}

/*
 * Pause on hover, and on keyboard focus reaching anything inside. Hover alone
 * leaves a keyboard user chasing a moving link.
 */
.tcs-marquee__view:hover .tcs-marquee__track,
.tcs-marquee__view:focus-within .tcs-marquee__track {
	animation-play-state: paused;
}

/* ===========================================================================
 * 3. BUTTON
 *
 * _head.html:66-70. .tcs-btn is the base; --dark and --orange are the two
 * skins, which invert on hover. box-shadow is transitioned here on purpose —
 * see the header note.
 * ========================================================================= */

.tcs-btn {
	position: relative;
	overflow: hidden;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* Minimum tap target, inline links included (tokens.md, 44px). */
	min-height: var(--tcs-tap-min);
	padding: 11px 20px;
	border: 0;
	border-radius: var(--tcs-radius);
	font-family: var(--tcs-font);
	font-size: var(--tcs-fs-body-sm);
	font-weight: var(--tcs-fw-semibold);
	text-decoration: none;
	cursor: pointer;
	transition:
		transform var(--tcs-dur-btn-hover) var(--tcs-ease-reveal),
		box-shadow var(--tcs-dur-btn-hover) ease,
		background-color var(--tcs-dur-btn-hover) ease,
		color var(--tcs-dur-btn-hover) ease;
}

/* Same cascade problem, same fix — see the note on .tcs-card:hover. */
.tcs-btn:hover,
.tcs-reveal.tcs-revealed.tcs-btn:hover {
	transform: translateY(var(--tcs-btn-hover-lift));
}

/* Never outline: none without a replacement — this IS the replacement. */
.tcs-btn:focus-visible {
	outline: 2px solid var(--tcs-orange);
	outline-offset: 3px;
}

.tcs-btn--dark {
	background: var(--tcs-black);
	color: var(--tcs-cream);
}

.tcs-btn--dark:hover {
	background: var(--tcs-orange);
	color: var(--tcs-black);
	box-shadow: var(--tcs-btn-hover-shadow-dark);
}

.tcs-btn--orange {
	background: var(--tcs-orange);
	color: var(--tcs-black);
}

.tcs-btn--orange:hover {
	background: var(--tcs-black);
	color: var(--tcs-orange);
	box-shadow: var(--tcs-btn-hover-shadow-orange);
}

/*
 * GeneratePress prints `button:hover, button:focus { background: #3f4047;
 * color: #fff }` inline, at (0,1,1). On a <button class="tcs-btn--dark">
 * that outranks the single-class colour above, so a focused Log in / Send
 * message / Pay button went grey. Pin the resting colours on :focus and
 * restate the hover so the two states stay distinct.
 */
button.tcs-btn--dark:focus {
	background: var(--tcs-black);
	color: var(--tcs-cream);
}

button.tcs-btn--dark:hover,
button.tcs-btn--dark:focus:hover {
	background: var(--tcs-orange);
	color: var(--tcs-black);
}

button.tcs-btn--orange:focus {
	background: var(--tcs-orange);
	color: var(--tcs-black);
}

button.tcs-btn--orange:hover,
button.tcs-btn--orange:focus:hover {
	background: var(--tcs-black);
	color: var(--tcs-orange);
}

/*
 * The shimmer sweep. A decorative <span> inside the button, clipped by the
 * button's own overflow: hidden. It is aria-hidden in markup and carries no
 * text, so it never reaches the accessibility tree.
 */
@keyframes tcs-shimmer {

	from {
		transform: translateX(var(--tcs-shimmer-from));
	}

	to {
		transform: translateX(var(--tcs-shimmer-to));
	}
}

.tcs-btn__shimmer {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	width: var(--tcs-shimmer-width);
	pointer-events: none;
	background: linear-gradient(100deg, transparent, var(--tcs-shimmer-highlight), transparent);
	animation: tcs-shimmer var(--tcs-dur-shimmer) ease-in-out infinite;
}

/* ===========================================================================
 * 3.5. WORDMARK SWEEP (Story 2.6)
 *
 * _head.html:37,63-64 (`@keyframes sweep` / `.wm`), ported and renamed to the
 * `tcs-` namespace. `background-clip: text` cuts the gradient to the glyphs'
 * own shape, so animating `background-position` reads as light travelling
 * through the letters — the one deliberate exception to the hard limit above,
 * because `transform` cannot reach inside a clipped background the way it can
 * move a whole element.
 * ========================================================================= */

@keyframes tcs-sweep {

	from {
		background-position: var(--tcs-sweep-from);
	}

	to {
		background-position: var(--tcs-sweep-to);
	}
}

.tcs-wordmark {
	background: var(--tcs-wordmark-gradient);
	background-size: var(--tcs-sweep-size);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	animation: tcs-sweep var(--tcs-dur-sweep) linear infinite;
}

/*
 * code-review fix, 2026-09-04: no fallback existed for a browser that lacks
 * background-clip: text support. `color: transparent` above has nothing to
 * fall back to in that case — the gradient never clips to the glyphs, so the
 * closing band's brand statement renders as blank space where the word
 * should be. This restores a solid, readable colour for exactly that case:
 * --tcs-black, the colour .tcs-band--orange already pairs with every other
 * element on this band (sections.css:127-130), so an unsupported browser
 * reads the word as if it had simply taken the band's own body colour. The
 * sweep is switched off too — animating background-position behind solid,
 * un-clipped text has no visible effect, so there is nothing to gain from
 * leaving it running.
 */
@supports not ( (background-clip: text) or (-webkit-background-clip: text) ) {

	.tcs-wordmark {
		background: none;
		color: var(--tcs-black);
		animation: none;
	}
}

/* ===========================================================================
 * 4. CARD
 *
 * _head.html:71-72. Lift plus shadow, 340ms.
 * ========================================================================= */

.tcs-card {
	transition:
		transform var(--tcs-dur-card-hover) var(--tcs-ease-reveal),
		box-shadow var(--tcs-dur-card-hover) ease;
}

/*
 * SPECIFICITY, deliberately raised with an explicit duplicated selector.
 *
 * (code-review fix, 2026-09-04: this comment used to say ":where() at zero
 * cost" — it does not use :where(), and could not: :where() always
 * contributes zero specificity, which would defeat the fix below rather than
 * perform it. Corrected to describe what the rule actually does.)
 *
 * An element is routinely both a reveal target and a card. The revealed state
 * is `.tcs-js .tcs-reveal.tcs-revealed` — three classes — so a plain
 * `.tcs-card:hover` (two) LOSES the cascade and the lift never applies once
 * the card has revealed. The bug is invisible on first paint and appears only
 * after scrolling, which is the worst way to find it.
 *
 * The fix is an explicit second selector carrying the revealed classes, so it
 * outranks the rule that pinned the transform. The plain `.tcs-card:hover`
 * stays for cards that are not reveal targets.
 */
.tcs-card:hover,
.tcs-card:focus-within,
.tcs-reveal.tcs-revealed.tcs-card:hover,
.tcs-reveal.tcs-revealed.tcs-card:focus-within {
	transform: translateY(var(--tcs-card-hover-lift));
	box-shadow: var(--tcs-card-hover-shadow);
}

/* ===========================================================================
 * 5. ROW
 *
 * _head.html:74-75. The padding-left shift is the second documented
 * exception; it is a hover affordance on a list row, not scroll motion.
 * ========================================================================= */

.tcs-row {
	transition:
		background-color var(--tcs-dur-btn-hover) ease,
		padding-left var(--tcs-dur-row-hover) var(--tcs-ease-reveal);
}

.tcs-row:hover,
.tcs-row:focus-within {
	background: var(--tcs-row-hover-bg);
	padding-left: var(--tcs-row-hover-indent);
}

/* ===========================================================================
 * 6. REDUCED MOTION
 *
 * Every animation resolves to its FINAL value — never zero, never invisible,
 * never zero-height. The blanket `transform: none` that _head.html uses is
 * correct for the reveal (whose final transform IS none) and WRONG for the
 * marquee, which gets its own treatment below.
 * ========================================================================= */

/*
 * PHONE REVEALS ARE QUICKER AND TRAVEL LESS (mobile pass, 2026-09-05). At
 * 390px a viewport is short and a thumb-flick moves it fast; a 900ms /
 * 26px rise that reads as graceful on a 1440 screen reads as "the section
 * hasn't loaded" on a phone. Same easing, shorter distance and duration.
 */
@media (max-width: 768px) {
	:root {
		--tcs-dur-reveal: 620ms;
		--tcs-reveal-distance: 16px;
	}
}

@media (prefers-reduced-motion: reduce) {

	/*
	 * The reveal, at its destination. Both the transition form and the
	 * keyframe form, and both regardless of whether the observer ever fired
	 * — under reduced motion the element must be visible on arrival, not on
	 * scroll.
	 *
	 * NOT SCOPED TO .tcs-js, deliberately, and this is the subtle part.
	 * reveal.js removes .tcs-js on the reduced-motion path, but it can only
	 * do that at DOMContentLoaded — and between the stylesheet parsing and
	 * that moment the element is already sitting at opacity 0 with a 900ms
	 * transition armed. Scoping this rule to .tcs-js would mean the class
	 * comes off and the element then TRANSITIONS up from 0, which is
	 * measurable as a partial opacity and visible as a fade — motion, for a
	 * user who asked for none. Matching whether or not the class is present
	 * makes opacity 1 true from the first paint.
	 */
	.tcs-reveal,
	.tcs-reveal.tcs-revealed,
	.tcs-rise {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
		animation: none !important;
	}

	/*
	 * MARQUEE — the one place the blanket rule is actively wrong.
	 *
	 * `transform: none` parks a doubled track at translateX(0), which leaves
	 * the entire second copy off-screen to the right: that is the animation's
	 * START, not its final value. The right reduced-motion treatment is a
	 * static, fully readable strip. So the track stops animating, drops to a
	 * single row's worth of content, wraps instead of running at max-content,
	 * and the duplicate copy is removed from both the layout and the
	 * accessibility tree — it was always redundant, and with no motion it is
	 * only a repeat.
	 */
	.tcs-marquee__track {
		animation: none !important;
		transform: none !important;
		/*
		 * `width: auto` alone is not enough: the track is a flex ITEM inside
		 * .tcs-marquee__view, and its surviving copy still measures wider
		 * than the viewport, so the row centres and the strip hangs off the
		 * left edge. Pinning it to the full width and starting its content at
		 * the beginning is what makes "static" also mean "readable from the
		 * first word".
		 */
		width: 100%;
		max-width: 100%;
		flex-wrap: wrap;
		justify-content: flex-start;
	}

	/*
	 * The surviving copy is itself a flex row sized to its content and
	 * carrying the track's scroll-in padding. Let it wrap and drop the
	 * leading indent, or the first chip starts 48px in and the last one
	 * overflows.
	 */
	.tcs-marquee__track > * {
		flex-wrap: wrap;
		max-width: 100%;
		padding-left: 0 !important;
	}

	.tcs-marquee__view {
		overflow: visible;
	}

	/*
	 * !important is load-bearing here, not decoration. The duplicate copy
	 * carries an inline `display: flex` (the track is a flex row, and the
	 * harness sets it on the element), and an inline declaration outranks any
	 * selector. Without !important the copy stays laid out, the strip is
	 * twice the viewport wide, and half of it sits off-screen — which is the
	 * exact "parked at its start value" failure this whole block exists to
	 * prevent.
	 */
	.tcs-marquee__dup {
		display: none !important;
	}

	/*
	 * The shimmer's final value is translateX(240%) — fully swept past the
	 * button and invisible. Neither the start nor the end is meaningful
	 * static content: the sweep is pure decoration over a button that is
	 * already legible. Hide it rather than park a translucent white bar
	 * across the label.
	 */
	.tcs-btn__shimmer {
		animation: none !important;
		display: none;
	}

	/*
	 * THE WORDMARK RESOLVES TO ITS FINAL SWEPT POSITION, not its start. Unlike
	 * the shimmer above, the wordmark is not decoration over already-legible
	 * text — the gradient IS the text's only paint, since `color: transparent`
	 * leaves nothing behind once `animation: none` freezes it. Landing on
	 * `--tcs-sweep-to` (the keyframe's own `to` value) is what keeps the word
	 * fully rendered rather than parked at a mid-sweep position that happens
	 * to still be legible only by coincidence of the gradient's stops.
	 */
	.tcs-wordmark {
		animation: none !important;
		background-position: var(--tcs-sweep-to);
	}

	/*
	 * Hover affordances lose their movement but keep their colour change, so
	 * the element still confirms it is interactive.
	 */
	.tcs-btn,
	.tcs-card,
	.tcs-row {
		transition-property: background-color, color, box-shadow;
	}

	.tcs-btn:hover,
	.tcs-card:hover,
	.tcs-card:focus-within {
		transform: none;
	}

	.tcs-row:hover,
	.tcs-row:focus-within {
		padding-left: 0;
	}
}
