/* Voice Lab — scoped to .vl- so nothing here reaches the rest of the site. */

.vl-wrap {
	padding-top: clamp(1.25rem, 3vw, 2rem);
	padding-bottom: 7rem;
	--vl-ink: #0e0e0e;
	--vl-gray: #6f6c68;
	--vl-line: #dcd8d2;
	--vl-red: #d92e20;
	--vl-i0: #2f6df0;
	--vl-i1: #12a45f;
	--vl-i2: #8b46d6;
	--vl-i3: #e0a021;
	
	max-width: 1180px;
	margin: 0 auto;
	font-variant-numeric: tabular-nums;
}

/* ---------- the overture: display type, full bleed, then it leaves ---------- */

.vl-overture {
	position: fixed;
	inset: 0;
	z-index: 50;
	background: #fff;
	display: flex;
	flex-direction: column;
	justify-content: center;
	padding: 0 clamp( 1rem, 4vw, 3rem );
	pointer-events: none;
	transition: opacity 0.7s ease, transform 0.9s cubic-bezier( 0.16, 1, 0.3, 1 );
}

.vl-overture__line {
	display: block;
	font-size: 15.5vw;
	line-height: 0.82;
	font-weight: 300;
	letter-spacing: -0.045em;
	color: var( --vl-ink );
	white-space: nowrap;
}
.vl-overture__line--1 { font-size: 15.5vw; }
.vl-overture__line--2 { font-size: 15.5vw; }

.vl-overture__foot {
	position: absolute;
	left: clamp( 1rem, 4vw, 3rem );
	bottom: clamp( 1.5rem, 5vw, 3rem );
	font-size: 0.62rem;
	letter-spacing: 0.26em;
	text-transform: uppercase;
	color: var( --vl-gray );
}

.vl-overture.is-gone {
	opacity: 0;
	transform: translateY( -4vh ) scale( 0.985 );
}

@media (max-width: 640px) {
	.vl-overture__line { font-size: 17vw; }
}

/* ---------- control bar: a floating pill, same geometry as the work filter ---------- */

.vl-bar {
	position: fixed;
	bottom: 2rem;
	left: 50%;
	transform: translateX(-50%);
	z-index: 52;
	display: inline-flex;
	align-items: center;
	gap: 0.9rem;
	max-width: calc(100vw - 2rem);
	background: var(--vl-ink);
	color: #fff;
	border-radius: 2rem;
	padding: 0.8rem 1.7rem;
	isolation: isolate;
}

/* A radial burst behind the pill. Idle it breathes; live it takes the colour of
   whoever holds the turn, so the page tells you when to talk without words. */
.vl-bar::before {
	content: "";
	position: absolute;
	inset: -34px;
	border-radius: 999px;
	z-index: -1;
	pointer-events: none;
	background: radial-gradient( closest-side, var( --vl-glow, rgba( 14, 14, 14, 0.5 ) ), transparent 72% );
	filter: blur( 20px );
	opacity: 0.5;
	transition: opacity 0.45s ease, background 0.45s ease;
	animation: vl-breathe 3.4s ease-in-out infinite;
}
.vl-bar.is-live::before { opacity: 1; animation-duration: 2.4s; }

/* Blue while it is listening to you, green while it is talking. Red reads as
   an alarm on a live interface, which is the wrong signal entirely. */
