/*
 * The Crypto Secret — the asset marquee and the noise-into-order band
 * (Story 2.2).
 *
 * SOURCE OF TRUTH. design/canvas/Main.dc.html:206-263 for desktop and
 * design/canvas/HomeMobile.dc.html:78-107 for mobile. Where the two disagree
 * they are not in conflict: mobile is a DESIGNED STATE, not a reflow. The
 * asset strip loses its motion and two of its six items; the illustration
 * turns from a 1360x230 horizontal spine into a 330x300 vertical one with five
 * stops; and the body copy is rewritten rather than rewrapped. Both variants
 * ship in the served HTML at every width and a media query displays exactly
 * one — the precedent hero.css:462 set with .tcs-hero__mobile-assurance.
 * wp_is_mobile() was rejected: under full-page caching the first visitor's
 * device decides what everyone sees, and it does not respond to a resize.
 *
 * ───────────────────────────────────────────────────────────────────────────
 * THE ONE THING THIS FILE MUST NOT GET WRONG
 * ───────────────────────────────────────────────────────────────────────────
 *
 * #55524B (--tcs-body) NEVER APPEARS ON A DARK GROUND. It measures 2.51:1
 * against #0C0C0D and fails WCAG AA's 4.5:1 for body text; DESIGN.md:181
 * records this exact pairing as "a real defect caught in review". The artboard
 * at Main.dc.html:261-262 nonetheless ships it on the illustration's two
 * baseline captions. The artboards win on a visual conflict; they do not win
 * on an accessibility failure they are documented as containing. Those two
 * captions are --tcs-muted here (#8B877D, 5.46:1) — passing AA while staying
 * quieter than the body copy, so they keep the subordinate weight the board
 * intended. --tcs-body-dark (7.88:1) would also pass but reads at full body
 * weight and would flatten the hierarchy.
 *
 * AND THE REASON THIS FILE HAS TO SAY SO AT ALL: sections.css:122-125 already
 * sets `color: var(--tcs-body-dark)` on .tcs-band--black, which covers every
 * paragraph in the band. That safety net DOES NOT REACH SVG. CSS `color` does
 * not inherit into `fill` — an SVG <text> with no fill of its own paints
 * BLACK, not the inherited colour — so every text node in both illustrations
 * sets `fill` explicitly, and the rules that do it are grouped together below
 * so none can be lost in a later edit.
 *
 * THE EXCEPTION THAT IS NOT A DEFECT: --tcs-dim (#6E6A62) on the headline's
 * setup line measures 3.63:1. That fails AA for body text and PASSES the 3:1
 * threshold for large text — the line is 54px on desktop and 34px semibold on
 * mobile, both of which are "large" by the WCAG definition. It is correct and
 * must not be "fixed" to --tcs-muted; any contrast gate has to scope itself to
 * paragraphs and SVG text and exclude the h2.
 *
 * ───────────────────────────────────────────────────────────────────────────
 *
 * CONSUMES, DOES NOT RE-AUTHOR. The bands come from sections.css; the marquee
 * and the reveal come from motion.css; every value below is a --tcs-* token
 * from tokens.css. There is no literal colour, no literal duration and no
 * literal easing in this file. The values that had no name — the flow line's
 * dash geometry and the five panel colours — were added to tokens.css first,
 * in the Story 2.2 block there.
 *
 * DEPARTS FROM THE ARTBOARD ON PURPOSE (2026-09-04): the illustration panel
 * now plays a one-shot entrance when it reveals (section 3d) and carries a
 * spark riding the rail. The board's figure read as an empty corner beside a
 * row of discs; the sequence is what makes "noise into order" legible.
 *
 * HARD LIMIT, inherited: only opacity and transform animate, never width,
 * height, top or left. THE ONE DOCUMENTED EXCEPTION is the flow line's
 * `stroke-dashoffset`, which is the only way to express a dashed line
 * travelling along its own path and is the exact thing the artboard draws
 * (_head.html:33 `@keyframes dash`). Its cost is bounded to a single 750px
 * path. Every animation here has a prefers-reduced-motion state that resolves
 * to its FINAL value — section 6 at the foot of this file.
 */

/* ===========================================================================
 * 1. THE ASSET STRIP  —  Main.dc.html:206-225
 *
 * A pair of hairlines across the full width, a static label in a 300px column,
 * and a scrolling track beside it. NOT a padded band: it takes .tcs-band for
 * the ground and the type stack and overrides the padding to zero, because the
 * 22px vertical inset belongs to the label and the track individually — the
 * hairlines have to meet the viewport edges, and band padding would inset them.
 * ========================================================================= */

.tcs-assets {
	padding-top: 0;
	padding-bottom: 0;
	padding-inline: 0;
	border-top: var(--tcs-border-width) solid var(--tcs-border);
	border-bottom: var(--tcs-border-width) solid var(--tcs-border);
	/*
	 * The track runs at width: max-content and is deliberately wider than the
	 * viewport. Clipping it HERE rather than leaving it to .tcs-bands upstream
	 * is what keeps body.scrollWidth equal to window.innerWidth: a section
	 * that overflows and is rescued by a wrapper is one refactor away from
	 * scrolling the whole document sideways.
	 */
	overflow: hidden;
}

