/**
 * BACTRONIZING. The full-viewport mold-and-spray demonstration.
 *
 * REWRITTEN 2026-09-17 FOR THE SHADER, on the plan in
 * docs/plans/2026-09-17-shader-mold-and-spray.md. The colony and the plume
 * are drawn by assets/js/bactronize.js on one WebGL canvas; nothing organic
 * is a picture any more and nothing organic is in this file. What this
 * stylesheet owns is the overlay's box, the canvas that fills it, the slate
 * (the word BACTRONIZING on its black bar, the one CSS animation left), the
 * dock with the cue and the button, lite mode, and reduced motion.
 *
 * THE BEATS. GROWTH: the head bootstrap puts .bx-grow on <html> at parse
 * time; the renderer starts on that class and reads --bx-grow below as its
 * clock; view.js shows the dock when the renderer reports the colony has
 * reached SHOW_AT of the field, and adds .is-grown at the clock. CLEARING:
 * the press adds .is-clearing; the renderer fires the plume and eats the
 * colony behind it; the slate slams in at 260ms, knocks once, flickers once,
 * holds and swells out by 2.4s. Teardown at the renderer's FOG_MS.
 *
 * TO SEE A CHANGE WITHOUT DEPLOYING IT: tools/design-loop/spore-lab.mjs, in
 * Chrome (--throttle 6) and in WebKit (--engine webkit). WebKit is the bar.
 */

/* THE CURE'S COLOUR, 2026-09-18. Surgical mint, chosen by a two-seat design
   pass (brand and accessibility) with measured contrast: 7.69:1 as text on
   the ink plate (AAA), 4.38:1 as a border against the mold's olive, 3.09:1
   against its viridian cords (the only candidate that clears 3:1 against
   every mold green), split-complement to the site's red, and outside the
   mold's own hue range, so a dim Low Power screen still tells cure from
   colony. The green it replaces, #4AD96A, sat inside that hue range and was
   near-triadic to the red. Edward: "the green color used isn't the right
   green." One token, every site. */
:root {
	--bx-cure: #5FE0B5;
	--bx-cure-hover: #7CE6C2;
	/* THE BRAND'S OWN RED, 2026-09-18, for the one place the brand colour
	   lives in this animation, the BACTRONIZING slate. It is the logo's fill,
	   #FF0800 (assets/brand/bactronix-logo.svg), not the site's button red
	   #BB2B1E, which is that red darkened to sit on white. The slate had
	   #FF3B21, a hot orange-red that predated the tokens; Edward: "the red in
	   the ... animation is maybe the wrong red." On black the logo red is
	   5.28:1, which a word that size clears with room. */
	--bx-brand-red: #FF0800;
}

/* THE CLOCK. The renderer reads this off #bx-mold at start; view.js reads
   it too, so the two cannot disagree. The default exists so the stylesheet
   is whole on its own. */
#bx-mold,
.bx-mold {
	position: fixed;
	inset: 0;
	z-index: 99990;
	overflow: hidden;
	pointer-events: none;
	--bx-grow: 14500ms;
}

/* Twice, deliberately. No level and no state makes this subtree clickable. */
#bx-mold *,
.bx-mold * {
	pointer-events: none;
}

/* THE CANVAS. It fills the overlay; its backing store is the renderer's
   business (CSS size times a scale it chooses and can lower). The markup
   gives it width and height of 1 so a parsed-but-not-yet-drawn canvas is
   a 1x1 transparent pixel stretched to nothing visible, never a white box. */
.bx-mold__gl {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
}

/* ===================================================================== THE SLATE */

/* BACTRONIZING, the receipt for the press. It fires with the jet at 260ms,
   slams in, knocks once, flickers once, holds to 1.6s and swells out by
   2.4s: gone before mid-clear, because the process is the shot.

   LAID OUT AT LOAD, SHOWN BY OPACITY, like every actor in the clear used to
   be: display:none to flex on the press cost a 400ms frame in the lab,
   because a screen-sized element was laid out and rasterised on the frame
   the visitor pressed. At opacity 0 it costs nothing to keep. */
