/* ==================================================================
   `hidden` MUST WIN, EVERYWHERE IN HERE
   ==================================================================

   The UA stylesheet's `[hidden] { display: none }` loses to ANY class in this
   file that sets display. That is not a hypothetical: `.ch-demo__idform`
   showed the address step and the code step at once, with two identical
   "Continue the demo" buttons, because `idStep()` was setting an attribute
   with no effect. `.ch-demo__go` had the same hole — `salesEl.hidden = false`
   could not hide "Talk to sales" on a message that should not offer it.

   Both are fixed individually below, but the pattern will recur: somebody adds
   a flex row, toggles it with `el.hidden`, and it never hides. So the guard is
   general and scoped to this component. `!important` is deliberate — it exists
   precisely to beat a later `display` declaration written by somebody who has
   not read this.

   Element rules that need to override it (the panels' own `display: flex` when
   shown) are unaffected: they apply when the attribute is absent.
   ================================================================== */
.ch-demo [hidden] {
	display: none !important;
}

/* ==========================================================================
   The live demo — "Experience the magic"
   ==========================================================================

   A FULL-SCREEN VIEW, NOT A DIALOG. This began as a modal card on a scrim,
   which was the wrong shape: the thing behind it is not context a visitor
   needs while a converged experience is running on their own website, and a
   card puts a border and a shadow around the only thing on screen. So the
   demo takes the viewport, carries its own bar, and offers Back rather than a
   close cross — with a history entry behind it, so the browser's own back
   button and a trackpad swipe do the same thing.

   It is still `role="dialog"` with the rest of the document `inert`: nothing
   behind it is reachable, which is what that role is describing.
   ========================================================================== */

.ch-demo {
	position: fixed;
	inset: 0;
	background: var(--card);

	/*
	 * ABOVE THE ADMIN BAR, WHICH SITS AT 99999.
	 *
	 * At 10000 this was painted under it: a signed-in visitor got the demo's
	 * own bar — Back control, address, countdown — sliced in half by
	 * WordPress's, which is not a rare edge case here. The whole Phase 1
	 * audience is signed-in editors, so it was the only state anyone could
	 * actually see.
	 *
	 * Covering it is right rather than insetting below it: this is a
	 * full-screen view, and Back is how you leave.
	 */
	z-index: 100001;
}

.ch-demo[hidden] {
	display: none;
}

.ch-demo__page {
	display: flex;
	flex-direction: column;
	width: 100%;
	height: 100%;
	overflow: hidden;
	opacity: 0;
	transform: translateY(8px);
	transition:
		opacity 200ms var(--ease-standard),
		transform 260ms var(--ease-standard);
}

.ch-demo.is-open .ch-demo__page {
	opacity: 1;
	transform: none;
}

/* ---- The bar ----------------------------------------------------------- */

.ch-demo__bar {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	flex: 0 0 auto;
	padding: var(--space-3) var(--space-4);
	border-bottom: 1px solid var(--border);
	background: var(--surface);
}

.ch-demo__back {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	flex: 0 0 auto;
	padding: 8px 14px 8px 10px;
	border: 1px solid var(--border-strong);
	border-radius: var(--radius-pill);
	background: var(--card);
	color: var(--text);
	font-family: var(--font-sans);
	font-size: 14px;
	font-weight: 500;
	cursor: pointer;
	transition:
		background 160ms var(--ease-standard),
		border-color 160ms var(--ease-standard),
		transform 160ms var(--ease-standard);
}

.ch-demo__back:hover {
	background: var(--input-hover);
	border-color: var(--accent-border);
	transform: translateX(-1px);
}

.ch-demo__mark {
	display: grid;
	place-items: center;
	width: 28px;
	height: 28px;
	flex: 0 0 auto;
	margin-left: var(--space-2);
	border-radius: var(--radius-icon);
	color: #fff;
	background: linear-gradient(135deg, var(--accent) 0%, var(--violet) 100%);
}

.ch-demo__site {
	flex: 1 1 auto;
	min-width: 0;
	margin: 0;
	overflow: hidden;
	font-size: 14px;
	font-weight: 500;
	color: var(--text-2);
	text-overflow: ellipsis;
	white-space: nowrap;
}