/*
 * THE GRID GOES ON .tcs-marquee, NOT ON THE WRAPPER AROUND IT.
 *
 * Main.dc.html:207 makes the label and the scrolling viewport the two COLUMNS
 * of one grid, and they are siblings inside .tcs-marquee. Putting
 * grid-template-columns on .tcs-assets__desktop instead would give the grid a
 * single child — .tcs-marquee itself — which lands in the 300px first column
 * and squeezes the label AND the track into it. The symptom is not an obvious
 * one: the label still renders and the track still animates, so every
 * assertion about classes, colours and motion passes, while the viewport
 * measures 5px wide and the strip appears empty. Measured, not assumed.
 *
 * .tcs-assets__desktop stays as a plain wrapper because it is the element the
 * mobile media query toggles — the swap must not have to also undo a display
 * mode.
 *
 * `minmax(0, 1fr)` on the second track, never a bare 1fr: a grid item's
 * default min-width is auto, meaning "as wide as my content", and the content
 * here is a max-content marquee track. A bare 1fr would size the column to the
 * whole track and push the page sideways — the exact overflow the I/O matrix
 * forbids.
 */
.tcs-assets__desktop > .tcs-marquee {
	display: grid;
	grid-template-columns: 300px minmax(0, 1fr);
	align-items: center;
}

/*
 * The label. 13px, against motion.css:177's 15px default for
 * .tcs-marquee__label — Main.dc.html:208 draws it at 13. This override is the
 * concrete reason noise.css must be enqueued AFTER motion.css; emitted before
 * it, the two rules tie on specificity and the earlier one loses.
 */
.tcs-assets__label {
	font-size: 13px;
	color: var(--tcs-muted);
	line-height: var(--tcs-lh-body);
}

.tcs-marquee__label.tcs-assets__label {
	padding: 22px 24px 22px var(--tcs-section-x);
	border-right: var(--tcs-border-width) solid var(--tcs-border);
}

.tcs-assets__view {
	padding-block: 22px;
	/*
	 * The fade at the right edge (Main.dc.html:209). It exists so items do not
	 * appear to be cut off by a hard edge — the strip should read as
	 * continuing past the viewport rather than as being clipped. -webkit- is
	 * still required by Safari, which has not unprefixed mask-image.
	 *
	 * WRITTEN AS AN ALPHA, NOT AS A COLOUR. The artboard's stop is `#000`,
	 * but in a mask only the alpha channel is read — `#000` there means
	 * "fully opaque", not "black", and nothing in this gradient is ever
	 * painted. Spelling it rgb(0 0 0 / 1) says what it actually is, and keeps
	 * a value that is not a design colour out of the palette a reader (or the
	 * gate) scans for hardcoded colours.
	 */
	-webkit-mask-image: linear-gradient(90deg, rgb(0 0 0 / 1) 84%, transparent);
	mask-image: linear-gradient(90deg, rgb(0 0 0 / 1) 84%, transparent);
}

/*
 * NO ANIMATION DECLARED HERE. .tcs-marquee__track already runs at
 * --tcs-dur-marquee-assets (40s) by default (motion.css:197), which is exactly
 * the asset speed tokens.md fixes. Restating it would create a second place
 * for the two to drift.
 *
 * ONE COPY MUST BE AT LEAST AS WIDE AS THE VIEWPORT IT SCROLLS THROUGH, and
 * this is the condition the -50% travel quietly depends on. MEASURED: six
 * items at their natural width come to 869px, two copies to 1738px, and the
 * viewport beside a 300px label at 1440 is 1140px. Half a track is 869px — so
 * at the end of each 40s cycle only 869px of content remains to the right of
 * the view's left edge and the last 271px of the strip is BLANK. It reappears
 * every cycle, which reads as the strip having run out rather than as a loop.
 *
 * The failure is invisible to almost every check that matters: the classes are
 * right, the colours are right, the track genuinely animates and genuinely
 * pauses, and the page does not overflow. Only measuring the trailing gap
 * across a full cycle finds it.
 *
 * THE FIX IS MORE ITEMS, NOT A WIDER BOX, and the two are not the same thing.
 *
 * A `min-width` on the copy does make each half of the track wide enough — but
 * it widens the BOX, not the content, so the extra width is empty space after
 * the sixth item. The blank moves; it does not go away. (Measured both ways:
 * `min-width: 100%` resolves circularly against the max-content track and
 * closes the gap at 1440 while reopening it at 2560; `100vw` widens the copy
 * to 1440px and leaves 571px of slack inside it.)
 *
 * What actually fills a viewport is items. The six assets are repeated to
 * twelve per copy, so one copy measures ~1738px against the 1140px viewport
 * beside the 300px label — comfortably wider, with the margin holding to past
 * 3440px. The repetition is invisible: the strip is a loop, and a reader
 * seeing Bitcoin twice within one pass sees exactly what they would see one
 * cycle later anyway.
 *
 * THE -50% TRAVEL IS UNAFFECTED, which is the constraint that governs
 * everything here. It requires the track to hold its content twice — two
 * COPIES, whatever each copy contains — and it still does. What changed is
 * what one copy is, not how many there are, so `.tcs-marquee__dup` still names
 * exactly one of two and the loop still lands the second copy precisely where
 * the first began.
 */