.bx-mold__flash {
	position: absolute;
	inset: 0;
	display: flex;
	opacity: 0;
	/* Its own stacking context, so the band's z-index: -1 lands behind the
	   word and nowhere else. */
	isolation: isolate;
	/* Promoted at load, so the word's text layer is rasterised while nobody is
	   watching rather than on the frame of the press. */
	will-change: transform, opacity;
	align-items: center;
	justify-content: center;
	color: var(--bx-brand-red, #FF0800);
	font-family: var(--bx-ff, 'Archivo', 'Archivo Fallback', Arial, sans-serif);
	font-size: clamp(2.1rem, 11.6vw, 9.5rem);
	font-stretch: var(--bx-w-board, 100%);
	font-weight: 800;
	line-height: 1;
	letter-spacing: -0.012em;
	text-transform: uppercase;
	white-space: nowrap;
}

/* THE BLACK BAR, 2026-09-17, Edward: "Put a black bar behind the
   BACTRONIZING text and make it all caps and add the rcircle." A solid band
   across the whole viewport, 1.56em tall, drawn as a pseudo-element behind
   the text: a solid colour on a short box rasterises in nothing. It is a
   hard edge over the plume, and the slate's later place in the markup puts
   it over the canvas, so the mist blooms against the bar rather than through
   it. */
.bx-mold__flash::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	height: 1.56em;
	margin-top: -0.78em;
	background: #000;
	z-index: -1;
}

.bx-mold.is-clearing .bx-mold__flash {
	animation: bx-flash 2.2s linear 0.26s both;
	will-change: transform, opacity;
}

/* The registered mark, raised and small, as a pseudo-element: the string
   itself stays "Bactronizing" so the markup test and the screen-reader rule
   hold. Same treatment the button's mark gets. */
.bx-mold.is-clearing .bx-mold__flash::after {
	content: "\00AE";
	font-size: 0.34em;
	font-weight: 700;
	line-height: 1;
	/* A flex item, not inline text (the parent is display: flex). A relative
	   offset raises it against the word it belongs to. */
	position: relative;
	top: -0.55em;
	margin-left: 0.06em;
	letter-spacing: 0;
}

/* Transform and opacity only. The slam arrives wide and tracks in (a
   horizontal scale, which reads as letter-spacing and costs no layout), the
   knock is a 2px jolt on the frame after it lands, then one flicker, a hold,
   and the swell out. Percentages of 2.2s. */
@keyframes bx-flash {
	0%    { opacity: 0;    transform: translate3d(0, 0.35em, 0) scale(1.14, 1.0); animation-timing-function: cubic-bezier(0.1, 0.9, 0.2, 1); }
	3.6%  { opacity: 1;    transform: none; }
	5.4%  { opacity: 1;    transform: translate3d(0, -2px, 0); }
	7.2%  { opacity: 1;    transform: none; }
	11%   { opacity: 0.5; }
	13.5% { opacity: 1; }
	20%   { opacity: 1;    transform: scale(1.01); }
	61%   { opacity: 1;    transform: scale(1.03); animation-timing-function: cubic-bezier(0.5, 0, 0.8, 0.4); }
	100%  { opacity: 0;    transform: scale(1.18); }
}

/* ===================================================================== THE DOCK */

/* THE DOCK. One fixed column holding the cue and the button, added 2026-09-17.
   It replaces three hardcoded offsets (button 78px, cue 166px, cue-with-no-bar
   86px) that all had to agree with BX_Callbar's reserved height by hand.

   THE BUG IT FIXES, and it was on every notched phone: callbar.css reserves
   calc(78px + env(safe-area-inset-bottom)) three times, and this file
   contained NO env() AT ALL. The inset is about 34px on a home-indicator
   iPhone, so the button overlapped the top of the bar by that much and the
   three actions read as one mass.

   The offset is now stated ONCE, here, and flow stacks what is inside it.
   --bx-bar-reserve deliberately restates callbar.css's arithmetic rather than
   importing it, because the two files are enqueued independently and a missing
   variable would silently collapse this to the floor; if BX_Callbar's 78px
   ever moves, this is the other place to change.

   left/right: 12px, not 0, matches the bar's own 12px side padding, so the
   button's edges line up with Call and Book rather than bleeding past them.
   That alignment is most of what makes them read as a set of three. */