.ch-demo__timer {
	flex: 0 0 auto;
	padding: 4px 10px;
	border-radius: var(--radius-pill);
	background: var(--accent-soft);
	color: var(--accent-strong);
	font-size: 13px;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

.ch-demo__timer[hidden] {
	display: none;
}

/* Under a minute the countdown stops being neutral information. */
.ch-demo__timer.is-low {
	background: var(--warning-soft);
	color: var(--warning-fg);
}

/* ---- The preview label -------------------------------------------------
   One line across the top for as long as the demo runs, and not dismissible:
   the full disclosures were accepted on the disclaimer before the demo opened,
   and what stays is the fact a screenshot must keep. Slim on purpose — it is
   a label on the demo, not a second notice competing with it. */

.ch-demo__notice {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	margin: 0;
	padding: 5px var(--space-4);
	border-bottom: 1px solid var(--border);
	background: var(--accent-soft);
	color: var(--text-2);
	font-size: 12px;
	font-weight: 600;
	line-height: 18px;
	letter-spacing: 0.02em;
	text-align: center;
}

.ch-demo__notice[hidden] {
	display: none;
}

.ch-demo__notice-text {
	min-width: 0;
}

/* ==================================================================
   THE DISCLAIMER
   ==================================================================

   Over the page the visitor is on, before the demo exists, and dressed as
   the site's own pieces rather than a component of its own (operator,
   2026-10-23: the first cut "doesn't gel with overall website design at
   all"):

   - the card is the /p/ preview card (convergedhub-demo-links preview.css,
     .chdl-card): an accent eyebrow, a large headline with the site in the
     brand gradient (.ch-gradient-text in base.css), a 24px radius;
   - the scrim is the Book a demo dialog's (book-demo.css, .cbd-overlay):
     a darker wash with a slight blur, so the page recedes;
   - "I understand" is the try bar's own button (sections.css, .ch-tryform
     button), the control the visitor pressed a moment ago.

   The statements are one soft panel, a row and an icon each, left-aligned
   inside the centred card: centred multi-line paragraphs were what made
   the first cut read as a wall of small print.

   A sibling of .ch-demo, not inside it, so its own `hidden` guard is here.
   Above the demo's layer (100001) and below the cookie bar (2147483000),
   which stays answerable over it. Buttons carry :not(#_) for the same
   reason as the rest of this file: block styles reach every <button>.
   ================================================================== */

.ch-consent[hidden] {
	display: none !important;
}

.ch-consent {
	position: fixed;
	inset: 0;
	z-index: 100002;
	display: grid;
	/* Centred by the card's auto margins, not place-items: a card taller than
	   the window then starts at the top and scrolls, instead of overflowing
	   both edges with its headline out of reach. */
	justify-items: center;
	align-items: start;
	padding: 40px 20px;
	overflow-y: auto;
	background: rgba(17, 17, 26, 0.5);
	-webkit-backdrop-filter: blur(3px);
	backdrop-filter: blur(3px);
	animation: ch-consent-in 160ms ease both;
}

.ch-consent__card {
	width: 100%;
	max-width: 600px;
	margin-block: auto;
	padding: 44px 44px 30px;
	text-align: center;
	color: var(--text);
	font-family: var(--font-sans);
	/* A faint wash of the accent at the top, the way the hero glows. */
	background:
		radial-gradient(120% 55% at 50% 0%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 70%),
		var(--card);
	border: 1px solid var(--border);
	border-radius: 24px;
	box-shadow: 0 30px 80px rgba(17, 17, 26, 0.28), 0 6px 16px rgba(17, 17, 26, 0.12);
	animation: ch-consent-rise 240ms var(--ease-standard) both;
}

.ch-consent__eyebrow {
	margin: 0;
	font-size: 13px;
	font-weight: 600;
	letter-spacing: 1.4px;
	text-transform: uppercase;
	color: var(--accent);
}

.ch-consent__headline {
	margin: 12px auto 0;
	max-width: 22em;
	font-size: clamp(24px, 2.4vw, 30px);
	font-weight: 600;
	line-height: 1.22;
	letter-spacing: -0.5px;
	text-wrap: balance;
	color: var(--text);
}

/* The site the visitor typed, in the brand gradient. */
.ch-consent__headline strong {
	font-weight: 600;
	overflow-wrap: anywhere;
	background: linear-gradient(100deg, #0b2a6b 0%, var(--accent-bright) 42%, #6e5be8 78%, var(--violet-soft) 100%);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	-webkit-text-fill-color: transparent;
}

.ch-consent__points {
	display: grid;
	margin: 28px 0 0;
	padding: 4px 20px;
	list-style: none;
	text-align: left;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: 16px;
}

.ch-consent__points li {
	display: grid;
	grid-template-columns: 34px minmax(0, 1fr);
	gap: 14px;
	align-items: start;
	padding: 14px 0;
	font-size: 15px;
	line-height: 1.55;
	color: var(--text-2);
}

.ch-consent__points li:not([hidden]) ~ li {
	border-top: 1px solid var(--border);
}

/* A line emptied on the Words tab takes its row (and its icon) with it, and
   so does the first point that doesn't fit the site (demo company or not). */
.ch-consent__points li[hidden],
.ch-consent__points li:has(span[data-ch-consent-text][hidden]) {
	display: none;
}

.ch-consent__pt strong {
	font-weight: 600;
	color: var(--text);
}

.ch-consent__pt strong[hidden] {
	display: none;
}

/* The agreement: links to the two documents, accepted with the button. */
.ch-consent__agree {
	margin: 20px auto 0;
	max-width: 38em;
	font-size: 14px;
	line-height: 1.5;
	color: var(--text-2);
}

.ch-consent__agree a {
	color: var(--accent);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.ch-consent__ic {
	display: grid;
	place-items: center;
	width: 34px;
	height: 34px;
	/* Centred on the FIRST line of its text, not on the top of it. */
	margin-top: -5px;
	margin-top: calc((1lh - 34px) / 2);
	border-radius: var(--radius-icon);
	background: var(--accent-soft);
	color: var(--accent);
}

.ch-consent__ic svg {
	display: block;
}

.ch-consent__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 12px 20px;
	margin-top: 28px;
}

.ch-consent .ch-consent__accept:not(#_) {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: 52px;
	padding: 0 34px;
	border: 0;
	border-radius: 14px;
	color: var(--accent-on);
	background: linear-gradient(100deg, var(--accent-bright) 0%, var(--accent) 55%, var(--accent-strong) 100%);
	box-shadow: 0 10px 28px color-mix(in srgb, var(--accent) 35%, transparent);
	font-family: var(--font-sans);
	font-size: 16px;
	font-weight: 600;
	cursor: pointer;
	transition: transform 150ms ease, box-shadow 150ms ease;
}

.ch-consent .ch-consent__accept:hover:not(#_) {
	transform: translateY(-1px);
	box-shadow: 0 14px 32px color-mix(in srgb, var(--accent) 45%, transparent);
}

.ch-consent .ch-consent__accept:active:not(#_) {
	transform: scale(var(--press-scale));
}

.ch-consent .ch-consent__decline:not(#_) {
	padding: 10px 8px;
	border: 0;
	background: none;
	color: var(--text-2);
	font-family: var(--font-sans);
	font-size: 15px;
	font-weight: 600;
	cursor: pointer;
}

.ch-consent .ch-consent__decline:hover:not(#_) {
	color: var(--text);
}

.ch-consent__accept:focus-visible,
.ch-consent__decline:focus-visible {
	outline: 3px solid var(--accent-ring);
	outline-offset: 2px;
}

/* The session and the microphone: small, but still read — text-2, not the
   fine-print grey, because it is one of the disclosures. */
.ch-consent__fine {
	margin: 22px auto 0;
	max-width: 38em;
	font-size: 13px;
	line-height: 1.5;
	color: var(--text-2);
}

.ch-consent__fine[hidden] {
	display: none;
}

/* Short windows (a 13" laptop): the card closes up rather than scroll. */
@media (max-height: 1000px) {
	.ch-consent {
		padding-block: 20px;
	}

	.ch-consent__card {
		padding: 30px 40px 22px;
	}

	.ch-consent__headline {
		font-size: 24px;
	}

	.ch-consent__agree {
		margin-top: 14px;
	}

	.ch-consent__fine {
		margin-top: 14px;
	}

	.ch-consent__points {
		margin-top: 18px;
	}

	.ch-consent__points li {
		padding: 9px 0;
		font-size: 14.5px;
		line-height: 1.5;
	}

	.ch-consent__actions {
		margin-top: 20px;
	}
}

@keyframes ch-consent-in {
	from { opacity: 0; }
}

@keyframes ch-consent-rise {
	from { opacity: 0; transform: translateY(14px) scale(0.985); }
}

@media (max-width: 560px) {
	.ch-consent__card {
		padding: 32px 22px 24px;
		border-radius: 20px;
	}

	.ch-consent__points {
		padding: 2px 16px;
	}

	.ch-consent .ch-consent__accept:not(#_) {
		width: 100%;
	}
}

@media (prefers-reduced-motion: reduce) {
	.ch-consent,
	.ch-consent__card {
		animation: none;
	}
}

/* ---- Panels ------------------------------------------------------------ */

.ch-demo__panel {
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--space-4);
	padding: var(--space-12) var(--space-6);
	text-align: center;
}

.ch-demo__panel[hidden] {
	display: none;
}

.ch-demo__title {
	max-width: 28ch;
	margin: 0;
	font-size: var(--type-title-size);
	font-weight: var(--type-title-weight);
	line-height: var(--type-title-line);
	letter-spacing: var(--type-title-tracking);
	color: var(--text);
}

.ch-demo__lede {
	max-width: 52ch;
	margin: 0;
	font-size: 16px;
	line-height: 24px;
	color: var(--text-2);
}

.ch-demo__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-3);
	align-items: center;
	justify-content: center;
	margin-top: var(--space-4);
}