.vl-bar.is-listening { --vl-glow: #2f6df0; }
.vl-bar.is-speaking  { --vl-glow: #12a45f; }

@keyframes vl-breathe {
	0%, 100% { transform: scale( 0.94 ); }
	50%      { transform: scale( 1.06 ); }
}

/* Parked before it can overlap the footer, the way the work filter behaves. */
.vl-bar.is-parked {
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transform: translateX(-50%) translateY(1.5rem);
}
/* The opening: one object, centred and large. Clicking it shrinks it into its
   working position and the record builds itself underneath. */
.vl-bar {
	transition:
		transform 0.85s cubic-bezier( 0.16, 1, 0.3, 1 ),
		padding 0.85s cubic-bezier( 0.16, 1, 0.3, 1 ),
		opacity 0.28s ease,
		visibility 0.28s;
}

.vl-bar.is-hero {
	transform: translate( -50%, -42vh ) scale( 2.6 );
	padding: 0.7rem 1.5rem;
}
.vl-bar.is-hero::before { inset: -22px; }

/* Everything else waits offstage until the conversation starts. */
.vl-cols {
	transition: opacity 0.7s ease 0.35s, transform 0.7s cubic-bezier( 0.16, 1, 0.3, 1 ) 0.35s;
}
.vl-wrap.is-intro { min-height: 74vh; }
.vl-wrap.is-intro .vl-cols {
	opacity: 0;
	transform: translateY( 26px );
	pointer-events: none;
}

.vl-btn {
	font: inherit;
	font-size: 1rem;
	border: 0;
	background: transparent;
	color: #fff;
	padding: 0.3rem 0.2rem;
	border-radius: 999px;
	cursor: pointer;
	white-space: nowrap;
}
.vl-btn:disabled { opacity: 0.45; cursor: default; }
.vl-btn--ghost {
	border: 1px solid rgba( 255, 255, 255, 0.4 );
	padding: 0.3rem 0.9rem;
	font-size: 0.85rem;
}
.vl-btn--ghost:hover { border-color: #fff; }

.vl-status {
	font-size: 0.85rem;
	color: rgba( 255, 255, 255, 0.7 );
	display: inline-flex;
	align-items: center;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 46vw;
}

.vl-eq { display: none; align-items: flex-end; gap: 3px; height: 1.05rem; }
.vl-eq.is-on { display: inline-flex; }
.vl-eq span {
	display: block; width: 3px; border-radius: 1px;
	background: rgba( 255, 255, 255, 0.55 ); height: 18%;
	transform-origin: bottom;
	transition: height 90ms linear, background-color 220ms ease;
}
.vl-eq.is-speaking span { background: #12a45f; }
.vl-eq.is-auto span { animation: vl-eq 1.05s ease-in-out infinite; }
.vl-eq.is-auto span:nth-child(2) { animation-delay: 0.13s }
.vl-eq.is-auto span:nth-child(3) { animation-delay: 0.26s }
.vl-eq.is-auto span:nth-child(4) { animation-delay: 0.39s }
.vl-eq.is-auto span:nth-child(5) { animation-delay: 0.52s }
@keyframes vl-eq { 0%,100% { height: 18% } 50% { height: 100% } }

.vl-hint { color: var(--vl-gray); font-weight: 300; margin: 0; }

/* ---------- two columns: the record, and the structures ---------- */

.vl-cols { display: grid; grid-template-columns: minmax(0,1fr); gap: clamp(2.5rem,6vw,5rem); align-items: start; }
@media (min-width: 900px) { .vl-cols { grid-template-columns: 300px minmax(0,1fr); } }

.vl-sec-label {
	font-size: 0.66rem; letter-spacing: 0.2em; text-transform: uppercase;
	font-weight: 500; color: var(--vl-gray); margin: 0 0 1.6rem;
	padding-bottom: 0.9rem; border-bottom: 1px solid var(--vl-ink);
	display: flex; justify-content: space-between; gap: 1rem;
}
.vl-sec-label span { color: var(--vl-ink); font-variant-numeric: tabular-nums; }

/* ---------- findings: tiny label, the answer set large ---------- */

.vl-findings { margin: 0; }

.vl-finding {
	display: block;
	padding: 1.1rem 0;
	border-bottom: 1px solid var(--vl-line);
}
.vl-finding dt {
	display: block;
	font-size: 0.6rem; letter-spacing: 0.24em; text-transform: uppercase;
	color: var(--vl-gray); font-weight: 500;
}
.vl-finding dd {
	display: block; margin: 0.45rem 0 0;
	font-size: 1.02rem; font-weight: 300; line-height: 1.35; letter-spacing: -0.011em;
}
.vl-finding.is-set { animation: vl-in 0.4s ease both; }
.vl-finding.is-set dd { color: var(--vl-ink); }
.vl-pending { color: #c4bfb8; font-size: 0.86rem; }

@keyframes vl-in { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } }

/* ---------- contact ---------- */

.vl-contact { margin-top: 2.5rem; padding: 1.4rem 0 0; border-top: 2px solid var(--vl-ink); animation: vl-in 0.4s ease both; }
.vl-contact__label { font-size: 0.6rem; letter-spacing: 0.24em; text-transform: uppercase; color: var(--vl-gray); margin: 0 0 0.7rem; font-weight: 500; }
.vl-contact__line { margin: 0; font-size: 1.3rem; font-weight: 300; letter-spacing: -0.015em; }
.vl-contact__email { color: var(--vl-gray); font-size: 1rem; }

/* ---------- structures ---------- */

.vl-solo { padding: 2rem 0 0; border-top: 2px solid var(--vl-ink); }
.vl-solo__label { font-size: 0.6rem; letter-spacing: 0.24em; text-transform: uppercase; color: var(--vl-gray); margin: 0 0 0.6rem; font-weight: 500; }
.vl-solo__name { font-size: clamp(2.6rem, 7vw, 5rem); font-weight: 300; letter-spacing: -0.04em; line-height: 0.98; margin: 0 0 1.4rem; }

.vl-stripe {
	border-top: 1px solid var(--vl-ink);
	padding: clamp(2.25rem, 4.5vw, 3.5rem) 0;
	transition: opacity 0.35s ease;
	animation: vl-in 0.45s ease both;
}
.vl-stripe.is-dimmed { opacity: 0.24; }

/* The call inverts. Nothing else on the page is black, so it cannot be missed. */
.vl-stripe.is-chosen {
	background: var(--vl-ink);
	color: #fff;
	border-top-color: var(--vl-ink);
	padding: clamp(2rem, 4vw, 3rem) clamp(1.4rem, 3vw, 2.5rem);
	margin: 0.5rem 0;
}
.vl-stripe.is-chosen .vl-stripe__label,
.vl-stripe.is-chosen .vl-brand__name { color: #fff; }
.vl-stripe.is-chosen .vl-numeral,
.vl-stripe.is-chosen .vl-brand__note { color: rgba( 255, 255, 255, 0.55 ); }
.vl-stripe.is-chosen .vl-stripe__blurb { color: rgba( 255, 255, 255, 0.8 ); }
.vl-stripe.is-chosen .vl-brand::before { background: #fff; }
.vl-stripe.is-chosen .vl-brand--parent::before { background: var(--vl-red); }

.vl-stripe__head { display: flex; align-items: baseline; gap: 1rem; margin-bottom: 1.75rem; }

.vl-numeral {
	font-size: 0.6rem; letter-spacing: 0.26em; color: var(--vl-gray);
	min-width: 2.2rem;
}

.vl-stripe__label {
	font-size: clamp(2rem, 5vw, 3.4rem);
	font-weight: 300; letter-spacing: -0.042em; line-height: 0.95;
	text-transform: none; color: var(--vl-ink); margin: 0;
}

.vl-badge {
	background: var(--vl-red); color: #fff; border-radius: 999px;
	padding: 0.2rem 0.7rem; font-size: 0.58rem; letter-spacing: 0.2em; text-transform: uppercase;
	align-self: center;
}

.vl-brands { list-style: none; margin: 0 0 1.75rem; padding: 0; display: flex; flex-wrap: wrap; gap: 1.4rem 3.5rem; }

.vl-brand { position: relative; padding-left: 1.05rem; }
.vl-brand::before {
	content: ""; position: absolute; left: 0; top: 0.62em;
	width: 0.4rem; height: 0.4rem; border-radius: 50%; background: var(--vl-ink);
}
.vl-brand--parent::before { background: var(--vl-red); }
.vl-brand--ghost::before { background: #c9c4bd; }
.vl-brand--indie0::before,
.vl-brand--indie1::before,
.vl-brand--indie2::before,
.vl-brand--indie3::before { background: var(--vl-ink); }
.vl-brand--ghost { opacity: 0.4; }

.vl-brand__name { display: block; font-size: clamp(1.25rem, 2.3vw, 1.9rem); font-weight: 300; letter-spacing: -0.025em; line-height: 1.1; }
.vl-brand__note { display: block; font-size: 0.6rem; letter-spacing: 0.22em; text-transform: uppercase; color: var(--vl-gray); margin-top: 0.4rem; }
.vl-brand__flag {
	display: inline-block; margin-top: 0.5rem; font-size: 0.6rem; letter-spacing: 0.14em; text-transform: uppercase;
	color: var(--vl-red); border: 1px solid var(--vl-red); border-radius: 999px; padding: 0.14rem 0.55rem;
}

.vl-stripe__blurb { margin: 0; font-size: 0.92rem; font-weight: 300; line-height: 1.7; color: var(--vl-gray); max-width: 54ch; }

@media (prefers-reduced-motion: reduce) {
	.vl-stripe, .vl-finding.is-set, .vl-contact { animation: none; }
	.vl-bar::before { animation: none; }
	.vl-bar, .vl-cols, .vl-overture { transition: opacity 0.2s ease; }
	.vl-bar.is-hero { transform: translate( -50%, -42vh ) scale( 1.9 ); }
	.vl-eq.is-auto span { animation: none; height: 55%; }
	.vl-eq span { transition: none; }
}


/* ---------- the stage ----------------------------------------------------
   Once the overture leaves, the board stops being a document that grows
   downward and becomes a fixed whiteboard. Everything is drawn inside this
   frame, so nothing the conversation adds can push the last thing said
   below the fold. The frame is sized, the contents fit to it. */

.vl-wrap:not(.is-intro) .vl-cols {
	height: clamp( 360px, 64vh, 700px );
	align-items: stretch;
}

.vl-wrap:not(.is-intro) .vl-cols > * {
	min-height: 0;              /* a grid child will not shrink without this */
	display: flex;
	flex-direction: column;
}

.vl-wrap:not(.is-intro) .vl-findings,
.vl-wrap:not(.is-intro) #vl-board {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	scrollbar-width: thin;
}

/* Four structures, four equal rows. They compress to fit rather than run on. */
#vl-board.is-full {
	display: grid;
	grid-auto-rows: 1fr;
	gap: 0;
	overflow: hidden;
}
#vl-board.is-full .vl-stripe {
	padding: clamp( 0.45rem, 1.5vh, 1.5rem ) 0;
	min-height: 0;
	overflow: hidden;
	animation: none;            /* the rows arrive together; no stagger here */
}
#vl-board.is-full .vl-stripe__head { margin-bottom: clamp( 0.3rem, 1vh, 1rem ); }
#vl-board.is-full .vl-brands { margin-bottom: clamp( 0.25rem, 0.9vh, 1rem ); }
#vl-board.is-full .vl-stripe__blurb {
	font-size: 0.9rem; line-height: 1.5;
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* The empty whiteboard, and the moment a name lands on it. Both live in the
   middle of the frame rather than at the top of a column. */
.vl-solo, .vl-empty {
	height: 100%;
	display: flex; flex-direction: column;
	align-items: flex-start; justify-content: center;
	border-top: 0;
	padding: 0;
}
.vl-empty { color: #b9b4ad; }
.vl-empty__rule { width: 100%; height: 1px; background: var( --vl-line ); margin: 1.2rem 0 0; }

.vl-solo__name {
	font-size: clamp( 2.6rem, 8vw, 6.4rem );
	line-height: 0.95;
	letter-spacing: -0.035em;
	font-weight: 300;
	margin: 0.2rem 0 0.9rem;
	animation: vl-land 0.7s cubic-bezier( 0.16, 1, 0.3, 1 ) both;
}
@keyframes vl-land {
	from { opacity: 0; transform: translateY( 0.18em ) scale( 0.96 ); }
	to   { opacity: 1; transform: none; }
}

/* ---------- glow, centred on the button rather than the whole bar -------- */

.vl-bar::before {
	left: 50%;
	width: 15rem; height: 15rem;
	margin-left: -7.5rem; margin-top: -7.5rem;
	top: 50%;
	background: radial-gradient( closest-side, var( --vl-glow, rgba(14,14,14,0.45) ), transparent 70% );
}

@media (prefers-reduced-motion: reduce) {
	.vl-solo__name { animation: none; }
}


/* ---------- glow strictly behind the pill --------------------------------
   .vl-bar carries a transform and a z-index, so it is its own stacking
   context and a z-index:-1 child still paints over its background. The fix
   is to stop the bar painting a background at all: ::before is the glow,
   ::after is the black pill laid on top of it, and the controls sit above
   both. Paint order inside the bar is then glow, pill, content. */

.vl-bar { background: transparent; }

.vl-bar::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 2rem;
	background: var( --vl-ink );
	pointer-events: none;
}

.vl-bar > * { position: relative; z-index: 1; }

.vl-bar::before {
	z-index: auto;              /* now simply the first positioned layer */
	inset: -46px;
	filter: blur( 26px );
	opacity: 0.65;
}
.vl-bar.is-live::before { opacity: 1; }

/* ---------- a pixel meter rather than a smooth one -----------------------
   The bars are cut into discrete cells by a hard-stop gradient, so a level
   reads as a stack of lit squares that pop on and off instead of a bar
   that slides. Square ends, wider gutters, no sub-pixel travel. */

.vl-eq { gap: 2px; height: 1.15rem; }

.vl-eq span {
	width: 4px;
	border-radius: 0;
	background:
		repeating-linear-gradient(
			to top,
			currentColor 0, currentColor 3px,
			transparent 3px, transparent 5px
		);
	color: rgba( 255, 255, 255, 0.6 );
	transition: height 60ms steps( 4, end ), color 220ms ease;
}

.vl-eq.is-speaking span { background: none; color: #12a45f; }
.vl-eq.is-speaking span,
.vl-eq span {
	background:
		repeating-linear-gradient(
			to top,
			currentColor 0, currentColor 3px,
			transparent 3px, transparent 5px
		);
}

@media (prefers-reduced-motion: reduce) {
	.vl-eq span { transition: none; }
}


/* ---------- no halo until there is something to signal -------------------
   The idle glow fell back to a near-black, so at rest the pill wore a dark
   smudge that read as a shadow. There is nothing to indicate before the
   conversation starts, so there should be no light at all: the glow only
   exists while it is listening or speaking. */

.vl-bar::before { opacity: 0; animation: none; }
.vl-bar.is-hero::before { opacity: 0; }
.vl-bar.is-live::before { opacity: 1; animation: vl-breathe 2.4s ease-in-out infinite; }

/* ---------- the overture, at full weight ---------------------------------
   Light at that scale reads as elegant. Bold reads as a statement, which is
   what an opening frame is for. */

.vl-overture__line {
	font-size: 19vw;
	line-height: 0.78;
	font-weight: 500;
	letter-spacing: -0.045em;
}

.vl-overture__foot {
	font-weight: 400;
	letter-spacing: 0.16em;
}


/* ---------- hover ---------------------------------------------------------
   The pill is the only thing on the page you can press, so it has to answer
   the cursor. The pill itself grows a few pixels and lifts a shade, and a
   restrained neutral halo comes up behind it. Only while it is pressable:
   once the conversation is live the colour is carrying meaning and a hover
   effect would compete with it. */

.vl-bar::after { transition: inset 0.22s ease, background-color 0.22s ease; }

.vl-bar:not(.is-live):hover::after,
.vl-bar:not(.is-live):focus-within::after {
	inset: -4px;
	background: #242424;
}

.vl-bar:not(.is-live):hover::before,
.vl-bar:not(.is-live):focus-within::before {
	--vl-glow: rgba( 255, 255, 255, 0.85 );
	opacity: 0.5;
}

/* The label inside should feel pressed too, without moving the layout. */
.vl-bar:not(.is-live):hover .vl-btn { background: transparent; }

@media (hover: none) {
	.vl-bar:not(.is-live):hover::after { inset: 0; background: var( --vl-ink ); }
	.vl-bar:not(.is-live):hover::before { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
	.vl-bar::after { transition: background-color 0.22s ease; }
	.vl-bar:not(.is-live):hover::after { inset: 0; }
}


/* ---------- the stage takes the whole screen -----------------------------
   Sizing the board to a fraction of the viewport does not help when the
   frame starts below a header, a subhead and two column labels: 64vh of
   well-behaved layout still begins at the fold. So for the length of the
   conversation the lab stops being a section of a page and becomes the
   screen. Nothing scrolls, nothing is below anything, and the name lands
   in the middle where you are already looking. */

.vl-wrap.is-stage {
	position: fixed;
	inset: 0;
	z-index: 48;
	max-width: none;
	margin: 0;
	background: #fff;
	display: flex;
	flex-direction: column;
	padding: clamp( 1rem, 2.6vw, 2.4rem );
	padding-bottom: clamp( 6rem, 13vh, 8.5rem );   /* the pill lives down there */
	overflow: hidden;
	animation: vl-stage-in 0.5s cubic-bezier( 0.16, 1, 0.3, 1 ) both;
}
@keyframes vl-stage-in { from { opacity: 0 } to { opacity: 1 } }

.vl-wrap.is-stage .vl-cols {
	flex: 1 1 auto;
	height: auto;
	min-height: 0;
}
.vl-wrap.is-stage .vl-col {
	min-height: 0;
	display: flex;
	flex-direction: column;
}

/* ---------- the rail ------------------------------------------------------
   Instrument chrome: what this is, and whether it is hearing you. */

.vl-rail {
	display: none;
	align-items: baseline;
	justify-content: space-between;
	gap: 1rem;
	font-size: 0.66rem;
	letter-spacing: 0.15em;
	text-transform: uppercase;
	font-weight: 500;
	color: var( --vl-gray );
	padding-bottom: clamp( 0.7rem, 1.6vh, 1.2rem );
	border-bottom: 1px solid var( --vl-ink );
	margin-bottom: clamp( 0.9rem, 2.4vh, 2rem );
	flex: 0 0 auto;
}
.vl-wrap.is-stage .vl-rail { display: flex; }
.vl-rail__mark { color: var( --vl-ink ); }
.vl-rail__state { display: inline-flex; align-items: center; gap: 0.5rem; }
.vl-rail__dot {
	width: 0.42rem; height: 0.42rem; border-radius: 50%;
	background: #c9c4bd; transition: background-color 0.3s ease;
}
.vl-wrap.is-listening .vl-rail__dot { background: #2f6df0; }
.vl-wrap.is-speaking .vl-rail__dot { background: #12a45f; }

/* ---------- tabs, when two columns will not both fit ---------------------- */

.vl-tabs { display: none; gap: 0; margin-bottom: 1rem; flex: 0 0 auto; }
.vl-tab {
	font: inherit;
	font-size: 0.66rem; letter-spacing: 0.15em; text-transform: uppercase; font-weight: 500;
	background: none; border: 0; border-bottom: 2px solid transparent;
	color: var( --vl-gray ); padding: 0.5rem 1.1rem 0.45rem; cursor: pointer;
}
.vl-tab[aria-selected="true"] { color: var( --vl-ink ); border-bottom-color: var( --vl-ink ); }

@media ( max-width: 899px ), ( max-height: 720px ) {
	.vl-wrap.is-stage .vl-tabs { display: flex; }
	.vl-wrap.is-stage .vl-cols { grid-template-columns: minmax( 0, 1fr ); }
	.vl-wrap.is-stage[data-panel="board"]    .vl-col:first-child { display: none; }
	.vl-wrap.is-stage[data-panel="findings"] .vl-col:last-child  { display: none; }
	.vl-wrap.is-stage .vl-sec-label { display: none; }
}

/* While the stage is up the pill is furniture on it, not a page element that
   has to dodge a footer. */
.vl-wrap.is-stage ~ * .vl-bar,
.vl-bar.is-staged.is-parked { transform: translateX( -50% ); }

@media (prefers-reduced-motion: reduce) {
	.vl-wrap.is-stage { animation: none; }
}


/* ---------- one authoritative glow ---------------------------------------
   Earlier passes left a width, a height and negative margins behind while a
   later inset took over positioning, so the halo sat one margin up and to
   the left of the pill. Everything is reset here and set once. */

.vl-bar::before {
	content: "";
	position: absolute;
	top: auto; right: auto; bottom: auto; left: auto;
	inset: -48px;
	width: auto; height: auto;
	margin: 0;
	border-radius: 999px;
	pointer-events: none;
	filter: blur( 28px );
	background: radial-gradient( closest-side, var( --vl-glow, transparent ), transparent 72% );
	opacity: 0;
	transition: opacity 0.45s ease, background 0.45s ease;
	animation: none;
}
.vl-bar.is-live::before { opacity: 1; animation: vl-breathe 2.4s ease-in-out infinite; }
.vl-bar.is-hero::before { opacity: 0; }

/* ---------- the record keeps its own counsel ------------------------------
   Eight rows of "not yet" announce every question before it is asked and
   make the column tall enough to push the board off screen. A finding now
   appears only once it exists. */

.vl-record-empty {
	border-top: 1px solid var( --vl-line );
	padding-top: 0.9rem;
	font-size: 0.8rem;
	color: #b9b4ad;
	font-weight: 300;
}


/* ---------- the stage sits above the site, not inside it -----------------
   At z-index 48 the sticky header and the admin bar both painted over the
   sheet, so the rail was buried behind the nav. The stage is the site while
   a conversation runs: it goes above everything except the admin bar, and
   the pill rides one step above it. */

.vl-wrap.is-stage { z-index: 99990; }
.vl-bar.is-staged { z-index: 99991; }

/* Logged in, the admin bar keeps its strip of screen. */
body.admin-bar .vl-wrap.is-stage { top: 32px; }
@media ( max-width: 782px ) {
	body.admin-bar .vl-wrap.is-stage { top: 46px; }
}


/* ---------- the canvas ----------------------------------------------------
   The rest of the site does its activities inside soft rounded boxes, so the
   board gets the same container: a warm gray sheet the conversation draws
   on. The record column stays bare — it is marginalia, not the show. */

#vl-board {
	background: #f4f2ef;
	border-radius: 1.5rem;
	padding: clamp( 1.2rem, 3vw, 2.5rem );
}

/* The rules between stripes read differently on gray. */
#vl-board .vl-stripe { border-top-color: #e2ded7; }
#vl-board .vl-solo, #vl-board .vl-empty { border-top: 0; }

/* ---------- the name, kinetic ---------------------------------------------
   The company name is the show. It lands enormous in the middle of the
   canvas, and when the four structures arrive it does not vanish — it
   shrinks up into the corner and stays, so the whole sequence reads as one
   continuous object settling into place. */

.vl-solo__name {
	font-size: clamp( 3.2rem, 10.5vw, 8.5rem );
	font-weight: 500;
	letter-spacing: -0.04em;
	line-height: 0.92;
}

.vl-board-parent {
	margin: 0 0 clamp( 0.6rem, 1.6vh, 1.4rem );
	font-size: clamp( 1.4rem, 2.6vw, 2.1rem );
	font-weight: 500;
	letter-spacing: -0.03em;
	line-height: 1;
	transform-origin: left top;
	animation: vl-settle 0.65s cubic-bezier( 0.16, 1, 0.3, 1 ) both;
}
@keyframes vl-settle {
	from { transform: scale( 2.6 ); opacity: 0.45; }
	to   { transform: none; opacity: 1; }
}

/* The header row is content-height; the four structures share the rest. */
#vl-board.is-full { grid-template-rows: auto; grid-auto-rows: 1fr; }

@media (prefers-reduced-motion: reduce) {
	.vl-board-parent { animation: none; }
}


/* ---------- hierarchy on the canvas --------------------------------------
   The model names had grown into display type, which put the least important
   words on the board above the company itself and pushed the brand names and
   explanations out of the clipped rows. Order restored, top down:

     1. The company name          — the subject, largest persistent element
     2. The brand names           — the content, what the structures rearrange
     3. What each model means     — one visible line, always
     4. The model's category name — a small filing label
*/

#vl-board .vl-board-parent {
	font-size: clamp( 1.7rem, 3.2vw, 2.6rem );
	padding-bottom: clamp( 0.5rem, 1.4vh, 1rem );
	border-bottom: 1px solid #e2ded7;
}

#vl-board .vl-stripe__head { gap: 0.55rem; align-items: baseline; }
#vl-board .vl-stripe__label {
	font-size: 0.72rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	font-weight: 500;
	font-style: normal;
	color: var( --vl-gray );
}
#vl-board .vl-numeral {
	font-size: 0.66rem;
	letter-spacing: 0.1em;
	min-width: 1.1rem;
	padding-top: 0;
}

#vl-board .vl-brand__name {
	font-size: clamp( 1.15rem, 1.9vw, 1.6rem );
	font-weight: 500;
	letter-spacing: -0.02em;
}
#vl-board .vl-brand__note { font-size: 0.68rem; }

/* The explanation is the answer to "what am I looking at". It stays visible. */
#vl-board .vl-stripe__blurb,
#vl-board.is-full .vl-stripe__blurb {
	display: block;
	font-size: 0.82rem;
	line-height: 1.45;
	color: var( --vl-gray );
	max-width: 70ch;
	overflow: visible;
	-webkit-line-clamp: unset;
}

/* With the labels back at label size the rows no longer need to clip. */
#vl-board.is-full .vl-stripe { overflow: visible; }

/* The chosen structure still gets its weight, at rank not size. */
#vl-board .vl-stripe.is-chosen .vl-stripe__label { color: #fff; }


/* ---------- rows own their height ----------------------------------------
   Forcing four equal 1fr rows while letting their content overflow visibly
   meant every row taller than its quarter spilled into the one below. The
   equal-rows idea goes: each structure is as tall as what it says, tight
   enough that all four fit, and the canvas scrolls only as a last resort. */

#vl-board.is-full {
	display: block;
	overflow-y: auto;
	scrollbar-width: thin;
}

#vl-board.is-full .vl-stripe {
	padding: clamp( 0.55rem, 1.6vh, 1.1rem ) 0;
	overflow: visible;
}
#vl-board.is-full .vl-stripe__head { margin-bottom: clamp( 0.25rem, 0.8vh, 0.55rem ); }
#vl-board.is-full .vl-brands { margin-bottom: clamp( 0.2rem, 0.7vh, 0.5rem ); gap: 0.35rem 2.2rem; }
#vl-board.is-full .vl-brand__note { margin-top: 0.05rem; }
#vl-board.is-full .vl-board-parent { margin-bottom: clamp( 0.4rem, 1.2vh, 0.9rem ); }