.bx-bz-dock {
	--bx-bar-reserve: calc(78px + env(safe-area-inset-bottom, 0px));
	position: fixed;
	left: 12px;
	right: 12px;
	bottom: calc(var(--bx-bar-reserve) + 10px);
	z-index: 99999;
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: 8px;
	/* Above 980px there is no bar to match the width of, and a call-to-action
	   stretched across a 1600px desktop reads as a cookie banner. The cap only
	   bites where there is room for it. margin-inline centres it once the cap
	   applies. */
	max-width: 560px;
	margin-inline: auto;
	/* The dock is a layout box, not a target. Only its children take presses,
	   so the 8px gap between the cue and the button does not eat a tap aimed
	   at the page behind it. */
	pointer-events: none;
}

/* NO BAR, SO TAKE THE FLOOR. Above 980px #bx-bar is display:none and there is
   nothing to sit on, but the safe-area inset is still real on a landscape
   phone, so it stays in the sum. */
.bx-bz-dock--nobar {
	bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
}

/* THE BUTTON. The dock places it. It keeps the square corners and the 2px
   border of .bx-bar-btn so it belongs to the same family as Call and Book,
   and takes a DIFFERENT fill so it does not compete with them. Ink plate,
   green rule, green lead-in: green is this feature's colour for the cure,
   and red is spoken for by whichever of Call and Book is primary at this
   hour. */