.ch-demo__go:not(#_),
.ch-demo__ghost {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	padding: 12px 20px;
	border: 0;
	border-radius: var(--radius-control);
	font-family: var(--font-sans);
	font-size: 15px;
	font-weight: 600;
	cursor: pointer;
	text-decoration: none;
	transition: transform 160ms var(--ease-standard), box-shadow 160ms var(--ease-standard);
}

/* Book a Demo: the end screen's call to action, in the site's primary blue.
   Hidden until the timed demo ends, when it replaces Back to the site. */
.ch-demo__book:not(#_) {
	display: none;
	align-items: center;
	padding: 12px 20px;
	border-radius: var(--radius-control);
	font-family: var(--font-sans);
	font-size: 15px;
	font-weight: 600;
	text-decoration: none;
	color: var(--accent-on, #fff);
	background: var(--accent);
	box-shadow: var(--shadow-card);
}

.ch-demo__book:not(#_):hover {
	filter: brightness(1.06);
}

.ch-demo__book:not(#_):focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

.ch-demo--ended .ch-demo__book:not(#_) {
	display: inline-flex;
}

/* Explore other experiences: the end screen's second action, beside Book a
   Demo and quieter than it. Hidden until the timed demo ends, like the book. */
.ch-demo__explore:not(#_) {
	display: none;
	align-items: center;
	padding: 12px 20px;
	border-radius: var(--radius-control);
	border: 1px solid var(--border-strong);
	font-family: var(--font-sans);
	font-size: 15px;
	font-weight: 600;
	text-decoration: none;
	color: var(--text);
	background: transparent;
}

.ch-demo__explore:not(#_):hover {
	background: var(--nav-hover);
}

.ch-demo__explore:not(#_):focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

.ch-demo--ended .ch-demo__explore:not(#_) {
	display: inline-flex;
}

.ch-demo--ended [data-ch-demo-panel="message"] [data-ch-demo-close] {
	display: none;
}

/* The logo above the end screen's heading, centred by the panel. Hidden on
   every other message (busy, refused, failed), like Book a Demo. The second
   image is the light-on-dark lockup, shown instead on a dark page. */
.ch-demo__endlogo {
	display: none;
	margin-bottom: var(--space-2);
}

.ch-demo--ended .ch-demo__endlogo {
	display: block;
}

.ch-demo__endlogo-img {
	display: block;
	height: 34px;
	width: auto;
	max-width: 100%;
}

.ch-demo__endlogo-img--dark,
[data-theme="dark"] .ch-demo__endlogo-img {
	display: none;
}

[data-theme="dark"] .ch-demo__endlogo-img--dark {
	display: block;
}

.ch-demo__go:not(#_) {
	color: var(--accent-on);
	background: linear-gradient(135deg, var(--accent) 0%, var(--violet-bright) 100%);
	box-shadow: 0 6px 18px -8px var(--accent);
}

.ch-demo__go:hover:not(#_) {
	transform: translateY(-1px);
	box-shadow: 0 10px 24px -8px var(--accent);
}

.ch-demo__ghost {
	color: var(--text-2);
	background: transparent;
	border: 1px solid var(--border-strong);
}

.ch-demo__ghost:hover {
	color: var(--text);
	background: var(--nav-hover);
}

/* ---- Loading ----------------------------------------------------------- */

.ch-demo__spinner {
	display: flex;
	gap: 6px;
}

.ch-demo__spinner span {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: var(--accent);
	animation: ch-demo-bounce 1s var(--ease-standard) infinite;
}

.ch-demo__spinner span:nth-child(2) {
	background: var(--indigo);
	animation-delay: 120ms;
}

.ch-demo__spinner span:nth-child(3) {
	background: var(--violet);
	animation-delay: 240ms;
}

@keyframes ch-demo-bounce {
	0%,
	100% {
		transform: translateY(0);
		opacity: 0.45;
	}

	50% {
		transform: translateY(-7px);
		opacity: 1;
	}
}

.ch-demo p.ch-demo__stage {
	margin: 0;
	font-size: 15px;
	color: var(--text-2);
}

/* ---- The frame --------------------------------------------------------- */

.ch-demo__stage {
	position: relative;
	flex: 1 1 auto;
	min-height: 0;
	background: var(--bg);
}

.ch-demo__stage[hidden] {
	display: none;
}

.ch-demo__frame {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
	opacity: 0;
	transition: opacity 320ms var(--ease-standard);
}

.ch-demo.is-live .ch-demo__frame {
	opacity: 1;
}

/*
 * The frame is on screen from the moment it is mounted, not from the moment a
 * session is granted — so the demo draws its own first screen instead of ours
 * drawing a spinner in front of it. Separate from `is-live`, which still means
 * "granted, clock running" and must not be implied by merely being visible.
 *
 * The background beneath is white rather than the page tint: for the few
 * hundred milliseconds before the frame paints, a tinted full-screen panel
 * reads as a screen of its own, and white reads as the page still arriving.
 */
.ch-demo.is-frame-early .ch-demo__frame {
	opacity: 1;
}

.ch-demo.is-frame-early .ch-demo__page {
	background: #fff;
}

/* ---- Small screens ----------------------------------------------------- */

/* Narrower than the configured width the script opens a tab instead, so this
   only has to cover the band between that and a laptop. */
@media (max-width: 900px) {
	.ch-demo__bar {
		padding: var(--space-2) var(--space-3);
		gap: var(--space-2);
	}

	/* The arrow carries it; the words are the first thing to go. */
	.ch-demo__back-label {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.ch-demo__back {
		padding: 8px 10px;
	}

	.ch-demo__mark {
		display: none;
	}

	.ch-demo__panel {
		padding: var(--space-8) var(--space-5);
	}

	.ch-demo__title {
		font-size: 26px;
		line-height: 32px;
	}

	.ch-demo__endlogo-img {
		height: 28px;
	}
}

/* ---- Reduced motion ---------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	.ch-demo__page,
	.ch-demo__frame,
	.ch-demo__back,
	.ch-demo__go:not(#_),
	.ch-demo__ghost {
		transition-duration: 0.001ms;
	}

	.ch-demo__page {
		transform: none;
	}

	.ch-demo__spinner span {
		animation: none;
		opacity: 1;
	}
}

/*
 * ── WHEN THE FRAME HOSTS ITS OWN BACK AND TIMER ───────────────────────────
 * The demo's own toolbar already carries a control cluster on the right, and
 * our bar repeated the same two jobs one row above it: two rows of chrome
 * around one view, and a countdown twice. So the frame renders both and this
 * row goes away — but only on chd:chrome, never on our own initiative, and
 * see live-demo.js for what brings it back.
 *
 * NOT `display: none`. The dialog is labelled by #ch-demo-site inside this
 * header, and an aria-labelledby pointing at a display:none element leaves the
 * dialog with no accessible name at all. So it is taken out of the layout
 * while its text stays readable to assistive technology.
 */
/*
 * Same treatment as the hosted-chrome rule, for the second before we know
 * which of us is drawing the bar. Visually hidden rather than display:none
 * because the dialog is labelled by the site name inside it — a dialog whose
 * accessible name appears a second late is a different bug, not a fix.
 */
.ch-demo--chrome-pending .ch-demo__bar,
.ch-demo--chrome-hosted .ch-demo__bar {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ---- The work-email gate ----------------------------------------------- */

/*
 * One field and one button on a line, wrapping to two rows when there is no
 * room. The panel is already centred, so this only has to hold the pair
 * together and keep the field wide enough to read a whole address back —
 * a work address truncated mid-domain is one the visitor cannot check.
 */
.ch-demo__idform {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-3);
	align-items: center;
	justify-content: center;
	width: 100%;
	max-width: 460px;
	margin: 0;
}

/*
 * `display: flex` ABOVE BEATS THE `hidden` ATTRIBUTE, and the visitor saw both
 * steps at once: the address field and the code field stacked, with two
 * identical "Continue the demo" buttons and no way to tell which one meant
 * anything. The UA stylesheet's `[hidden] { display: none }` loses to any class
 * that sets display, so every rule in this file that sets display on something
 * toggled with `el.hidden` needs its own counterpart. The panels have one; when
 * these forms were added they did not, and `idStep()` was correctly setting an
 * attribute that had no effect.
 */
.ch-demo__idform[hidden] {
	display: none;
}

.ch-demo__idfield:not(#_) {
	flex: 1 1 240px;
	min-width: 0;
	padding: 11px var(--space-4);
	border: 1px solid var(--border);
	border-radius: var(--radius-pill);
	background: var(--surface);
	color: var(--text);
	font: inherit;
	font-size: 15px;
	line-height: 20px;
}

.ch-demo__idfield::placeholder {
	color: var(--text-3);
}

.ch-demo__idfield:focus-visible:not(#_) {
	outline: 2px solid var(--accent);
	outline-offset: 1px;
}

/*
 * The invalid state is a border AND the message below, never colour alone —
 * the message is what a screen reader gets and what a visitor who cannot
 * distinguish the border reads instead.
 */
.ch-demo__idfield[aria-invalid="true"]:not(#_) {
	border-color: var(--danger, #d33);
}

.ch-demo__iderror {
	max-width: 46ch;
	margin: 0;
	color: var(--danger, #d33);
	font-size: 14px;
	line-height: 20px;
}

.ch-demo__iderror[hidden] {
	display: none;
}

/*
 * The code step. Narrower than the address field and centred, because a code
 * is six characters and a field sized for an email address invites the visitor
 * to wonder what else belongs in it.
 */
.ch-demo__codefield:not(#_) {
	flex: 0 1 180px;
	text-align: center;
	font-size: 17px;
	letter-spacing: 0.18em;
}

/*
 * The way back to the address field. A link rather than a second button: it is
 * the escape hatch from a typo, not a peer of "continue".
 */
.ch-demo__idswap {
	padding: 0;
	border: 0;
	background: transparent;
	color: var(--text-2);
	font: inherit;
	font-size: 14px;
	line-height: 20px;
	text-decoration: underline;
	text-underline-offset: 2px;
	cursor: pointer;
}

.ch-demo__idswap:hover {
	color: var(--text);
}

.ch-demo__idswap[hidden] {
	display: none;
}

/* ---- The gate is a step, not an interruption ---------------------------- */

/*
 * WHY THE DEMO'S OWN FURNITURE GOES AWAY HERE.
 *
 * The work-email step was rendering inside the opened demo — its bar across
 * the top, the "you are viewing an embedded preview" strip beneath it — so it
 * read as a wall thrown up in the middle of something already running, rather
 * than as the last step of starting it. The visitor entered a URL, watched the
 * demo begin, and was then stopped by furniture that said the demo was
 * underway.
 *
 * So while the gate is up, the chrome around it is not: no bar, no disclosure
 * strip. The panel carries its own "Back to the site", so nothing is trapped —
 * and the strip is about a page being framed, which at this moment is not
 * happening yet. It returns with the demo it describes.
 *
 * The bar is visually hidden rather than display:none for the same reason as
 * the hosted-chrome rule: the dialog is labelled by the site name inside it.
 */
.ch-demo--gate .ch-demo__bar {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.ch-demo--gate .ch-demo__notice {
	display: none;
}

/*
 * The frame draws the label beside its address (chd:chrome preview:true), so
 * our row goes — and it is held back, like our bar, for the second before we
 * know. A frame that never claims it gets the row when the hold ends.
 */
.ch-demo--chrome-pending .ch-demo__notice,
.ch-demo--preview-hosted .ch-demo__notice {
	display: none;
}

/* ==================================================================
   CLEARING THE STAGE
   ==================================================================

   The page parts around the control the visitor pressed: sections furthest
   from it leave first, the one holding the button leaves last, above goes up
   and below goes down. Ordered by `--ch-exit-i`, which live-demo.js sets from
   each section's distance from that control.

   THE TIMELINE, since four numbers have to agree and eyeballing them is how the
   first cut ended up invisible:

     section i=0 (furthest)   0ms →  520ms
     …90ms apart…
     section i=6 (nearest)  540ms → 1060ms
     try bar (dissolves)    620ms → 1180ms   ← the last thing on screen
     try bar morphs         200ms → 1100ms   ← onto the demo's own address pill
     overlay + frame        1100ms → 1400ms   ← arrives as the morph lands

   The bot-check does not appear for roughly a second either way, so all of
   this sits inside a wait the visitor already had.

   THE OVERLAY WAITS FOR IT. Its fade is delayed while this runs — otherwise
   the full-screen panel covers the page in 200ms and the choreography happens
   behind an opaque sheet, which is the same fault as a challenge drawn into a
   frame nobody has revealed. The delay applies only while `ch-clearing` is on
   the root, so closing is unaffected and stays immediate.

   Transform and opacity only: both are composited, and this plays on a machine
   that is about to be asked for a video call.
   ================================================================== */

/*
 * ── PACED TO BE WATCHED, NOT JUST TO HAPPEN ──────────────────────────────
 *
 * The first cut ran 400ms with a 34ms stagger: the whole page cleared in
 * roughly the time it takes to notice something has changed, and the overlay
 * arrived over the tail of it. It was playing correctly and reading as an
 * abrupt cut — the movement was real and too quick to see.
 *
 * 520ms each with a 90ms stagger spreads the clear over about a second, and
 * the overlay now arrives as the last section goes rather than on top of it.
 * Nothing is waiting on this: the frame, the service call and the challenge
 * are already in flight, and the challenge itself does not appear for about a
 * second, so the choreography fills a gap that existed anyway.
 */
html.ch-clearing [data-ch-exit] {
	animation: ch-clear-down 520ms cubic-bezier(0.32, 0, 0.24, 1) both;
	animation-delay: calc(var(--ch-exit-i, 0) * 90ms);
	will-change: transform, opacity;
}

html.ch-clearing [data-ch-exit="up"] {
	animation-name: ch-clear-up;
}

/*
 * The section the visitor pressed in does not slide away with the rest — it
 * settles and dissolves in place, so the last thing to go is the thing they
 * were looking at, and the overlay arrives where their attention already is.
 */
html.ch-clearing [data-ch-exit="focus"] {
	animation: ch-clear-focus 560ms cubic-bezier(0.32, 0, 0.24, 1) both;
	animation-delay: 620ms;
}

/*
 * Far enough to be a departure rather than a nudge. 26px read as a flinch at
 * full-bleed section width; 64px reads as the section leaving.
 */
@keyframes ch-clear-up {
	40% {
		opacity: 0.72;
	}
	to {
		opacity: 0;
		transform: translate3d(0, -64px, 0) scale(0.955);
	}
}

@keyframes ch-clear-down {
	40% {
		opacity: 0.72;
	}
	to {
		opacity: 0;
		transform: translate3d(0, 64px, 0) scale(0.955);
	}
}

/*
 * The section the visitor pressed in does not travel: it swells very slightly
 * and dissolves where it stands, so the last thing on screen is the thing they
 * were looking at and the overlay opens into that space.
 */
/*
 * The section holding the try bar travels to the top instead of dissolving in
 * place: everything in it except the bar itself fades immediately, and the bar
 * rides up to where the demo's address bar lands. `--ch-fly-y` is that
 * element's own measured distance, set in live-demo.js.
 */
html.ch-clearing [data-ch-exit="fly"] {
	animation: none;
	opacity: 1;
}

html.ch-clearing [data-ch-exit="fly"] > * {
	animation: ch-clear-fade-fast 220ms linear both;
}

/*
 * The real try bar goes out quickly and quietly the moment the stand-in is in
 * place on top of it — the visitor's eye is on the stand-in, which is at the
 * identical position and looks identical, so this is a swap they cannot see.
 */
html.ch-clearing [data-ch-exit="fly"] .ch-trybar__inner {
	animation: ch-clear-fade-fast 120ms linear both;
}

@keyframes ch-clear-fade-fast {
	to { opacity: 0; }
}

/*
 * Lifts, narrows toward the width of the bar it is becoming, and fades only at
 * the very end — so the address is readable for most of the journey and the
 * handover happens where the eye already is.
 */
/*
 * Travels to the demo's address pill and shrinks to its size on the way, so
 * the last frame of ours and the first frame of theirs are the same object in
 * the same place. Opaque until 82% — the URL stays readable for almost the
 * whole journey, and only crosses over once it has arrived.
 */
@keyframes ch-morph {
	0% {
		transform: translate3d(0, 0, 0) scale(1);
		opacity: 1;
	}
	82% {
		opacity: 1;
	}
	100% {
		transform:
			translate3d( var(--ch-fly-x, 0), var(--ch-fly-y, -300px), 0 )
			scale( var(--ch-fly-s, 0.55) );
		opacity: 0;
	}
}

@keyframes ch-clear-focus {
	55% {
		opacity: 0.6;
		transform: translate3d(0, -3px, 0) scale(1.03);
	}
	to {
		opacity: 0;
		transform: translate3d(0, -10px, 0) scale(1.075);
	}
}

/*
 * ── THE CONTAINER IS OPAQUE, AND THAT IS WHAT HID THE CHOREOGRAPHY ────────
 *
 * `.ch-demo` carries `background: var(--card)`, so the moment `hidden` comes
 * off it paints a full-screen sheet over the page — and the sections were
 * dissolving behind it, unseen. Delaying `.ch-demo__page` was not enough
 * because the panel was never what covered the page; its parent was.
 *
 * This is the third time in two days that something was drawn where nobody
 * could see it, and the first time I did it to myself: I wrote the rule about
 * the overlay waiting for the stage, and then let its container arrive
 * instantly anyway. Checking the panel and not the thing behind it is the same
 * shape as measuring the screens and calling the fetch count verified.
 */
html.ch-clearing .ch-demo {
	background: transparent;
}

/*
 * The opaque ground moves to the panel, which already fades in on a delay, so
 * the white arrives WITH the overlay instead of ahead of it. Deliberately not
 * a transition on the container: `ch-clearing` and `is-open` are added in the
 * same turn, so the transparent state is never painted and a transition
 * between them has no start value to run from. A property that is simply
 * inherited by the thing already fading is more robust than a transition that
 * depends on the order two classes happen to land in.
 */
html.ch-clearing .ch-demo__page {
	background: var(--card);
}

/* The overlay arrives as the stage empties, not over the top of it. */
html.ch-clearing .ch-demo.is-open .ch-demo__page {
	transition:
		opacity 300ms var(--ease-standard) 1100ms,
		transform 360ms var(--ease-standard) 1100ms;
}

/*
 * ── THE FRAME IS HELD BACK SO THE MORPH IS SEEN AGAINST THE CLEARED PAGE ──
 *
 * The frame is mounted and working from the click — the service call, the
 * challenge, all of it in flight — but it does not appear until the morph has
 * landed. Otherwise the demo's toolbar paints underneath while our bar is
 * still travelling toward it, and the visitor watches a bar fly into a bar
 * that is already there.
 *
 * 1.1s, which the visitor was spending anyway: the challenge does not appear
 * for about a second regardless. Nothing waits on this.
 */
/*
 * `!important`, and not for want of trying specificity first. Two other state
 * rules set this frame to `opacity: 1` — `is-frame-early` and `is-live` — and
 * both are live at this moment, because the frame IS mounted and the overlay
 * IS open; the frame is simply not to be looked at yet. Measured rather than
 * reasoned: the selector matched (`el.matches()` true) and the computed value
 * was still 1, so the cascade was being lost to something a higher specificity
 * was not reliably beating. A state that must win over two other states is
 * what `!important` is actually for.
 */
html.ch-clearing.ch-morphing .ch-demo__frame {
	opacity: 0 !important;
}

html.ch-clearing .ch-demo__frame {
	transition: opacity 260ms var(--ease-standard);
}

/*
 * REDUCED MOTION: no travel and no stagger — but the sequence still reads,
 * because the page still goes before the demo arrives. Turning the whole thing
 * off would hand these visitors the abrupt cut the choreography exists to
 * replace.
 */
@media (prefers-reduced-motion: reduce) {
	html.ch-clearing [data-ch-exit],
	html.ch-clearing [data-ch-exit="focus"] {
		animation: ch-clear-fade 160ms linear both;
		animation-delay: 0ms;
	}

	html.ch-clearing .ch-demo.is-open .ch-demo__page {
		transition: opacity 160ms linear 120ms;
	}

	@keyframes ch-clear-fade {
		to { opacity: 0; }
	}
}

/* ==================================================================
   THE BAR THAT BECOMES THE DEMO'S BAR
   ==================================================================

   One element that starts as the try bar and ends as the demo's toolbar.

   EVERY PART IS ABSOLUTELY POSITIONED AND ANIMATED BY ITS OWN LEFT AND WIDTH.
   The first cut laid the interior out with flexbox and animated the container;
   flex then recomputed the children's positions on every frame from whatever
   width the container happened to have, so the mark, the pill and the controls
   jumped about inside a box that was itself moving. Two layout systems
   fighting over the same frame is what made it look wrong.

   Now nothing is laid out during the move: live-demo.js measures the start and
   the end of each part and sets both, and the browser interpolates each one
   independently. The pill is the anchor — it never fades, and its text never
   reflows, because its width is animated rather than derived.
   ================================================================== */

.ch-demo__morph {
	position: fixed;
	z-index: 100002;
	box-sizing: border-box;
	overflow: hidden;
	pointer-events: none;
	border-radius: 999px;
	background: #fff;
	box-shadow: 0 18px 50px rgba(24, 40, 80, 0.18);
	transition:
		left 880ms cubic-bezier(0.45, 0, 0.12, 1),
		top 880ms cubic-bezier(0.45, 0, 0.12, 1),
		width 880ms cubic-bezier(0.45, 0, 0.12, 1),
		height 880ms cubic-bezier(0.45, 0, 0.12, 1),
		border-radius 700ms cubic-bezier(0.45, 0, 0.12, 1),
		background-color 660ms ease 120ms,
		box-shadow 660ms ease;
}

.ch-demo__morph[hidden] {
	display: none;
}

.ch-demo__morph > * {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	white-space: nowrap;
}

/* ---- The pill: the one continuous thing -------------------------------- */

.ch-demo__morph-pill {
	gap: 10px;
	height: 48px;
	padding: 0 18px;
	box-sizing: border-box;
	border-radius: 999px;
	background: transparent;
	border: 1px solid transparent;
	color: #101322;
	font-size: 15px;
	transition:
		left 880ms cubic-bezier(0.45, 0, 0.12, 1),
		top 880ms cubic-bezier(0.45, 0, 0.12, 1),
		width 880ms cubic-bezier(0.45, 0, 0.12, 1),
		background-color 620ms ease 100ms,
		border-color 620ms ease 100ms,
		color 620ms ease 100ms;
}

.ch-demo__morph-url {
	overflow: hidden;
	text-overflow: ellipsis;
}

.ch-demo__morph-dot {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: #2464e8;
	flex: 0 0 auto;
	opacity: 0;
	transition: opacity 280ms ease 400ms;
}

.ch-demo__morph-scheme {
	color: #9aa1b4;
	font-family: var(--font-mono, ui-monospace, monospace);
	font-size: 14px;
	flex: 0 0 auto;
	transition: opacity 240ms ease;
}

.ch-demo__morph-go {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	border-radius: 50%;
	flex: 0 0 auto;
	margin-left: auto;
	color: #8e93a6;
	opacity: 0;
	transition: opacity 280ms ease 400ms;
}

/* ---- The furniture that belongs to the destination --------------------- */

.ch-demo__morph-logo,
.ch-demo__morph-back,
.ch-demo__morph-note,
.ch-demo__morph-ctl {
	gap: 12px;
	opacity: 0;
	transition: opacity 320ms ease 440ms;
}

.ch-demo__morph-logo img {
	width: 152px;
	height: auto;
	display: block;
	transition: width 560ms ease;
}

/*
 * The demo's bar is the site's own header (renderer, 2026-10-23), so its parts
 * are this theme's: Back is .ch-btn-ghost, the label and the clock are the
 * renderer's light chips. Sizes are the renderer's, measured.
 */
.ch-demo__morph-back {
	gap: 7px;
	height: 36px;
	padding: 0 16px;
	box-sizing: border-box;
	border-radius: 999px;
	border: 1px solid var(--border-strong);
	color: var(--text);
	font-size: 14px;
	font-weight: 600;
}

/* Back is the arrow alone at 640 and below, as in the frame (38 wide). */
.ch-demo__morph-back.is-bare {
	padding: 0 10px; /* 1 + 10 + 16 + 10 + 1 = 38 */
}

.ch-demo__morph-back.is-bare .ch-demo__morph-back-label {
	display: none;
}

.ch-demo__morph-note {
	gap: 6px;
	height: 32px;
	padding: 0 13px;
	box-sizing: border-box;
	border-radius: 999px;
	border: 1px solid rgba(16, 24, 48, 0.1);
	background: rgba(17, 17, 26, 0.04);
	color: var(--text-2);
	font-size: 13px;
	font-weight: 600;
}

.ch-demo__morph-note svg {
	color: var(--accent);
	flex: 0 0 auto;
}

.ch-demo__morph-timer {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	height: 31px;
	padding: 0 13px;
	box-sizing: border-box;
	border-radius: 999px;
	border: 1px solid var(--border-strong);
	background: var(--input);
	color: var(--text-2);
	font-size: 13px;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

.ch-demo__morph-timer::before {
	content: "";
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--accent);
}

/* ---- Landed ------------------------------------------------------------ */

/*
 * Dissolves over the real bar rather than being swapped for it, so the last
 * few pixels of difference between a replica and the thing it copies are never
 * on screen at the same moment as the original.
 */
.ch-demo__morph.is-gone {
	opacity: 0;
	transition: opacity 420ms ease;
}

/* Lands as the demo's bar: the site header's white pill on its lavender
   ground, which this approximates with one near-white fill — it dissolves
   into the real thing a moment later. */
.ch-demo__morph.is-end {
	border-radius: 0;
	background: #f7f8fe;
	box-shadow: inset 0 -1px 0 rgba(16, 24, 48, 0.06);
}

.ch-demo__morph.is-end .ch-demo__morph-logo,
.ch-demo__morph.is-end .ch-demo__morph-back,
.ch-demo__morph.is-end .ch-demo__morph-note,
.ch-demo__morph.is-end .ch-demo__morph-ctl,
.ch-demo__morph.is-end .ch-demo__morph-dot,
.ch-demo__morph.is-end .ch-demo__morph-go {
	opacity: 1;
}

.ch-demo__morph.is-end .ch-demo__morph-scheme {
	opacity: 0;
}

.ch-demo__morph.is-end .ch-demo__morph-pill {
	background: var(--input);
	border-color: var(--border-strong);
	color: var(--text);
}

@media (prefers-reduced-motion: reduce) {
	.ch-demo__morph,
	.ch-demo__morph * {
		transition-duration: 160ms !important;
		transition-delay: 0ms !important;
	}
}