/* The row-tightening pass zeroed horizontal padding on every stripe, which
   on the inverted bar pressed the numeral against its own edge. The call
   keeps its room. */
#vl-board.is-full .vl-stripe.is-chosen {
	padding: clamp( 0.8rem, 2vh, 1.3rem ) clamp( 1.1rem, 2.4vw, 1.8rem );
	border-radius: 0.9rem;
	margin: 0.5rem 0;
}
.vl-stripe.is-chosen .vl-badge { background: #fff; color: var( --vl-ink ); }


/* ---------- the models chart, lightbox style -----------------------------
   The same three diagrams the brand architecture page uses, flashed over
   the stage while the agent names the structures. It leaves on a tap, on
   Escape, or on its own the moment the first finding lands: reference
   material, not a slide to sit through. */

.vl-chartbox {
	position: fixed;
	inset: 0;
	z-index: 99992;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: clamp( 1rem, 4vw, 3rem );
	background: rgba( 14, 14, 14, 0.78 );
	animation: vl-stage-in 0.35s ease both;
	cursor: pointer;
}
.vl-chartbox.is-leaving { opacity: 0; transition: opacity 0.3s ease; }

.vl-chartbox__panel {
	background: #fff;
	border-radius: 1.5rem;
	padding: clamp( 1.2rem, 3vw, 2.2rem );
	max-width: min( 860px, 100% );
	max-height: 100%;
	overflow-y: auto;
	scrollbar-width: thin;
	display: grid;
	gap: clamp( 0.9rem, 2vh, 1.5rem );
	animation: vl-chart-pop 0.45s cubic-bezier( 0.16, 1, 0.3, 1 ) both;
}
@keyframes vl-chart-pop {
	from { transform: translateY( 14px ) scale( 0.97 ); opacity: 0; }
	to   { transform: none; opacity: 1; }
}

.vl-chartbox__title {
	font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase;
	font-weight: 500; color: var( --vl-gray ); margin: 0;
	display: flex; justify-content: space-between; gap: 1rem;
}
.vl-chartbox__hint { font-weight: 400; text-transform: none; letter-spacing: 0; }

.vl-chartbox__fig { margin: 0; }
.vl-chartbox__fig img {
	display: block; width: 100%; height: auto;
	border-radius: 0.6rem; border: 1px solid var( --vl-line );
}

@media (prefers-reduced-motion: reduce) {
	.vl-chartbox, .vl-chartbox__panel { animation: none; }
}


/* ---------- all four structures, one glance ------------------------------
   A stack you scroll is a document; four up at once is a slide. The grid is
   sized so nothing ever scrolls on a laptop, and the agent hides the whole
   thing itself when it is done talking over it. */

.vl-chartbox__panel {
	grid-template-columns: 1fr 1fr;
	gap: clamp( 0.6rem, 1.4vw, 1rem );
	max-width: min( 1080px, 100% );
	overflow: hidden;
}
.vl-chartbox__title { grid-column: 1 / -1; }
.vl-chartbox__fig img { max-height: 34vh; width: 100%; object-fit: contain; background: #0e0e0e; }

/* The fourth structure never got a 2019 diagram, so it is drawn here in the
   same language: black card, white line work, caption underneath. */
.vl-chartbox__endorsed {
	background: #0e0e0e;
	border-radius: 0.6rem;
	border: 1px solid var( --vl-line );
	min-height: min( 34vh, 100% );
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 1.1rem;
	padding: 1.4rem;
	color: #fff;
}
.vl-chartbox__endorsed-brand {
	border: 3px solid #fff;
	padding: 0.7rem 1.6rem;
	font-weight: 600;
	font-size: clamp( 1rem, 1.7vw, 1.4rem );
	letter-spacing: 0.02em;
}
.vl-chartbox__endorsed-sig {
	font-size: 0.72rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: rgba( 255, 255, 255, 0.75 );
}
.vl-chartbox__endorsed-cap {
	font-size: clamp( 0.78rem, 1.2vw, 0.95rem );
	color: #fff;
	text-align: center;
	margin: 0.4rem 0 0;
}

@media ( max-width: 640px ) {
	.vl-chartbox__panel { grid-template-columns: 1fr; overflow-y: auto; }
	.vl-chartbox__fig img { max-height: none; }
}


/* ---------- phones --------------------------------------------------------
   Measured on a 375x812 viewport, staged and live. Two things were broken:
   the opening pill scaled 2.6x came out 407px wide and hung off both edges,
   and the board's last row finished 11px underneath the pill. The only
   scale-down that existed was inside prefers-reduced-motion, which is a
   different question entirely. */

@media ( max-width: 640px ) {

	/* The hero has to fit the screen it opens on. 1.75 keeps the pill about
	   275px wide at 375px, still the only object on the page. */
	/* Sized with type rather than scale: a transform ignores max-width, so a
	   scaled pill runs off both edges of a phone no matter what it is capped
	   at. Same visual weight, but it can actually fit. */
	.vl-bar.is-hero {
		transform: translate( -50%, -38vh ) scale( 1 );
		font-size: 1.15rem;
		padding: 0.95rem 1.7rem;
	}
	.vl-bar.is-hero::before { inset: -26px; }

	/* iOS puts a toolbar over the bottom of a fixed element, and the home
	   indicator sits under that again. Both get their room. */
	.vl-bar { bottom: calc( 1.25rem + env( safe-area-inset-bottom ) ); }

	/* Clear the pill properly: its own height, its offset, and a gap. */
	.vl-wrap.is-stage {
		padding: 0.9rem;
		/* Measured: this leaves the board 8px off the pill, which reads as a
		   collision. A little more keeps them visibly separate. */
		padding-bottom: calc( 8.5rem + env( safe-area-inset-bottom ) );
	}

	/* A little air at the edges for the opening type. */
	.vl-overture__line { font-size: 17vw; }
	.vl-overture { padding: 0 0.6rem; }

	/* The rail is a label, not a headline: let it shrink before it wraps
	   into the status. */
	.vl-rail { font-size: 0.6rem; letter-spacing: 0.1em; gap: 0.6rem; }
	.vl-rail__mark { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
	.vl-rail__state { flex: 0 0 auto; }

	/* Structures need to stay readable when they only get one column. */
	#vl-board { padding: 1rem 0.9rem; border-radius: 1.1rem; }
	#vl-board .vl-board-parent { font-size: 1.5rem; }
	#vl-board .vl-brand__name { font-size: 1.05rem; }
	#vl-board .vl-brands { gap: 0.3rem 1.2rem; }
	#vl-board .vl-stripe__blurb { font-size: 0.78rem; line-height: 1.4; }

	.vl-chartbox { padding: 0.75rem; }
	.vl-chartbox__panel { padding: 0.9rem; border-radius: 1.1rem; }
}

/* Landscape phones: almost no height, so the hero must not run off the top. */
@media ( max-height: 480px ) {
	.vl-bar.is-hero { transform: translate( -50%, -30vh ) scale( 1.5 ); }
	.vl-wrap.is-stage { padding-bottom: calc( 6rem + env( safe-area-inset-bottom ) ); }
	.vl-overture__line { font-size: 12vh; }
}

/* On a phone the browser's own chrome moves; dvh follows it, vh does not. */
@supports ( height: 100dvh ) {
	.vl-wrap.is-stage { height: 100dvh; bottom: auto; }
	body.admin-bar .vl-wrap.is-stage { height: calc( 100dvh - 32px ); }
}


/* ---------- box model ----------------------------------------------------
   The theme does not put box-sizing on everything, so the stage was taking
   its padding OUTSIDE its height: a 812px screen produced a 946px sheet,
   which pushed the last structure 93px underneath the pill. Measured, not
   guessed. The grid row needs telling too, or it sizes to its content and
   overflows the height it was given. */

.vl-wrap.is-stage,
.vl-wrap.is-stage *,
.vl-chartbox,
.vl-chartbox * { box-sizing: border-box; }

.vl-wrap.is-stage .vl-cols { grid-template-rows: minmax( 0, 1fr ); }


/* ---------- paused --------------------------------------------------------
   Held, not ended: the pill stays put, the light goes out and the meter
   stops. Nothing is listening and nothing is talking. */

.vl-bar.is-paused::before { opacity: 0 !important; animation: none !important; }
.vl-bar.is-paused .vl-status { color: rgba( 255, 255, 255, 0.55 ); }

#vl-pause { padding-left: 1.1rem; padding-right: 1.1rem; }

/* On a phone the pill holds End, Pause and a status; keep it inside the
   screen by trimming the controls rather than letting it wrap. */
@media ( max-width: 640px ) {
	.vl-bar { gap: 0.5rem; padding: 0.7rem 1rem; }
	.vl-bar .vl-btn { padding: 0.6rem 0.9rem; font-size: 0.88rem; }
	#vl-pause { padding-left: 0.9rem; padding-right: 0.9rem; }
	.vl-status { font-size: 0.8rem; }
}


/* ---------- the board fits itself ----------------------------------------
   Measured at 1280x720: four structures came to 706px inside a 494px
   canvas, so the fourth was cut off. Tuning the numbers by hand only moves
   the problem, because the content is not fixed: two brands or six, short
   names or long, a flag or none, and every screen a different height.

   So the whole board is sized in em from one root, and the script lowers
   that root until the four fit. Everything keeps its proportions; only the
   overall scale moves. */

#vl-board.is-full {
	font-size: calc( 1rem * var( --vl-fit, 1 ) );
	padding: 1.5em;
}

#vl-board.is-full .vl-board-parent {
	font-size: 2.2em;
	margin-bottom: 0.5em;
	padding-bottom: 0.4em;
}

#vl-board.is-full .vl-stripe { padding: 0.55em 0; }
#vl-board.is-full .vl-stripe__head { margin-bottom: 0.35em; gap: 0.6em; }
#vl-board.is-full .vl-stripe__label { font-size: 0.7em; }
#vl-board.is-full .vl-numeral { font-size: 0.64em; min-width: 1.6em; }

#vl-board.is-full .vl-brands { gap: 0.3em 1.8em; margin-bottom: 0.4em; }
#vl-board.is-full .vl-brand { padding-left: 0.9em; }
#vl-board.is-full .vl-brand::before { width: 0.34em; height: 0.34em; top: 0.62em; }
#vl-board.is-full .vl-brand__name { font-size: 1.3em; line-height: 1.15; }
#vl-board.is-full .vl-brand__note { font-size: 0.64em; margin-top: 0.1em; }
#vl-board.is-full .vl-brand__flag { font-size: 0.6em; margin-top: 0.25em; }

#vl-board.is-full .vl-stripe__blurb { font-size: 0.82em; line-height: 1.45; max-width: 78ch; }

#vl-board.is-full .vl-stripe.is-chosen { padding: 0.8em 1.1em; border-radius: 0.8em; margin: 0.35em 0; }
#vl-board.is-full .vl-badge { font-size: 0.6em; padding: 0.25em 0.8em; }


/* Short windows: shed detail rather than shrink into illegibility. */
#vl-board.is-full.is-tight .vl-stripe__blurb {
	display: -webkit-box;
	-webkit-line-clamp: 1;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
#vl-board.is-full.is-tighter .vl-brand__note { display: none; }
#vl-board.is-full.is-tighter .vl-brands { gap: 0.25em 1.6em; }


/* ---------- quieter board -------------------------------------------------
   Three things were doing work the words already do: a second hairline under
   the company name (its own border plus the first structure's), a dot in
   front of every brand, and the indent that dot required. The names carry
   themselves. */

/* The parent name draws the only rule; the first structure does not add another. */
#vl-board.is-full .vl-stripe:first-of-type { border-top: 0; }

/* No bullets, no indent. Colour coding lived in those dots, but the note
   under each name says the same thing in words, and the dimmed
   invisible-to-customers case still reads as dimmed. */
#vl-board .vl-brand::before { display: none; }
#vl-board .vl-brand { padding-left: 0; }
#vl-board.is-full .vl-brand { padding-left: 0; }


/* ---------- the record column scrolls, visibly ---------------------------
   It always scrolled; nothing said so. A thin rail and a faded bottom edge
   turn a column that appears to end into a column with more in it. */

.vl-findings {
	scrollbar-width: thin;
	scrollbar-color: #cfcac3 transparent;
	overscroll-behavior: contain;
}
.vl-findings::-webkit-scrollbar { width: 6px; }
.vl-findings::-webkit-scrollbar-track { background: transparent; }
.vl-findings::-webkit-scrollbar-thumb { background: #cfcac3; border-radius: 3px; }

/* No mask here: it fades the scrollbar along with the text. The rail below
   does the signalling instead. */
.vl-findings.has-more { -webkit-mask-image: none; mask-image: none; }


/* ---------- the canvas scrolls, visibly ----------------------------------
   fitBoard shrinks the structures until they fit, but it stops at a
   legibility floor. Past that the canvas has to scroll, and it was doing it
   in silence, so a cut-off row just looked broken.

   The fade is a gradient laid over the column rather than a mask on the box:
   a mask would fade the beige itself and leave a pale notch against the
   white behind it. */

#vl-board.is-full {
	scrollbar-width: thin;
	scrollbar-color: #cfcac3 transparent;
	overscroll-behavior: contain;
}
#vl-board.is-full::-webkit-scrollbar { width: 7px; }
#vl-board.is-full::-webkit-scrollbar-track { background: transparent; }
#vl-board.is-full::-webkit-scrollbar-thumb {
	background: #cfcac3;
	border-radius: 4px;
	border: 2px solid #f4f2ef;
}