.bx-bactronize {
	position: relative;
	pointer-events: auto;
	box-sizing: border-box;
	display: none;
	width: 100%;
	padding: 9px 12px 11px;
	border: 2px solid var(--bx-cure);
	border-radius: 0;
	background: var(--bx-ink, #2E3439);
	color: var(--bx-white, #FFFFFF);
	font-family: var(--bx-ff, 'Archivo', 'Archivo Fallback', Arial, sans-serif);
	font-size: 15px;
	font-weight: 700;
	line-height: 1.2;
	text-align: center;
	text-transform: none;
	cursor: pointer;
	touch-action: manipulation;
	-webkit-appearance: none;
	appearance: none;
	/* Lifts the plate off heavy mold so it reads as a control sitting ON the
	   page rather than another patch of the overlay. */
	box-shadow: 0 6px 18px rgba(0, 0, 0, 0.34);
	/* The press is physical: the plate compresses under the thumb and the
	   shadow tightens, in under a tenth of a second. */
	transition: transform 90ms cubic-bezier(0.2, 0.7, 0.3, 1), box-shadow 90ms ease-out, background-color 120ms ease-out, border-color 120ms ease-out;
}

.bx-bactronize.is-shown {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	animation: bx-bz-in 0.32s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
	will-change: transform, opacity;
}

/* THE IMPERATIVE, held small above the brand word so the button reads as an
   instruction rather than a label: "APPLY THE / BACTRONIZING® PROCESS / to this
   website". Green, matching the cue plate directly above it, so the lead-in and
   the question read as one thought. */
.bx-bactronize__lead {
	display: block;
	font-size: clamp(10px, 2.9vw, 12px);
	font-weight: 700;
	line-height: 1.1;
	letter-spacing: 0.14em;
	color: var(--bx-cure);
	text-transform: uppercase;
	white-space: nowrap;
}

/* THE CAPITALS ARE APPLIED HERE AND NOWHERE ELSE: the string stays sentence
   case in the markup because some screen readers spell a word stored in
   capitals out letter by letter. display: block is load bearing too: two inline
   spans with no whitespace render as "Bactronizethis website!", the bug that
   shipped on 2026-09-16.

   Measured, not guessed: at 5vw on a 390px viewport the dock's content box is
   338px and this line sets about 250px, and at 320px it is 268px against about
   204px. white-space: nowrap is kept, so if either sum is ever wrong it
   overflows visibly instead of silently reflowing into a fourth line. */
.bx-bactronize__shout {
	display: block;
	font-size: clamp(17px, 5vw, 26px);
	font-weight: 800;
	line-height: 1.04;
	letter-spacing: 0.01em;
	text-transform: uppercase;
	white-space: nowrap;
}

/* The registered mark, raised and shrunk so it reads as a mark and not as a
   letter. line-height: 0 keeps it from growing the shout's line box. It stays
   INSIDE the shout span so the accessible name comes out "Apply the
   Bactronizing® Process to this website" with no stray space before the
   symbol. */
.bx-bactronize__mark {
	font-size: 0.46em;
	font-weight: 700;
	line-height: 0;
	vertical-align: 0.62em;
	letter-spacing: 0;
}

/* The quiet half: smaller, held off full white so it sits behind the shout. */
.bx-bactronize__tail {
	display: block;
	font-size: clamp(12px, 3.4vw, 15px);
	font-weight: 500;
	line-height: 1.16;
	color: rgba(255, 255, 255, 0.86);
	text-transform: none;
	white-space: nowrap;
}

@keyframes bx-bz-in {
	from { opacity: 0; transform: translate3d(0, 8px, 0) scale(0.97); }
	to { opacity: 1; transform: none; }
}

/* Hover lightens the ink and floods the rule toward the green it is about
   to spray. */
.bx-bactronize:hover {
	background: #3A424A;
	border-color: var(--bx-cure-hover);
}

/* The press compresses: down a pixel, a shade smaller, the shadow pulled
   in under it. */
.bx-bactronize:active {
	background: #3A424A;
	border-color: var(--bx-cure-hover);
	transform: translate3d(0, 1px, 0) scale(0.985);
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}

/* Mint, not white: white on this ink plate is the same value as the shout it
   would sit beside, and the ring disappeared into the text. The mint is
   7.69:1 on #2E3439. The default ring is never shown; this one is.

   THE RING SITS ON THE PAGE, NOT THE PLATE (found 2026-09-18): with a 3px
   offset its neighbour is the white page, where the mint measures 1.64:1, a
   miss against the 3:1 a focus indicator owes. The box-shadow fills the gap
   with ink and edges the ring by 2px outside it, so the ring is 7.69:1 on
   both sides and the ink edge is 12.60:1 against the page. */
.bx-bactronize:focus {
	outline: none;
}

.bx-bactronize:focus-visible {
	outline: 3px solid var(--bx-cure);
	outline-offset: 3px;
	box-shadow: 0 0 0 8px var(--bx-ink, #2E3439), 0 6px 18px rgba(0, 0, 0, 0.34);
}

/* THE CUE, on a dark plate rather than green text on the page, because of
   contrast. The mint is 1.64:1 on white, so green text on the page fails at
   every level, and fails DIFFERENTLY second to second as the mold grows under
   it. Mint on ink #2E3439 is 7.69:1, AAA for normal text, and the plate is
   12.60:1 on white, 4.07:1 on heavy mold. NOT aria-hidden: it is content. In
   flow inside the dock since 2026-09-17; align-self keeps it hugging its own
   text instead of stretching to the dock's width. */
.bx-mold-cue {
	position: relative;
	align-self: center;
	display: none;
	box-sizing: border-box;
	width: max-content;
	max-width: 100%;
	padding: 7px 16px;
	background: var(--bx-ink, #2E3439);
	color: var(--bx-cure);
	font-family: var(--bx-ff, 'Archivo', 'Archivo Fallback', Arial, sans-serif);
	font-size: clamp(16px, 4.6vw, 21px);
	font-weight: 800;
	line-height: 1.2;
	text-align: center;
	white-space: nowrap;
	pointer-events: none;
	box-shadow: 0 3px 10px rgba(20, 26, 18, 0.4);
}

/* The nib, so the question reads as attached to the control. */
.bx-mold-cue::after {
	content: "";
	position: absolute;
	bottom: -9px;
	left: 50%;
	width: 18px;
	height: 10px;
	margin-left: -9px;
	background: var(--bx-ink, #2E3439);
	clip-path: polygon(0 0, 100% 0, 50% 100%);
}

.bx-mold-cue.is-shown {
	display: block;
	animation: bx-cue-in 0.3s ease-out both, bx-cue-bounce 2.4s ease-in-out 0.3s infinite;
	will-change: transform, opacity;
	/* A tap on the cue presses the button it points at (2026-09-18, the
	   second-tap report from Edward's iPhone; see view.js, THE CUE PRESSES
	   TOO). It stays out of the Tab order: the button is the control. */
	pointer-events: auto;
	cursor: pointer;
}

@keyframes bx-cue-in {
	from { opacity: 0; transform: translate3d(0, -10px, 0) scale(0.88); }
	to { opacity: 1; transform: none; }
}

/* Listed second so it owns transform once its delay is up. 7px, not the
   arrow's 14px: the plate stays clear of the button. ONE nudge every 2.4s,
   not a 0.9s bounce forever (motion brief: infinite bounces read cheap). */
@keyframes bx-cue-bounce {
	0%, 70%, 100% { transform: translate3d(0, 0, 0); }
	82% { transform: translate3d(0, 7px, 0); }
}

/* ===================================================================== THE TOAST */

/* BACTRONIZED, the title card, 2026-09-18. Edward: "there should be final
   toast in the animation confirming that the site has been BACTRONIZED with
   a green check mark", then "the toast should be center screen and center
   of its banner, BACTRONIZED is the process we're selling here." So it is
   not a status line above the call bar; it is the slate's twin. The slate
   is the process running: a black band across the middle of the screen, the
   word in the brand red, flickering. The card is the result: the same black
   band, the seal above, a small line, and the word BACTRONIZED in the mint,
   landing with a knock, a mint hairline sweeping out across the band. It
   lands at 6.6s after the press as the haze clears, holds three seconds,
   and leaves; view.js runs the timers and the teardown takes it out with
   everything else.

   role=status and aria-live=polite in the markup, aria-hidden until it
   lands, so a screen reader hears the one line once, after the press it
   made. It takes no press: the whole thing is pointer-events none, so Call
   and Book under it stay live.

   LAID OUT AT LOAD, like every actor in the clear, hidden by visibility, so
   showing it costs no layout on the frame it lands. Transform and opacity
   only, everywhere below. */
.bx-bz-toast {
	position: fixed;
	inset: 0;
	z-index: 99999;
	display: flex;
	align-items: center;
	justify-content: center;
	visibility: hidden;
	pointer-events: none;
}

.bx-bz-toast * {
	pointer-events: none;
}

.bx-bz-toast.is-shown {
	visibility: visible;
}

/* The out: the whole card fades and swells a touch, the slate's own exit.
   Its opacity is owned by this one animation; nothing else touches it. */
.bx-bz-toast.is-leaving {
	animation: bx-toast-out 0.34s ease-in both;
}

@keyframes bx-toast-out {
	from { opacity: 1; transform: none; }
	to { opacity: 0; transform: scale(1.04); }
}

/* THE CARD: full width, a column, everything centred. Its font-size is the
   word's size; the seal, the lead and the rules are em-fractions of it, so
   the phone card and the desktop card are the same composition. */
.bx-bz-toast__card {
	position: relative;
	isolation: isolate;
	box-sizing: border-box;
	width: 100%;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 0.16em;
	padding: 0.42em 12px 0.5em;
	font-family: var(--bx-ff, 'Archivo', 'Archivo Fallback', Arial, sans-serif);
	font-size: clamp(2.1rem, 10.5vw, 7rem);
	font-stretch: var(--bx-w-board, 100%);
	color: var(--bx-white, #FFFFFF);
	text-align: center;
	will-change: transform, opacity;
}

/* THE BAND: solid black, the width of the screen, behind the card's content
   (z-index -1 inside the card's own stacking context). It opens from its
   centre line on the press of the timer: a hard wipe, not a fade. A solid
   colour on a short box rasterises in nothing (the slate's finding). */
.bx-bz-toast__card::before {
	content: "";
	position: absolute;
	inset: 0;
	background: #000;
	z-index: -1;
	transform: scaleY(0.02);
	transform-origin: 50% 50%;
}

.bx-bz-toast.is-shown .bx-bz-toast__card::before {
	animation: bx-toast-band 0.3s cubic-bezier(0.2, 0.9, 0.3, 1) both;
}

@keyframes bx-toast-band {
	from { transform: scaleY(0.02); }
	to { transform: scaleY(1); }
}

/* THE RULES: a mint hairline along the band's top edge and its bottom edge,
   sweeping out from the centre to both ends once the word has landed. The
   clean line the spray leaves. */
.bx-bz-toast__card::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	bottom: 0;
	border-top: 2px solid var(--bx-cure);
	border-bottom: 2px solid var(--bx-cure);
	transform: scaleX(0);
	transform-origin: 50% 50%;
	pointer-events: none;
}

.bx-bz-toast.is-shown .bx-bz-toast__card::after {
	animation: bx-toast-rules 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.86s both;
}

@keyframes bx-toast-rules {
	from { transform: scaleX(0); }
	to { transform: scaleX(1); }
}

/* THE SEAL: a solid mint disc with an ink check inside it. Not a white
   check on a green disc: that construction fails 3:1 for every green that
   passes on ink, and a solid disc holds at low brightness where a thin
   stroke thins out. Both edges measure 7.69:1. It pops in with a little
   overshoot, then the check draws itself, one stroke then the other. */
.bx-bz-toast__mark {
	flex: 0 0 auto;
	width: 0.92em;
	height: 0.92em;
	opacity: 0;
	transform: scale(0.2);
}

.bx-bz-toast.is-shown .bx-bz-toast__mark {
	animation: bx-toast-mark 0.42s cubic-bezier(0.2, 0.9, 0.3, 1.35) 0.22s both;
}

@keyframes bx-toast-mark {
	from { opacity: 0; transform: scale(0.2); }
	to { opacity: 1; transform: none; }
}

.bx-bz-toast__mark circle {
	fill: var(--bx-cure);
}

.bx-bz-toast__mark path {
	fill: none;
	stroke: var(--bx-ink, #2E3439);
	stroke-width: 3.2;
	stroke-linecap: round;
	stroke-linejoin: round;
	stroke-dasharray: 20;
	stroke-dashoffset: 20;
}

.bx-bz-toast.is-shown .bx-bz-toast__mark path {
	animation: bx-toast-check 0.38s cubic-bezier(0.2, 0.7, 0.3, 1) 0.52s forwards;
}

@keyframes bx-toast-check {
	to { stroke-dashoffset: 0; }
}

/* THE LEAD: small, tracked, white. It rises into place under the seal. */
.bx-bz-toast__lead {
	display: block;
	font-size: 0.3em;
	font-weight: 700;
	line-height: 1.1;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	white-space: nowrap;
	opacity: 0;
}

.bx-bz-toast.is-shown .bx-bz-toast__lead {
	animation: bx-toast-lead 0.32s ease-out 0.36s both;
}

@keyframes bx-toast-lead {
	from { opacity: 0; transform: translate3d(0, 0.5em, 0); }
	to { opacity: 1; transform: none; }
}

/* THE WORD: the process we sell, in the mint, as big as the slate's word.
   Stored sentence case in the markup and shouted here, the mark a pseudo
   element, the same three rules the button and the slate follow, so the
   string a screen reader gets is "Bactronized" and the markup carries one
   fewer thing to get wrong. It arrives wide and tracks in (a horizontal
   scale, no layout), lands, knocks once, and holds. */
.bx-bz-toast__word {
	display: block;
	font-size: 1em;
	font-weight: 800;
	line-height: 1;
	letter-spacing: -0.01em;
	color: var(--bx-cure);
	text-transform: uppercase;
	white-space: nowrap;
	opacity: 0;
}

.bx-bz-toast__word::after {
	content: "\00AE";
	font-size: 0.34em;
	font-weight: 700;
	vertical-align: 1.2em;
	margin-left: 0.06em;
	letter-spacing: 0;
}

.bx-bz-toast.is-shown .bx-bz-toast__word {
	animation: bx-toast-word 0.62s cubic-bezier(0.1, 0.9, 0.2, 1) 0.44s both;
}

@keyframes bx-toast-word {
	0%   { opacity: 0; transform: translate3d(0, 0.3em, 0) scale(1.18, 1); }
	48%  { opacity: 1; transform: none; }
	60%  { transform: translate3d(0, -2px, 0); }
	72%  { transform: none; }
	100% { opacity: 1; transform: none; }
}

/* ===================================================================== MODES */

/* LITE MODE. The renderer measures its own frame gaps over the first second
   of growth and adds this class to the overlay if the device is not holding
   them (a Low Power Mode iPhone caps requestAnimationFrame at 30fps and shows
   up here on its own). What lite gives up is the renderer's business: half
   the render scale, the finest octaves, half the droplets. Nothing in this
   stylesheet changes with it; the class is kept on the overlay so it can be
   read off a live page alongside the dock's data-bx-* attributes. */
.bx-mold--lite {
	--bx-lite: 1;
}

/* REDUCED MOTION. Still frames and a short dissolve, not a frozen scene and
   not a hidden feature (2026-09-17; the sprite build hid everything). The
   renderer draws the colony at its final state at once and answers the press
   with a 700ms fade and no plume. Here: the cue does not bounce, the button
   and the cue arrive without an entrance, the slate appears and leaves
   without the slam, the knock or the flicker. */
@media (prefers-reduced-motion: reduce) {
	.bx-bactronize.is-shown,
	.bx-mold-cue.is-shown {
		animation: none;
	}

	.bx-bactronize {
		transition: none;
	}

	.bx-mold.is-clearing .bx-mold__flash {
		animation: bx-flash-still 0.9s linear both;
	}

	/* The toast: no wipe, no knock, no sweep. The card is drawn complete and
	   fades in and out in 120ms; the check is drawn already. */
	.bx-bz-toast.is-shown {
		animation: bx-toast-fade-in 0.12s linear both;
	}

	.bx-bz-toast.is-leaving {
		animation: bx-toast-fade-out 0.12s linear both;
	}

	.bx-bz-toast.is-shown .bx-bz-toast__card::before,
	.bx-bz-toast.is-shown .bx-bz-toast__card::after,
	.bx-bz-toast.is-shown .bx-bz-toast__mark,
	.bx-bz-toast.is-shown .bx-bz-toast__lead,
	.bx-bz-toast.is-shown .bx-bz-toast__word {
		animation: none;
		opacity: 1;
		transform: none;
	}

	.bx-bz-toast.is-shown .bx-bz-toast__mark path {
		animation: none;
		stroke-dashoffset: 0;
	}
}

@keyframes bx-toast-fade-in {
	from { opacity: 0; }
	to { opacity: 1; }
}

@keyframes bx-toast-fade-out {
	from { opacity: 1; }
	to { opacity: 0; }
}

@keyframes bx-flash-still {
	0%   { opacity: 0; }
	12%  { opacity: 1; }
	70%  { opacity: 1; }
	100% { opacity: 0; }
}

/* RETIRED, kept as tombstones so a cached stylesheet cannot resurrect them.
   The arrow was replaced by the cue (2026-09-16); the no-bar case is one
   class on the dock (2026-09-17); the sprite colonies, puffs, jet, burst and
   fog are gone with the shader (2026-09-17). */
.bx-mold-arrow,
.bx-mold__field,
.bx-mold__fog,
.bx-mold__spore,
.bx-mold__puff,
.bx-mold__jet,
.bx-mold__burst {
	display: none !important;
}

.bx-bactronize--nobar,
.bx-bactronize--nobar ~ .bx-mold-cue,
.bx-mold-cue.is-nobar {
	bottom: auto;
}