.tcs-assets__copy {
	display: flex;
	align-items: center;
	gap: 48px;                  /* Main.dc.html:210 */
	padding-left: 48px;
	flex: 0 0 auto;
}

.tcs-assets__item {
	display: flex;
	align-items: center;
	gap: 9px;                   /* Main.dc.html:211 */
	white-space: nowrap;
}

.tcs-assets__name {
	font-size: 16px;
	font-weight: var(--tcs-fw-semibold);
	color: var(--tcs-black);
}

.tcs-assets__item svg {
	flex: 0 0 auto;
}

/* The mobile strip is hidden at desktop; the media query at section 5 swaps
 * the pair. Both are in the served HTML at every width. */
.tcs-assets__mobile {
	display: none;
}

/* ===========================================================================
 * 2. THE NOISE BAND  —  Main.dc.html:227-263
 *
 * A black band with the two-tone headline on the left, the body copy on the
 * right, and the illustration panel across the full width beneath both.
 * ========================================================================= */

.tcs-noise {
	/*
	 * The blob is positioned 160px above this band's own top edge and 560px
	 * wide from 36% across. Clipping it here is the same decision the hero
	 * takes about its own aurora (hero.css:48) and for the same reason.
	 */
	overflow: hidden;
	isolation: isolate;
}

/* --- 2a. The single aurora blob (Main.dc.html:229) ----------------------- */

.tcs-noise__decor {
	position: absolute;
	inset: 0;
	pointer-events: none;
	overflow: hidden;
}

.tcs-noise__blob {
	position: absolute;
	top: -160px;
	left: 36%;
	width: 560px;
	height: 560px;
	border-radius: var(--tcs-radius-full);
	background: radial-gradient(
		circle,
		color-mix(in srgb, var(--tcs-orange) 16%, transparent) 0%,
		color-mix(in srgb, var(--tcs-orange) 0%, transparent) 66%
	);
	/* code-review fix, 2026-09-04: was a hardcoded 32px; now the token this
	 * story's Always rule requires (see tokens.css, --tcs-blob-blur-3). */
	filter: blur(var(--tcs-blob-blur-3));
	pointer-events: none;
}

/* The band's own content sits above the decoration. */
.tcs-noise__inner {
	position: relative;
	z-index: 1;
}

/* --- 2b. The headline / copy pair (Main.dc.html:230) --------------------- */

.tcs-noise__lede {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: 70px;                  /* Main.dc.html:230 */
	align-items: start;
}

/*
 * THE TWO-TONE HEADLINE. The tracking is load-bearing, not decoration: the
 * ratio of letter-spacing to font size is what the design is, and at 54px a
 * default-tracked headline reads as a different website. Both come from the
 * shipped tokens (-0.040em at 54px desktop, -0.038em at 34px mobile), so the
 * ratio is exact by construction rather than by a hand-computed pixel value.
 */
.tcs-noise__headline {
	margin: 0;
	font-size: var(--tcs-fs-h2);
	line-height: var(--tcs-lh-h2);
	letter-spacing: var(--tcs-ls-h2);
	font-weight: var(--tcs-fw-semibold);
	/*
	 * The payoff half inherits --tcs-cream from sections.css:142-144. Stated
	 * nowhere here on purpose: a second declaration would be a second place
	 * for it to go wrong.
	 */
}

/*
 * The setup line. --tcs-dim at 3.63:1 is CORRECT — see the file header. It
 * passes the 3:1 large-text threshold and its whole job is to sit back so the
 * payoff line lands.
 */
.tcs-noise__setup {
	color: var(--tcs-dim);
}

.tcs-noise__copy {
	display: flex;
	flex-direction: column;
	gap: 18px;                  /* Main.dc.html:231 */
	padding-top: 8px;
}

/*
 * NO COLOUR HERE. .tcs-band--black resolves these paragraphs to
 * --tcs-body-dark already (sections.css:122-125). Declaring it again would be
 * a second place for the failing #55524B to be introduced by an edit that
 * only looked at this file.
 *
 * The artboard sets line-height 1.68 against --tcs-lh-body's 1.66. The token
 * wins: the drift is sub-pixel at 17px and token consistency is worth more
 * than reproducing a rounding artefact.
 */
.tcs-noise__copy p {
	margin: 0;
	font-size: var(--tcs-fs-body);
	line-height: var(--tcs-lh-body);
}

/* The mobile copy variant is hidden at desktop; section 5 swaps the pair. */
.tcs-noise__mobile-copy {
	display: none;
}

/* ===========================================================================
 * 3. THE ILLUSTRATION PANEL  —  Main.dc.html:242-263
 *
 * A rounded, bordered surface holding the caption row and the figure. The
 * five colours below have no name in tokens.md and were added to tokens.css
 * first; none of them is ever a text colour.
 * ========================================================================= */