.vl-col { position: relative; }

.vl-col.has-more::after {
	content: "";
	position: absolute;
	left: 0; right: 0; bottom: 0;
	height: 2.75rem;
	pointer-events: none;
	border-radius: 0 0 1.5rem 1.5rem;
	background: linear-gradient( to top, #f4f2ef 15%, rgba( 244, 242, 239, 0 ) );
}


/* ---------- their site, on the board -------------------------------------
   A mark and a domain the instant the site is named, and the homepage
   itself when the screenshot lands. Sits above the company name, small: it
   is evidence that the thing is paying attention, not the subject. */

.vl-site {
	margin: 0 0 0.9em;
	display: grid;
	grid-template-columns: auto 1fr;
	align-items: center;
	gap: 0.55em;
}
.vl-site__icon {
	width: 1.5em; height: 1.5em;
	border-radius: 0.3em;
	object-fit: contain;
	background: #fff;
}
.vl-site__domain {
	font-size: 0.68em;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var( --vl-gray );
}
.vl-site__shot {
	grid-column: 1 / -1;
	width: 100%;
	max-height: 7.5em;
	object-fit: cover;
	object-position: top center;
	border-radius: 0.5em;
	border: 1px solid #e2ded7;
	margin-top: 0.2em;
	animation: vl-in 0.5s ease both;
}


/* ---------- the site card, sized to the moment ---------------------------
   Before the structures exist there is room to show the homepage properly;
   once four structures are drawing, it steps back to a strip. Cropping from
   the top keeps the header and headline, which is the part that reads as
   "that is us".*/


/* Nothing until it actually loads, so a pending image leaves no gap. */
.vl-site__shot { display: none; }
.vl-site__shot.is-loaded { display: block; }

/* With a card above it, the solo block stops trying to fill the canvas. */


/* ---------- who we are talking to ----------------------------------------
   A thumbnail at the head of the record column: their mark, their domain,
   their homepage. Small on purpose. It is evidence the thing is paying
   attention, and it leaves the canvas entirely to the four structures. */

.vl-site-slot:empty { display: none; }
.vl-site-slot { flex: 0 0 auto; margin-bottom: 1.25rem; }

.vl-site {
	margin: 0;
	display: grid;
	grid-template-columns: auto 1fr;
	align-items: center;
	gap: 0.5rem;
}
.vl-site__icon {
	width: 1.35rem; height: 1.35rem;
	border-radius: 0.25rem;
	object-fit: contain;
	background: #fff;
}
.vl-site__domain {
	font-size: 0.65rem;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var( --vl-gray );
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vl-site__shot {
	grid-column: 1 / -1;
	width: 100%;
	aspect-ratio: 16 / 10;
	object-fit: cover;
	object-position: top center;
	border-radius: 0.5rem;
	border: 1px solid var( --vl-line );
	margin-top: 0.45rem;
	animation: vl-in 0.5s ease both;
}


/* ---------- the drawn rail ------------------------------------------------
   Always visible while the record has more in it, unlike the system
   scrollbar, which on macOS only appears once you are already scrolling. */

.vl-scrollrail {
	position: absolute;
	/* Transparent borders widen the hit target to 15px while the bar you can
	   see stays 3px. A 3px grab target is a scrollbar you fight with. The
	   right offset compensates for the added border so nothing moves. */
	right: calc( -0.55rem - 6px );
	width: 3px;
	border-left: 6px solid transparent;
	border-right: 6px solid transparent;
	background-clip: padding-box;
	border-radius: 2px;
	background: #e6e2db;
	opacity: 0;
	transition: opacity 0.25s ease;
	pointer-events: none;
	touch-action: none;
	cursor: default;
}

/* Only grabbable once there is something to grab. */
.vl-scrollrail.is-on {
	pointer-events: auto;
	cursor: pointer;
}

.vl-scrollrail__thumb { cursor: grab; }
.vl-scrollrail.is-dragging .vl-scrollrail__thumb { cursor: grabbing; }

/* No easing while a finger or cursor is driving it, or the thumb lags the
   pointer and feels broken. */
.vl-scrollrail.is-dragging .vl-scrollrail__thumb { transition: none; }
.vl-scrollrail.is-on { opacity: 1; }

.vl-scrollrail__thumb {
	display: block;
	width: 100%;
	border-radius: 2px;
	background: var( --vl-ink );
	transition: transform 0.12s linear, height 0.12s linear;
}

@media ( max-width: 640px ) { .vl-scrollrail { right: -0.35rem; } }


/* Shown only when a spend ceiling closes the voice side for the day. */
.vl-fallback {
	position: fixed;
	bottom: 6.5rem;
	left: 50%;
	transform: translateX( -50% );
	z-index: 99991;
	margin: 0;
	font-size: 0.9rem;
	color: var( --vl-gray );
	background: #fff;
	padding: 0.5rem 1rem;
	border-radius: 999px;
	border: 1px solid var( --vl-line );
	max-width: calc( 100vw - 2rem );
	text-align: center;
}
.vl-fallback a { color: var( --vl-ink ); }


/* ---------- orb -----------------------------------------------------------
   Takes the meter's place in the pill. The bars stay in the markup as a
   fallback for transports that expose no levels, hidden whenever the orb is
   running. */

.vl-orb {
	display: none;
	width: 34px;
	height: 34px;
	flex: 0 0 auto;
}
.vl-orb.is-on { display: block; }
.vl-orb.is-on ~ .vl-eq,
.vl-bar:has( .vl-orb.is-on ) .vl-eq { display: none; }


/* ---------- the opening, a size smaller ---------------------------------- */

.vl-bar.is-hero { transform: translate( -50%, -42vh ) scale( 1.9 ); }

/* ---------- consent, said before the tap ---------------------------------
   The recording begins when the conversation does, so this belongs on the
   opening screen rather than after the button. Quiet, but not hidden. */

.vl-consent {
	position: fixed;
	left: 50%;
	/* Down at the foot of the screen rather than under the pill. Directly
	   below the pill it lands in the middle of the display type, and a line
	   this quiet cannot compete with letters that size. The bottom strip is
	   empty, so it reads cleanly and still sits in front of the decision. */
	top: auto;
	bottom: 2.4rem;
	transform: translateX( -50% );
	z-index: 51;
	margin: 0;
	max-width: min( 30rem, calc( 100vw - 3rem ) );
	text-align: center;
	font-size: 0.78rem;
	line-height: 1.5;
	color: var( --vl-gray );
	opacity: 1;
	transition: opacity 0.45s ease;
	pointer-events: auto;
}
.vl-consent.is-gone { opacity: 0; pointer-events: none; }
.vl-consent a { color: var( --vl-ink ); text-underline-offset: 0.18em; }

.vl-wrap:not( .is-intro ) ~ .vl-consent,
.vl-consent.is-gone { opacity: 0; }

@media ( max-width: 640px ) {
	.vl-bar.is-hero { transform: translate( -50%, -38vh ) scale( 1 ); }
	.vl-consent { top: auto; bottom: 1.6rem; font-size: 0.72rem; }
}


/* The voice tool cannot serve everyone: this is the equivalent route, and it
   is offered from the start rather than only when a cap is hit. */
.vl-consent__alt {
	display: inline-block;
	margin-top: .35rem;
	color: var( --vl-ink );
}


/* ---------- the way out ---------------------------------------------------
   Shown when someone declines the recording, or asks for the written version.
   A spoken URL is useless; this is the same offer they can actually click. */

.vl-handoff {
	height: 100%;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	gap: .9rem;
}
.vl-handoff__label {
	margin: 0;
	font-size: .7rem;
	letter-spacing: .14em;
	text-transform: uppercase;
	font-weight: 500;
	color: var( --vl-gray );
}
.vl-handoff__lede {
	margin: 0;
	font-size: clamp( 1.3rem, 2.6vw, 2rem );
	font-weight: 500;
	letter-spacing: -.02em;
	line-height: 1.15;
	max-width: 22ch;
}
.vl-handoff__btn {
	display: inline-block;
	background: var( --vl-ink );
	color: #fff;
	text-decoration: none;
	border-radius: 2rem;
	padding: .8rem 1.6rem;
	font-size: .95rem;
	margin-top: .4rem;
}