.tcs-noise__panel {
	position: relative;
	margin-top: 62px;           /* Main.dc.html:242 */
	border: var(--tcs-border-width) solid var(--tcs-border-dark);
	border-radius: var(--tcs-radius-lg);
	background: linear-gradient(160deg, var(--tcs-panel-grad-from), var(--tcs-panel-grad-to));
	overflow: hidden;
}

.tcs-noise__panel-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 22px 30px;         /* Main.dc.html:244 */
	border-bottom: var(--tcs-border-width) solid var(--tcs-panel-divider);
}

/*
 * The two caption labels. The artboard tracks these at .15em against the
 * shipped eyebrow token's .13em; the token wins for the same reason the
 * line-height does — one eyebrow treatment across the site is worth more than
 * a 0.02em difference nobody can see at 12px.
 */
.tcs-noise__panel-label {
	font-size: var(--tcs-fs-eyebrow);
	font-weight: var(--tcs-fw-semibold);
	letter-spacing: var(--tcs-ls-eyebrow);
	text-transform: var(--tcs-tt-eyebrow);
	color: var(--tcs-dim);
}

.tcs-noise__panel-label--accent {
	color: var(--tcs-orange);
}

/*
 * The figure. `display: block` because an inline SVG sits on the text
 * baseline and leaves a few pixels of the panel gradient below it, which reads
 * as a misaligned border. `height: auto` with the viewBox is what lets the
 * 1360-wide drawing scale down inside a narrower panel instead of overflowing
 * the band — the I/O matrix forbids exactly that overflow.
 */
.tcs-noise__figure {
	display: block;
	width: 100%;
	height: auto;
}

/* --- 3a. The noise: dots and tangled threads ----------------------------- */

/*
 * The cloud is drawn at just under three-quarter strength. It is what the
 * reader is LEAVING BEHIND, so it must sit back from the ordered spine — but
 * the artboard's half-strength #2A2A2C threads measured so faint on a real
 * screen that the corner read as empty, which is the opposite of noise. The
 * threads now take --tcs-flow-line and the dots --tcs-muted; the opacity token
 * on the group is what keeps the whole tangle subordinate.
 */
.tcs-noise__cloud {
	opacity: var(--tcs-thread-opacity);
}

/*
 * EVERY DRAWN LINE IN THIS FIGURE CARRIES pathLength="1", and this pair of
 * declarations is its resting state: a dash exactly one path long, at offset
 * zero, is a fully drawn line. The entrance (section 3d) animates the offset
 * from 1 to 0 — a line drawing itself — and no rule ever needs a path's true
 * length. The chapter miniatures established the precedent for stroke drawing
 * (chapters.css, --tcs-dur-svg-draw); this is the same documented exception
 * to "only opacity and transform", bounded to seven short paths.
 */
.tcs-noise__thread,
.tcs-noise__join,
.tcs-noise__rail,
.tcs-noise__spine {
	fill: none;
	stroke-linecap: round;
	stroke-linejoin: round;
	stroke-dasharray: 1;
	stroke-dashoffset: 0;
}

.tcs-noise__thread {
	stroke: var(--tcs-flow-line);
	stroke-width: 1.2;
}

/*
 * The pulsing dots. Opacity only for the loop — a dot that also drifted would
 * be one more thing travelling in a picture whose whole point is that the left
 * half does not go anywhere. The pulse phase comes from --tcs-noise-pulse-delay
 * on the element, applied NEGATIVE so each dot starts mid-cycle rather than
 * waiting its delay out at full opacity.
 *
 * transform-box: fill-box is what makes `scale()` on an SVG circle grow from
 * its own centre instead of from the SVG's origin.
 */
.tcs-noise__dot {
	fill: var(--tcs-muted);
	transform-box: fill-box;
	transform-origin: center;
	animation:
		tcs-noise-pop var(--tcs-dur-noise-pop) var(--tcs-ease-reveal) both,
		tcs-noise-pulse var(--tcs-dur-pulse) ease-in-out infinite;
	animation-delay:
		calc(var(--tcs-noise-enter-dots) + var(--tcs-noise-i, 0) * var(--tcs-noise-stagger)),
		calc(-1 * var(--tcs-noise-pulse-delay, 0s));
}

@keyframes tcs-noise-pulse {

	0%,
	100% {
		opacity: var(--tcs-pulse-opacity-from);
	}

	50% {
		opacity: 1;
	}
}

/* --- 3b. The joins, the resolve node, the rail and the flow ------------- */

/*
 * The two joins are SOLID now, not the artboard's 4/6 dash: a dashed join
 * beside a dashed flow line made two different dashes mean two different
 * things, and a line that draws itself in already says "tentative, then
 * settled" without the texture.
 */
.tcs-noise__join {
	stroke: var(--tcs-flow-line);
	stroke-width: 1.4;
}

/*
 * THE RESOLVE NODE. The only orange left of the rail — a hollow ring on a soft
 * halo — so the eye lands on the moment the noise becomes order before it
 * follows the rail right.
 */
.tcs-noise__resolve {
	transform-box: fill-box;
	transform-origin: center;
}

.tcs-noise__resolve-glow {
	fill: var(--tcs-orange);
	opacity: var(--tcs-noise-glow-opacity);
}

.tcs-noise__resolve-ring {
	fill: none;
	stroke: var(--tcs-orange);
	stroke-width: 1.5;
}

/* The rail: the solid line the eight stops sit on. It draws in once. */
.tcs-noise__rail {
	stroke: var(--tcs-flow-line);
	stroke-width: 2;
}

/*
 * THE FLOW LINE — the artboard's travelling dash, now in orange at low opacity
 * so the movement reads as energy along the rail rather than as a second grey
 * line. `stroke-dashoffset` is the documented exception to "only opacity and
 * transform": it is the only property that can express a dash travelling along
 * its own path. One 776px path, one property, no layout.
 *
 * The offset is NEGATIVE so the dashes travel left to right — the direction
 * the eight chapters are read. -160 is a whole number of 14px (5 + 9) dash
 * cycles, which is what makes the loop seamless instead of jumping.
 */
.tcs-noise__flow {
	stroke: var(--tcs-orange);
	stroke-width: 2;
	fill: none;
	opacity: .42;
	stroke-dasharray: var(--tcs-flow-dash);
	animation:
		tcs-noise-fade var(--tcs-dur-noise-fade) ease both,
		tcs-noise-dash var(--tcs-dur-flow) linear infinite;
	animation-delay: var(--tcs-noise-enter-flow), 0s;
}

@keyframes tcs-noise-dash {

	to {
		stroke-dashoffset: var(--tcs-flow-offset);
	}
}

/*
 * THE SPARK. One orange point riding the rail from the resolve node to the
 * eighth chapter, on a loop — the illustration's argument made kinetic: this
 * is the path, and it goes one way. Transform and opacity only. It rests at
 * opacity 0 because it IS motion: a spark parked at either end would read as
 * a ninth stop.
 */
.tcs-noise__spark {
	opacity: 0;
	animation: tcs-noise-spark var(--tcs-dur-spark) ease-in-out infinite;
	animation-delay: var(--tcs-noise-enter-spark);
}

.tcs-noise__spark-glow {
	fill: var(--tcs-orange);
	opacity: var(--tcs-noise-spark-glow);
}

.tcs-noise__spark-core {
	fill: var(--tcs-orange);
}

@keyframes tcs-noise-spark {

	0% {
		transform: translateX(0);
		opacity: 0;
	}

	7% {
		opacity: 1;
	}

	93% {
		opacity: 1;
	}

	100% {
		transform: translateX(var(--tcs-noise-rail-travel));
		opacity: 0;
	}
}

/* The mobile spine draws in exactly like the rail. */
.tcs-noise__spine {
	stroke: var(--tcs-flow-line);
	stroke-width: 2;
}

/* --- 3c. The stops --------------------------------------------------------- */

/*
 * Each stop is a soft halo, an orange disc, a numeral and a label. The halo is
 * what gives the disc depth against the panel, and it brightens on hover — the
 * one bit of interactivity the figure has, and it costs an opacity transition.
 */
.tcs-noise__stop-halo {
	fill: var(--tcs-orange);
	opacity: var(--tcs-noise-halo-opacity);
	transition: opacity var(--tcs-dur-btn-hover) ease;
}

.tcs-noise__stop-g:hover .tcs-noise__stop-halo {
	opacity: var(--tcs-noise-halo-opacity-hover);
}

.tcs-noise__stop {
	fill: var(--tcs-orange);
}

.tcs-noise__stop-halo,
.tcs-noise__stop {
	transform-box: fill-box;
	transform-origin: center;
}

/*
 * EVERY TEXT NODE SETS ITS FILL. CSS `color` does not inherit into `fill`, so
 * .tcs-band--black's colour never reaches these — an SVG <text> with no fill
 * of its own paints black, invisible on this ground. The three rules below are
 * grouped so the set can be read at a glance and none quietly lost.
 *
 * font-family is set too, and for a related reason: SVG text does not inherit
 * the band's font stack in every engine, and the fallback is the UA's serif
 * default. A serif numeral in an orange disc is a visible defect.
 */
.tcs-noise__stop-number,
.tcs-noise__stop-label,
.tcs-noise__caption {
	font-family: var(--tcs-font);
}

/* The numeral sits on the orange disc, so it takes the ground colour. */
.tcs-noise__stop-number {
	font-size: 12px;
	font-weight: var(--tcs-fw-bold);
	fill: var(--tcs-black);
}

.tcs-noise__stop-number--mobile {
	font-size: 11px;
}

/*
 * The stop labels are the eight chapter names — the one thing this figure
 * exists to say — so they are cream (16.1:1) at 13px medium, not the 12.5px
 * body-dark the artboard drew. Body-dark passed AA but sat at the same weight
 * as the captions, and a reader could not tell the chapters from the notes.
 */
.tcs-noise__stop-label {
	font-size: 13px;
	font-weight: var(--tcs-fw-medium);
	fill: var(--tcs-cream);
	transform-box: fill-box;
	transform-origin: center;
}

.tcs-noise__stop-label--mobile {
	font-size: 13.5px;
}

/*
 * THE TWO CAPTIONS THE ARTBOARD GETS WRONG. Main.dc.html:261-262 sets
 * fill="#55524B", measured at 2.51:1 against this ground and failing AA;
 * DESIGN.md:181 records that exact pairing as a defect caught in review.
 * --tcs-muted is 5.46:1 — it passes, and it is still quieter than the body
 * copy, so the captions keep their intended subordinate weight.
 */
.tcs-noise__caption {
	font-size: 12px;
	fill: var(--tcs-muted);
}

/* --- 3d. THE ENTRANCE ------------------------------------------------------ */

/*
 * The figure plays once, keyed off the panel's own reveal. .tcs-noise__panel
 * is a .tcs-reveal target, so reveal.js adds .tcs-revealed to it when it
 * scrolls into view; every animation below is declared in the base rules with
 * `both` fill and a tokenised delay, and this rule simply holds them PAUSED
 * until that class arrives. The sequence:
 *
 *   0.08s  the fourteen dots pop, 45ms apart
 *   0.22s  the three threads draw through them
 *   1.05s  the two joins draw toward one point
 *   1.45s  the resolve node pops
 *   1.56s  the rail draws left to right (1s for 776px)
 *   1.64s  stop 1 pops; each next stop 135ms later, as the rail reaches it
 *   2.70s  the flow's travelling dash fades in over the rail
 *   3.10s  the spark makes its first run
 *
 * THE DEFAULT STATE IS FINAL. Without .tcs-js nothing is paused, the sequence
 * plays from page load and has long finished by the time anyone scrolls here;
 * under reduced motion every animation is removed (section 6) and each element
 * simply sits at the value the base rules declare.
 */
.tcs-js .tcs-noise__panel:not(.tcs-revealed) .tcs-noise__figure * {
	animation-play-state: paused;
}

.tcs-noise__thread {
	animation: tcs-noise-draw var(--tcs-dur-noise-draw) var(--tcs-ease-draw) both;
	animation-delay: calc(var(--tcs-noise-enter-threads) + var(--tcs-noise-i, 0) * var(--tcs-noise-thread-stagger));
}

.tcs-noise__join {
	animation: tcs-noise-draw var(--tcs-dur-noise-fade) var(--tcs-ease-draw) both;
	animation-delay: var(--tcs-noise-enter-joins);
}

.tcs-noise__resolve {
	animation: tcs-noise-pop var(--tcs-dur-noise-pop) var(--tcs-ease-reveal) both;
	animation-delay: var(--tcs-noise-enter-resolve);
}

.tcs-noise__rail {
	animation: tcs-noise-draw var(--tcs-dur-noise-rail) var(--tcs-ease-draw) both;
	animation-delay: var(--tcs-noise-enter-rail);
}

.tcs-noise__stop-halo,
.tcs-noise__stop {
	animation: tcs-noise-pop var(--tcs-dur-noise-pop) var(--tcs-ease-reveal) both;
	animation-delay: calc(var(--tcs-noise-enter-stops) + var(--tcs-noise-i, 0) * var(--tcs-noise-stop-stagger));
}

.tcs-noise__stop-label {
	animation: tcs-noise-rise var(--tcs-dur-noise-fade) var(--tcs-ease-reveal) both;
	animation-delay: calc(var(--tcs-noise-enter-stops) + var(--tcs-noise-i, 0) * var(--tcs-noise-stop-stagger) + var(--tcs-noise-enter-dots));
}

.tcs-noise__caption {
	animation: tcs-noise-fade var(--tcs-dur-noise-fade) ease both;
	animation-delay: var(--tcs-noise-enter-flow);
}

/* The numeral rides its disc: it is in the same group but a separate node, so
 * it needs the same pop or it would float over an empty panel until the disc
 * arrived. */
.tcs-noise__stop-number {
	transform-box: fill-box;
	transform-origin: center;
	animation: tcs-noise-pop var(--tcs-dur-noise-pop) var(--tcs-ease-reveal) both;
	animation-delay: calc(var(--tcs-noise-enter-stops) + var(--tcs-noise-i, 0) * var(--tcs-noise-stop-stagger));
}

@keyframes tcs-noise-pop {

	from {
		transform: scale(0);
	}

	65% {
		transform: scale(1.16);
	}

	to {
		transform: none;
	}
}

/* `from` only: the `to` is whatever the element's own rule declares, so the
 * flow settles at its .42 and the captions at 1 without restating either. */
@keyframes tcs-noise-fade {

	from {
		opacity: 0;
	}
}

@keyframes tcs-noise-rise {

	from {
		opacity: 0;
		transform: translateY(6px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

@keyframes tcs-noise-draw {

	from {
		stroke-dashoffset: 1;
	}

	to {
		stroke-dashoffset: 0;
	}
}

/* The mobile panel and its figure are hidden at desktop; section 5 swaps. */
.tcs-noise__panel-mobile {
	display: none;
}

/* ===========================================================================
 * 4. THE TEXT ALTERNATIVE
 *
 * Both figures are aria-hidden — a screen reader reading loose <text> nodes
 * out of a scatter plot produces word salad, and the eight labels would arrive
 * with no statement of what they are labels FOR. The illustration still
 * carries real information (all eight chapters, in order), so that information
 * is stated once as a sentence instead. GeneratePress ships .screen-reader-text
 * (main.css:485); this rule only removes the paragraph's margins so it cannot
 * affect the panel's layout even if the utility class were ever unavailable.
 * ========================================================================= */

.tcs-noise__alt {
	margin: 0;
}

/* ===========================================================================
 * 5. MOBILE  —  HomeMobile.dc.html:78-107
 *
 * The swap, not a reflow. 768px is the breakpoint sections.css and hero.css
 * both already use, so the three files change state together — a section that
 * switched at a different width would show a desktop illustration inside
 * mobile band padding.
 * ========================================================================= */

@media (max-width: 768px) {

	/* --- 5a. The asset strip becomes a static wrapped list -------------- */

	.tcs-assets__desktop {
		display: none;
	}

	.tcs-assets__mobile {
		display: flex;
		flex-direction: column;
		gap: 14px;                                    /* HomeMobile.dc.html:79 */
		padding: 22px var(--tcs-section-x-mobile);
	}

	/*
	 * 12.5px — HomeMobile.dc.html:80. Below --tcs-fs-body-sm but above the
	 * 12px interface floor the accessibility criteria set, and it is a caption
	 * rather than interface text.
	 */
	.tcs-assets__label--mobile {
		font-size: 12.5px;
		padding: 0;
		border-right: 0;
	}

	.tcs-assets__mobile-row {
		display: flex;
		flex-wrap: wrap;
		gap: 20px;                                    /* HomeMobile.dc.html:80 */
	}

	/*
	 * .66 opacity — HomeMobile.dc.html:81. It applies to the whole item, mark
	 * and word together, and it is the board's way of seating the strip behind
	 * the price block above it. The words are --tcs-black at .66 on cream,
	 * which is still comfortably past AA.
	 */
	.tcs-assets__item--mobile {
		gap: 7px;
		opacity: .66;
	}

	.tcs-assets__item--mobile .tcs-assets__name {
		font-size: 14px;
	}

	/* --- 5b. The noise band stacks -------------------------------------- */

	/*
	 * One column, not two at half width. At 390px a 70px gap between two
	 * columns leaves each about 125px wide, which is roughly seven characters
	 * per line.
	 */
	.tcs-noise__lede {
		grid-template-columns: minmax(0, 1fr);
		gap: 22px;                                    /* HomeMobile.dc.html:92 */
	}

	.tcs-noise__headline {
		font-size: var(--tcs-fs-h2-mobile);
		line-height: var(--tcs-lh-h2-mobile);
		letter-spacing: var(--tcs-ls-h2-mobile);
	}

	.tcs-noise__copy {
		padding-top: 0;
		gap: 16px;                                    /* HomeMobile.dc.html:93 */
	}

	/*
	 * 16px rather than the 17px body token — HomeMobile.dc.html:93. Mobile is
	 * a designed state and the board sets its own body size here; the
	 * line-height still comes from --tcs-lh-body, against the board's 1.66,
	 * which happens to agree exactly.
	 */
	.tcs-noise__copy p {
		font-size: 16px;
	}

	/* --- 5c. The copy and illustration variants swap -------------------- */

	.tcs-noise__desktop-copy {
		display: none;
	}

	/*
	 * `display: inline` on the headline's setup span, not `block`: it is a
	 * phrase inside a sentence and the <br> after it supplies the break.
	 * Making it a block would add a line box of its own and open a gap between
	 * the two halves of one headline.
	 */
	span.tcs-noise__mobile-copy {
		display: inline;
	}

	div.tcs-noise__mobile-copy {
		display: flex;
	}

	/*
	 * The desktop panel's chrome goes with the desktop figure. What remains is
	 * the mobile panel: a plain --tcs-panel surface with its own label, which
	 * is what HomeMobile.dc.html:97 draws.
	 */
	.tcs-noise__panel {
		margin-top: 36px;                             /* HomeMobile.dc.html:97 */
		border-radius: var(--tcs-radius-md);
		background: var(--tcs-panel);
		padding: 24px var(--tcs-section-x-mobile);
	}

	.tcs-noise__panel-head,
	.tcs-noise__figure--desktop {
		display: none;
	}

	.tcs-noise__panel-mobile {
		display: block;
	}

	/*
	 * The mobile label is --tcs-muted, not --tcs-dim: HomeMobile.dc.html:98
	 * draws it at #8B877D. At 12px it is interface text, where --tcs-dim's
	 * 3.63:1 would fail — the large-text exemption that makes --tcs-dim
	 * correct on the 34px headline does not reach a 12px label.
	 */
	.tcs-noise__panel-mobile .tcs-noise__panel-label {
		display: block;
		color: var(--tcs-muted);
	}

	.tcs-noise__figure--mobile {
		margin-top: 18px;                             /* HomeMobile.dc.html:99 */
	}

	/*
	 * No cloud to wait for at this width: the spine starts drawing at the
	 * dots' slot and each stop pops one 64px pitch of spine later.
	 */
	.tcs-noise__spine {
		animation: tcs-noise-draw var(--tcs-dur-noise-rail) var(--tcs-ease-draw) both;
		animation-delay: var(--tcs-noise-enter-dots);
	}

	.tcs-noise__figure--mobile .tcs-noise__stop-halo,
	.tcs-noise__figure--mobile .tcs-noise__stop,
	.tcs-noise__figure--mobile .tcs-noise__stop-number {
		animation-delay: calc(var(--tcs-noise-enter-threads) + var(--tcs-noise-i, 0) * var(--tcs-noise-stop-stagger-mobile));
	}

	.tcs-noise__figure--mobile .tcs-noise__stop-label {
		animation-delay: calc(var(--tcs-noise-enter-threads) + var(--tcs-noise-i, 0) * var(--tcs-noise-stop-stagger-mobile) + var(--tcs-noise-enter-dots));
	}
}

/* ===========================================================================
 * 6. REDUCED MOTION
 *
 * Every animation above resolves to its FINAL value — never to zero, never to
 * invisible, never to a gap. The marquee's own reduced-motion treatment lives
 * in motion.css:418-462 and already does the right thing (the track stops,
 * wraps, and the duplicate copy leaves the layout); the two rules below are
 * for the two animations this file adds.
 *
 * NOT SCOPED TO .tcs-js, following motion.css:379-401 and hero.css:1307 for
 * the same reason: reveal.js can only remove that class at DOMContentLoaded,
 * and anything scoped to it would spend the interval before that at its start
 * value — which is motion, for a reader who asked for none.
 * ========================================================================= */

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

	/*
	 * The dots rest FULLY VISIBLE. Their animation runs from opacity .25 to 1
	 * and back, so "stop it" without pinning the opacity leaves them at
	 * whatever frame the compositor last computed — a cloud at a quarter
	 * strength, which is dimmer than the design and reads as a rendering
	 * fault.
	 */
	.tcs-noise__dot {
		animation: none !important;
		opacity: 1 !important;
	}

	/*
	 * THE FLOW LINE — the one place `animation: none` alone is a bug.
	 *
	 * Stopping the dash animation leaves the line at dasharray 5 9: a dotted
	 * line with two thirds of its length missing. That is the animation's
	 * TEXTURE, not its final value, and the texture only reads as a line
	 * because it is moving. Clearing the dasharray resolves it to what the
	 * motion was expressing all along — one continuous path from the noise to
	 * the eighth chapter.
	 */
	.tcs-noise__flow {
		animation: none !important;
		stroke-dasharray: none !important;
		stroke-dashoffset: 0 !important;
	}

	/*
	 * THE ENTRANCE AND THE SPARK. Every base rule in section 3 declares the
	 * element's FINAL value (a drawn line, a full-size disc, a visible label),
	 * and the keyframes only ever supply the start; removing the animation is
	 * therefore the whole treatment. The one exception is the spark, which
	 * rests at opacity 0 on purpose — it is motion and nothing else, and parked
	 * at either end of the rail it would read as a ninth stop.
	 */
	.tcs-noise__thread,
	.tcs-noise__join,
	.tcs-noise__rail,
	.tcs-noise__spine,
	.tcs-noise__resolve,
	.tcs-noise__stop-halo,
	.tcs-noise__stop,
	.tcs-noise__stop-number,
	.tcs-noise__stop-label,
	.tcs-noise__caption,
	.tcs-noise__spark {
		animation: none !important;
	}

	/*
	 * The strip's mask is not an animation, but under reduced motion the
	 * static track no longer scrolls out from under it — motion.css:447 makes
	 * .tcs-marquee__view overflow visible and the track wrap. A fade over a
	 * wrapped, stationary list hides the last item of every row behind a
	 * gradient for no reason, so it comes off.
	 */
	.tcs-assets__view {
		-webkit-mask-image: none;
		mask-image: none;
	}

	/*
	 * THE THREE-PASS REPEAT, TRIMMED BACK TO ONE (code-review fix,
	 * 2026-09-04). tcs_asset_marquee_copy() in front-page.php prints the six
	 * assets three times per copy — see its own doc comment — so the
	 * ANIMATED strip never runs a blank tail at wide viewports. motion.css
	 * already drops the whole SECOND copy (`.tcs-marquee__dup`) under
	 * reduced motion, but nothing trimmed the surviving copy's three passes
	 * back to one, so a reduced-motion visitor saw Bitcoin, Ethereum, Tether,
	 * Altcoins, Wallets and Exchanges three times over — statically and
	 * permanently. Passes 2 and 3 are exactly items 7 through 18 of the
	 * eighteen printed per copy; hide them once the strip stops moving.
	 */
	.tcs-assets__copy:not(.tcs-marquee__dup) .tcs-assets__item:nth-child(n+7) {
		display: none !important;
	}
}
