/* =================================================================
   Vocalis Studio — App shell styles
   Direct CSS port of the Lovable reference's app/* Tailwind classes.
   Companion to tokens.css. Presentation-only — no business logic.
   ================================================================= */

/* Ensure HTML5 [hidden] attribute always hides elements.
   Some WordPress themes/resets override this — !important wins. */
[hidden] { display: none !important; }

#vx-app-page { position: relative; min-height: 100vh; overflow-x: hidden; background: var(--background); padding-bottom: 7rem; }
@media (min-width: 1024px) { #vx-app-page { padding-bottom: 3rem; } }

.vx-app-page__bg { position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: 0.9; background-image: var(--gradient-hero); }
.vx-app-page__blob-1 { position: fixed; z-index: -1; pointer-events: none; top: 5rem; left: -5rem; width: 26.25rem; height: 26.25rem; border-radius: 999px; background: color-mix(in oklab, var(--brand-purple) 20%, transparent); filter: blur(64px); }
.vx-app-page__blob-2 { position: fixed; z-index: -1; pointer-events: none; top: 33%; right: -6rem; width: 28.75rem; height: 28.75rem; border-radius: 999px; background: color-mix(in oklab, var(--brand-blue) 20%, transparent); filter: blur(64px); }

.vx-container-xl { max-width: 80rem; margin-inline: auto; }

/* =================================================================
   WordPress Admin Bar compensation.
   When logged in, WP adds a 32px (mobile: 46px) fixed admin bar and
   puts margin-top on <html>. Our fixed header must sit below it, and
   body padding-top must account for both header height + admin bar.
   ================================================================= */

/* Admin bar is 32px on desktop, 46px on mobile (≤782px) */
.admin-bar .vx-app-header { top: 32px; }
@media screen and (max-width: 782px) { .admin-bar .vx-app-header { top: 46px; } }

/* Body padding: normal 6rem + 32px admin bar = ~8rem; tight-top similarly */
.admin-bar .vx-app-body { padding-top: calc(6rem + 32px); }
@media (min-width: 640px) { .admin-bar .vx-app-body { padding-top: calc(7rem + 32px); } }
@media screen and (max-width: 782px) {
  .admin-bar .vx-app-body { padding-top: calc(6rem + 46px); }
}
.admin-bar #vx-app-page.vx-tight-top .vx-app-body { padding-top: calc(4rem + 32px); }
@media (min-width: 640px) { .admin-bar #vx-app-page.vx-tight-top .vx-app-body { padding-top: calc(4.5rem + 32px); } }
@media screen and (max-width: 782px) {
  .admin-bar #vx-app-page.vx-tight-top .vx-app-body { padding-top: calc(4rem + 46px); }
}

/* Sidebar sticky top also needs adjustment */
.admin-bar .vx-app-sidebar__sticky { top: calc(6rem + 32px); }
.admin-bar #vx-app-page.vx-tight-top .vx-app-sidebar__sticky { top: calc(4rem + 32px); }
.admin-bar .vx-app-rail__sticky { top: calc(6rem + 32px); }

/* Tab bar stays at bottom — no change needed */

/* =================================================================
   Header
   ================================================================= */
.vx-app-header { position: fixed; inset-inline: 0; top: 0; z-index: 40; padding: 0.75rem 0.75rem 0; }
@media (min-width: 640px) { .vx-app-header { padding: 1rem 1.25rem 0; } }
.vx-app-header__bar { display: flex; align-items: center; justify-content: space-between; border-radius: var(--r-full); padding: 0.5rem 0.75rem; box-shadow: var(--shadow-soft); }
@media (min-width: 640px) { .vx-app-header__bar { padding: 0.5rem 1rem; } }

.vx-app-header__brand { display: flex; align-items: center; gap: 0.5rem; }
.vx-app-header__back { display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: var(--r-2xl); background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); transition: transform 0.15s ease; }
.vx-app-header__back:active { transform: scale(0.95); }
.vx-app-header__logo { display: none; align-items: center; gap: 0.5rem; padding-left: 0.25rem; }
@media (min-width: 640px) { .vx-app-header__logo { display: flex; } }
.vx-app-header__logo-name { font-family: var(--font-display); font-size: 1rem; font-weight: 600; letter-spacing: -0.02em; }

.vx-app-header__nav { display: none; align-items: center; gap: 0.25rem; list-style: none; margin: 0; padding: 0; }
@media (min-width: 768px) { .vx-app-header__nav { display: flex; } }
.vx-app-header__nav a { display: inline-block; border-radius: var(--r-full); padding: 0.375rem 0.875rem; font-size: 0.875rem; font-weight: 500; color: var(--muted-foreground); transition: background-color 0.2s ease, color 0.2s ease; }
.vx-app-header__nav a:hover { background: var(--secondary); color: var(--foreground); }
.vx-app-header__nav a.is-active { background: var(--secondary); color: var(--foreground); font-weight: 600; }

.vx-app-header__actions { display: flex; align-items: center; gap: 0.375rem; }
.vx-app-header__filter-btn { display: grid; }
@media (min-width: 1024px) { .vx-app-header__filter-btn { display: none; } }
.vx-app-header__user { margin-left: 0.125rem; display: flex; align-items: center; gap: 0.5rem; border-radius: var(--r-full); background: var(--secondary); border: none; padding: 0.25rem 0.625rem 0.25rem 0.25rem; transition: background-color 0.2s ease; cursor: pointer; }
.vx-app-header__user:hover { background: var(--accent); }
/* The avatar is now the link to the Profile page, so it needs the same
   "you are here" affordance every other nav target has. Additive only —
   the resting appearance is untouched. */
.vx-app-header__user.is-active { background: var(--accent); box-shadow: inset 0 0 0 1.5px var(--brand-purple); }
.vx-app-header__avatar { display: grid; place-items: center; width: 1.75rem; height: 1.75rem; border-radius: var(--r-full); background: linear-gradient(135deg, #c4b5fd, #e879f9); font-size: 0.6875rem; font-weight: 700; color: #fff; }

/* ------------------------------------------------------------------
   Avatar chip with a real picture.

   The gradient fill and the letter styling above stay exactly as they
   were — they are still what shows when no picture is configured. The
   `.has-image` variant only strips the parts that would show THROUGH or
   AROUND a photo: the gradient (which would tint a transparent PNG) and
   any letter-spacing inherited from the initials.

   `overflow: hidden` plus `border-radius: inherit` on the image is what
   keeps a square source photo inside the circle. `object-fit: cover`
   crops rather than squashes, so a portrait that is not perfectly square
   still fills the chip without distorting the face.
   ------------------------------------------------------------------ */
.vx-app-header__avatar.has-image,
.vx-menu__avatar.has-image {
	background: none;
	background-image: none;
	overflow: hidden;
	padding: 0;
	letter-spacing: normal;
}

.vx-app-header__avatar img,
.vx-menu__avatar img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center top; /* portraits crop better from the top */
	border-radius: inherit;
}
.vx-app-header__user-name { display: none; font-size: 0.75rem; font-weight: 600; }
@media (min-width: 640px) { .vx-app-header__user-name { display: inline; } }

.vx-icon-btn { display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: var(--r-full); background: var(--secondary); border: none; color: var(--foreground); transition: transform 0.15s ease; cursor: pointer; }
.vx-icon-btn:hover { transform: scale(1.05); }
.vx-icon-btn:active { transform: scale(0.95); }

/* =================================================================
   App layout grid (sidebar + main + rail)
   ================================================================= */
.vx-app-body { margin-inline: auto; max-width: 80rem; display: flex; gap: 1.5rem; padding: 0 0.75rem; padding-top: 6rem; }
@media (min-width: 640px) { .vx-app-body { padding-inline: 1.25rem; padding-top: 7rem; } }
#vx-app-page.vx-tight-top .vx-app-body { padding-top: 4rem; }
@media (min-width: 640px) { #vx-app-page.vx-tight-top .vx-app-body { padding-top: 4.5rem; } }
.vx-app-main { min-width: 0; flex: 1; }
.vx-app-main > * + * { margin-top: 1.25rem; }

/* =================================================================
   Sidebar
   ================================================================= */
.vx-app-sidebar { display: none; width: 16rem; flex-shrink: 0; }
@media (min-width: 1024px) { .vx-app-sidebar { display: block; } }
.vx-app-sidebar__sticky { position: sticky; top: 6rem; }
#vx-app-page.vx-tight-top .vx-app-sidebar__sticky { top: 4rem; }
.vx-app-sidebar__panel { border-radius: var(--r-3xl); padding: 0.75rem; }
.vx-app-sidebar__label { padding: 0.5rem 0.75rem; }
.vx-app-sidebar__nav { display: flex; flex-direction: column; gap: 0.25rem; }
.vx-app-sidebar__link { display: flex; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); padding: 0.625rem 0.75rem; font-size: 0.875rem; font-weight: 500; color: var(--muted-foreground); transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; }
.vx-app-sidebar__link:hover { background: rgba(255,255,255,0.7); color: var(--foreground); }
.vx-app-sidebar__link.is-active { background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-glow); }
.vx-app-sidebar__icon { display: grid; place-items: center; flex-shrink: 0; width: 1.75rem; height: 1.75rem; border-radius: var(--r-xl); background: var(--secondary); color: var(--foreground); transition: background-color 0.2s ease; }
.vx-app-sidebar__link.is-active .vx-app-sidebar__icon { background: rgba(255,255,255,0.2); color: #fff; }
.vx-app-sidebar__link:hover:not(.is-active) .vx-app-sidebar__icon { background: #fff; }
.vx-app-sidebar__link-label { flex: 1; text-align: left; }
.vx-app-sidebar__chevron { opacity: 0.8; }

.vx-app-sidebar__upsell { margin-top: 1rem; border-radius: var(--r-2xl); padding: 1rem; background: linear-gradient(135deg, color-mix(in oklab, #8b5cf6 10%, transparent), color-mix(in oklab, #d946ef 10%, transparent), color-mix(in oklab, #0ea5e9 10%, transparent)); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); }
.vx-app-sidebar__upsell-head { display: flex; align-items: center; gap: 0.5rem; }
.vx-app-sidebar__upsell-icon { color: var(--primary); }
.vx-app-sidebar__upsell-head p { margin: 0; font-size: 0.75rem; font-weight: 600; }
.vx-app-sidebar__upsell-desc { margin: 0.25rem 0 0; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-app-sidebar__upsell-link { margin-top: 0.75rem; display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.75rem; font-weight: 600; }

/* =================================================================
   Mobile tab bar
   ================================================================= */
.vx-app-tabbar { position: fixed; inset-inline: 0; bottom: 0; z-index: 40; padding: 0 0.75rem max(0.75rem, env(safe-area-inset-bottom)); }
@media (min-width: 1024px) { .vx-app-tabbar { display: none; } }
.vx-app-tabbar__bar { margin-inline: auto; display: flex; max-width: 28rem; align-items: center; justify-content: space-between; border-radius: var(--r-full); padding: 0.375rem; box-shadow: var(--shadow-elevated); }
.vx-app-tabbar__item { display: flex; flex: 1; flex-direction: column; align-items: center; gap: 0.125rem; border-radius: var(--r-full); padding: 0.5rem 0.5rem; color: var(--muted-foreground); transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; }
.vx-app-tabbar__item:active { transform: scale(0.95); }
.vx-app-tabbar__item.is-active { background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); }
.vx-app-tabbar__item span { font-size: 0.625rem; font-weight: 600; line-height: 1; }

/* =================================================================
   Shared primitives (Stat, PageHeading, SectionLabel, Card, etc.)
   ================================================================= */
.vx-app-eyebrow { margin: 0; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted-foreground); }

.vx-card { border-radius: 24px; padding: 1.25rem; }
@media (min-width: 640px) { .vx-card { padding: 1.5rem; } }
.vx-card__head { display: flex; align-items: center; justify-content: space-between; }
.vx-card__title { margin: 0.25rem 0 0; font-size: 1.125rem; font-weight: 600; }

.vx-link-sm { font-size: 0.75rem; font-weight: 600; }

.vx-page-heading { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 0.75rem; }
.vx-page-heading__title { margin: 0.25rem 0 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.03em; }
@media (min-width: 640px) { .vx-page-heading__title { font-size: 1.875rem; } }
.vx-page-heading__subtitle { margin: 0.25rem 0 0; max-width: 36rem; font-size: 0.875rem; color: var(--muted-foreground); }
.vx-page-heading__action { flex-shrink: 0; }

.vx-stat-tile { border-radius: var(--r-2xl); background: rgba(255,255,255,0.55); backdrop-filter: blur(12px); padding: 1rem; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); }
.vx-stat-tile__label { margin: 0; font-size: 0.6875rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted-foreground); }
.vx-stat-tile__value { margin: 0.25rem 0 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; color: var(--foreground); }
.vx-stat-tile__sub { margin: 0.125rem 0 0; font-size: 0.75rem; color: var(--muted-foreground); }

.vx-pkg-stat { display: flex; height: 100%; min-width: 0; flex-direction: column; border-radius: var(--r-2xl); background: rgba(255,255,255,0.55); backdrop-filter: blur(12px); padding: 0.75rem; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); }
@media (min-width: 640px) { .vx-pkg-stat { padding: 1rem; } }
.vx-pkg-stat__label { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.625rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted-foreground); }
@media (min-width: 640px) { .vx-pkg-stat__label { font-size: 0.6875rem; } }
.vx-pkg-stat__value { margin: 0.25rem 0 0; font-size: 1rem; font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; color: var(--foreground); word-break: break-word; }
@media (min-width: 640px) { .vx-pkg-stat__value { font-size: 1.125rem; } }
@media (min-width: 1024px) { .vx-pkg-stat__value { font-size: 1.25rem; } }

.vx-usage-ring { position: relative; display: grid; place-items: center; }
.vx-usage-ring circle:last-child { transition: stroke-dasharray 0.6s cubic-bezier(0.22, 1, 0.36, 1); }
.vx-usage-ring__label { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 2px; }
.vx-usage-ring__value { margin: 0; font-size: 1.75rem; font-weight: 800; letter-spacing: -0.02em; }
.vx-usage-ring__caption { margin: 0; font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted-foreground); }

.vx-history-list { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.vx-history-row { display: flex; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.6); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); padding: 0.75rem; transition: transform 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease; }
.vx-history-row:hover { transform: translateY(-2px); background: rgba(255,255,255,0.8); box-shadow: var(--shadow-soft); }
.vx-history-row__body { min-width: 0; flex: 1; }
.vx-history-row__title { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-history-row__meta { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-history-row__more { opacity: 0; }
.vx-history-row:hover .vx-history-row__more,
.vx-history-row__menu-wrap.is-open .vx-history-row__more { opacity: 1; }
.vx-history-row__menu-wrap { position: relative; }

.vx-icon-square { display: grid; place-items: center; width: 2.25rem; height: 2.25rem; flex-shrink: 0; border-radius: var(--r-xl); background: var(--secondary); color: var(--foreground); }

.vx-pill-trend { margin-top: 0.5rem; display: inline-flex; align-items: center; gap: 0.25rem; border-radius: var(--r-full); background: rgba(16,185,129,0.1); padding: 0.125rem 0.5rem; font-size: 0.625rem; font-weight: 600; color: #059669; }

/* =================================================================
   Voice tints — one gradient per voice (bg-gradient-to-br from/to)
   ================================================================= */
.vx-voice-tint-aurora { background: linear-gradient(135deg, #a78bfa, #e879f9); }
.vx-voice-tint-atlas  { background: linear-gradient(135deg, #38bdf8, #6366f1); }
.vx-voice-tint-luna   { background: linear-gradient(135deg, #fda4af, #ec4899); }
.vx-voice-tint-kai    { background: linear-gradient(135deg, #6ee7b7, #14b8a6); }
.vx-voice-tint-milo   { background: linear-gradient(135deg, #fcd34d, #f97316); }
.vx-voice-tint-nova   { background: linear-gradient(135deg, #f472b6, #8b5cf6); }
.vx-voice-tint-orion  { background: linear-gradient(135deg, #818cf8, #3b82f6); }
.vx-voice-tint-sage   { background: linear-gradient(135deg, #67e8f9, #0ea5e9); }
.vx-voice-tint-ember  { background: linear-gradient(135deg, #fb923c, #f43f5e); }
.vx-voice-tint-vega   { background: linear-gradient(135deg, #94a3b8, #52525b); }
.vx-voice-tint-rio    { background: linear-gradient(135deg, #fde047, #fb923c); }
.vx-voice-tint-zen    { background: linear-gradient(135deg, #5eead4, #10b981); }

.vx-voice-avatar { display: grid; place-items: center; flex-shrink: 0; color: #fff; }
.vx-voice-avatar.has-image > svg { display: none; }

/* =================================================================
   Right rail (Text-to-Speech page)
   ================================================================= */
.vx-app-rail { display: none; width: 20rem; flex-shrink: 0; }
@media (min-width: 1280px) { .vx-app-rail { display: block; } }
.vx-app-rail__sticky { position: sticky; top: 6rem; display: flex; flex-direction: column; gap: 1rem; }

.vx-rail-usage { margin-top: 1rem; display: flex; align-items: center; gap: 1rem; }
.vx-rail-usage__amount { margin: 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; }
.vx-rail-usage__caption { margin: 0; font-size: 0.75rem; color: var(--muted-foreground); }
.vx-rail-bars { margin-top: 1rem; display: flex; align-items: flex-end; gap: 0.375rem; height: 4rem; }
.vx-rail-bars__col { flex: 1; border-radius: 6px 6px 0 0; background-image: var(--gradient-brand); opacity: 0.7; }

.vx-rail-quick { margin-top: 0.75rem; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; }
.vx-rail-quick__item { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.7); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); padding: 0.75rem; font-size: 0.75rem; font-weight: 600; transition: transform 0.2s ease, box-shadow 0.2s ease; }
.vx-rail-quick__item:hover { transform: translateY(-2px); box-shadow: var(--shadow-soft); }
.vx-rail-quick__item .vx-icon-badge { width: 2rem; height: 2rem; }

.vx-rail-star { color: #f59e0b; }
.vx-rail-voice-list, .vx-rail-download-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.vx-rail-voice-list li { display: flex; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.6); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); padding: 0.625rem; transition: background-color 0.2s ease; }
.vx-rail-voice-list li:hover { background: rgba(255,255,255,0.8); }
.vx-rail-voice-list .vx-voice-avatar { width: 2.25rem; height: 2.25rem; border-radius: var(--r-xl); }
.vx-rail-voice-list__body { min-width: 0; flex: 1; }
.vx-rail-voice-list__body p:first-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-rail-voice-list__body p:last-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }

.vx-rail-download-list li { display: flex; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.6); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); padding: 0.625rem; }
.vx-rail-download-list__body p:first-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.75rem; font-weight: 600; }
.vx-rail-download-list__body p:last-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.625rem; color: var(--muted-foreground); }

/* =================================================================
   Access Code Hero (Text page) / Dashboard Hero — shared shell
   ================================================================= */
.vx-hero-shell { position: relative; overflow: hidden; border-radius: 28px; padding: 1.5rem; }
@media (min-width: 640px) { .vx-hero-shell { padding: 2rem; } }
.vx-hero-shell__bg { position: absolute; inset: 0; pointer-events: none; background-image: var(--gradient-hero); }
.vx-hero-shell__blob-a { position: absolute; pointer-events: none; left: -4rem; top: -4rem; width: 16rem; height: 16rem; border-radius: 999px; filter: blur(64px); }
.vx-hero-shell__blob-b { position: absolute; pointer-events: none; right: -2.5rem; bottom: -40%; width: 18rem; height: 18rem; border-radius: 999px; filter: blur(64px); }
.vx-access-hero .vx-hero-shell__blob-a { background: color-mix(in oklab, var(--brand-purple) 30%, transparent); }
.vx-access-hero .vx-hero-shell__blob-b { background: color-mix(in oklab, var(--brand-blue) 30%, transparent); }
.vx-dashboard-hero .vx-hero-shell__blob-a { background: color-mix(in oklab, var(--brand-blue) 30%, transparent); }
.vx-dashboard-hero .vx-hero-shell__blob-b { background: color-mix(in oklab, var(--brand-pink) 25%, transparent); }
.vx-hero-shell__panel { position: relative; border-radius: 24px; padding: 1.25rem; }
@media (min-width: 640px) { .vx-hero-shell__panel { padding: 1.5rem; } }

.vx-hero-shell__top { display: flex; flex-direction: column; gap: 1.25rem; }
@media (min-width: 640px) { .vx-hero-shell__top { flex-direction: row; align-items: flex-start; justify-content: space-between; } }
.vx-hero-shell__greeting { display: inline-flex; align-items: center; gap: 0.375rem; border-radius: var(--r-full); background: rgba(255,255,255,0.7); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); padding: 0.25rem 0.625rem; font-size: 0.6875rem; font-weight: 600; }
.vx-hero-shell__greeting svg { color: var(--brand-purple); }
.vx-hero-shell__title { margin: 0.75rem 0 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.03em; }
@media (min-width: 640px) { .vx-hero-shell__title { font-size: 1.875rem; } }
.vx-hero-shell__subtitle { margin: 0.375rem 0 0; max-width: 28rem; font-size: 0.875rem; color: var(--muted-foreground); }
.vx-hero-shell__cta { border-radius: var(--r-full); background-image: var(--gradient-brand); padding: 0.5rem 1rem; font-size: 0.75rem; font-weight: 600; color: #fff; box-shadow: var(--shadow-glow); transition: transform 0.2s ease; }
.vx-hero-shell__cta:hover { transform: scale(1.03); }
.vx-hero-shell__cta:active { transform: scale(0.95); }

.vx-pkg-stats { margin-top: 1.25rem; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; }
@media (min-width: 640px) { .vx-pkg-stats { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1024px) { .vx-pkg-stats { grid-template-columns: repeat(6, 1fr); } }
.vx-stat-row { margin-top: 1.25rem; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; }
@media (min-width: 640px) { .vx-stat-row { grid-template-columns: repeat(4, 1fr); } }

/* =================================================================
   Access verification (mock code form)
   ================================================================= */
.vx-access-form__label { margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.375rem; font-size: 0.6875rem; font-weight: 600; color: color-mix(in oklab, var(--foreground) 80%, transparent); }
.vx-access-form__label svg { color: var(--brand-purple); }
.vx-access-form__row { display: flex; align-items: center; gap: 0.5rem; border-radius: var(--r-full); padding: 0.375rem 0.375rem 0.375rem 1rem; transition: opacity 0.3s ease, box-shadow 0.3s ease; }
.vx-access-form__row.is-loading { opacity: 0.9; }
.vx-access-form__row.is-error { box-shadow: var(--shadow-glass), 0 0 0 1px rgba(248,113,113,0.6); }
.vx-access-form__row input { height: 2.25rem; min-width: 0; flex: 1; background: transparent; border: none; font-size: 0.875rem; font-weight: 500; letter-spacing: 0.01em; color: var(--foreground); outline: none; }
.vx-access-form__row input::placeholder { color: color-mix(in oklab, var(--muted-foreground) 70%, transparent); }
.vx-access-form__submit { display: inline-flex; height: 2.25rem; flex-shrink: 0; align-items: center; justify-content: center; gap: 0.375rem; border-radius: var(--r-full); background-image: var(--gradient-brand); padding: 0 1rem; font-size: 0.75rem; font-weight: 600; color: #fff; box-shadow: var(--shadow-glow); border: none; transition: transform 0.2s ease; cursor: pointer; }
.vx-access-form__submit:hover:not(:disabled) { transform: scale(1.03); }
.vx-access-form__submit:active:not(:disabled) { transform: scale(0.95); }
.vx-access-form__submit:disabled { opacity: 0.6; cursor: default; }
.vx-access-form__error { margin-top: 0.5rem; display: flex; align-items: flex-start; gap: 0.5rem; border-radius: var(--r-2xl); border: 1px solid rgba(252,165,165,0.4); background: rgba(239,68,68,0.1); padding: 0.5rem 0.75rem; box-shadow: var(--shadow-soft); }
.vx-access-form__error svg { margin-top: 0.125rem; flex-shrink: 0; color: #ef4444; }
.vx-access-form__error p { margin: 0; font-size: 0.6875rem; font-weight: 600; color: #dc2626; }

.vx-access-verified { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; border-radius: var(--r-full); padding: 0.375rem 0.375rem 0.375rem 1rem; }
.vx-access-verified__status { display: inline-flex; align-items: center; gap: 0.375rem; font-size: 0.6875rem; font-weight: 600; color: #059669; }
.vx-access-verified__change { display: inline-flex; height: 2.25rem; flex-shrink: 0; align-items: center; justify-content: center; gap: 0.375rem; border-radius: var(--r-full); background: rgba(255,255,255,0.7); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); padding: 0 0.875rem; font-size: 0.75rem; font-weight: 600; color: var(--foreground); border: none; transition: transform 0.2s ease; cursor: pointer; }
.vx-access-verified__change:hover { transform: scale(1.03); }

/* =================================================================
   Dashboard page
   ================================================================= */
.vx-dashboard-grid { margin-top: 1.25rem; display: grid; gap: 1.25rem; }
@media (min-width: 1024px) { .vx-dashboard-grid { grid-template-columns: 1.4fr 1fr; } }
.vx-dashboard-grid__col { display: flex; flex-direction: column; gap: 1.25rem; }

.vx-quick-grid { margin-top: 1rem; display: grid; gap: 0.75rem; }
@media (min-width: 640px) { .vx-quick-grid { grid-template-columns: repeat(2, 1fr); } }
.vx-quick-card { position: relative; overflow: hidden; border-radius: var(--r-2xl); background: rgba(255,255,255,0.7); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); padding: 1rem; transition: transform 0.3s ease, box-shadow 0.3s ease; }
.vx-quick-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-soft); }
.vx-quick-card__top { display: flex; align-items: flex-start; justify-content: space-between; }
.vx-quick-card__top .vx-icon-badge { width: 2.5rem; height: 2.5rem; border-radius: var(--r-xl); transition: transform 0.3s ease; }
.vx-quick-card:hover .vx-icon-badge { transform: scale(1.05); }
.vx-quick-card__arrow { color: var(--muted-foreground); opacity: 0; transition: opacity 0.3s ease; }
.vx-quick-card:hover .vx-quick-card__arrow { opacity: 1; }
.vx-quick-card__label { margin: 0.75rem 0 0; font-size: 0.875rem; font-weight: 600; }
.vx-quick-card__desc { margin: 0; font-size: 0.6875rem; color: var(--muted-foreground); }

.vx-analytics-trend { display: inline-flex; align-items: center; gap: 0.25rem; border-radius: var(--r-full); background: rgba(16,185,129,0.1); padding: 0.125rem 0.5rem; font-size: 0.625rem; font-weight: 600; color: #059669; }
.vx-analytics-trend.is-down { background: rgba(239,68,68,0.1); color: #dc2626; }
.vx-top-voices__empty { margin: 0.5rem 0 0; text-align: center; font-size: 0.8125rem; color: var(--muted-foreground); padding: 1rem 0; }
.vx-dashboard-usage { margin-top: 1rem; display: flex; align-items: center; gap: 1rem; }
.vx-dashboard-usage__amount { margin: 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; }
.vx-dashboard-usage__caption { margin: 0; font-size: 0.75rem; color: var(--muted-foreground); }
.vx-dashboard-usage__limit { margin: 0.25rem 0 0; font-size: 0.6875rem; color: var(--muted-foreground); }

.vx-week-bars { margin-top: 1.25rem; }
.vx-week-bars__label { margin: 0; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted-foreground); }
.vx-week-bars__row { margin-top: 0.5rem; display: flex; align-items: flex-end; gap: 0.375rem; height: 5rem; }
.vx-week-bars__col { flex: 1; border-radius: 6px 6px 0 0; background-image: var(--gradient-brand); opacity: 0.8; transition: opacity 0.2s ease; }
.vx-week-bars__col:hover { opacity: 1; }
.vx-week-bars__days { margin-top: 0.375rem; display: flex; justify-content: space-between; font-size: 0.625rem; font-weight: 600; color: var(--muted-foreground); }

.vx-top-voices { list-style: none; margin: 0.75rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.vx-top-voices li { display: flex; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.6); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); padding: 0.625rem; transition: transform 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease; }
.vx-top-voices li:hover { transform: translateY(-2px); background: rgba(255,255,255,0.8); }
.vx-top-voices .vx-voice-avatar { width: 2.25rem; height: 2.25rem; border-radius: var(--r-xl); }
.vx-top-voices__body { min-width: 0; flex: 1; }
.vx-top-voices__body p:first-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-top-voices__body p:last-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-top-voices__pct { border-radius: var(--r-full); background: var(--secondary); padding: 0.125rem 0.5rem; font-size: 0.625rem; font-weight: 600; }

/* ── Voice Clone Section ── */
.vx-voice-clone-section { margin-bottom: 1.25rem; }
.vx-vc-card { padding: 1.25rem; }
.vx-vc-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
.vx-vc-header__icon { font-size: 1.5rem; flex-shrink: 0; }
.vx-vc-header__text { flex: 1; }
.vx-vc-header__title { font-size: 1rem; font-weight: 700; }
.vx-vc-header__sub { font-size: 0.6875rem; color: var(--muted-foreground); margin-top: 1px; }
.vx-vc-slots-strip { display: flex; gap: 5px; flex-shrink: 0; }
.vx-vc-slot { width: 11px; height: 11px; border-radius: 50%; background: var(--secondary); border: 1.5px solid rgba(255,255,255,0.6); transition: background-color 0.3s, box-shadow 0.3s, border-color 0.3s; }
.vx-vc-slot.active { background: #4ade80; box-shadow: 0 0 5px #4ade80; border-color: #22c55e; }
.vx-vc-slot.mine   { background: #fbbf24; box-shadow: 0 0 5px #fbbf24; border-color: #f59e0b; }

.vx-vc-upload { position: relative; border: 2px dashed rgba(99,102,241,.3); border-radius: var(--r-2xl); padding: 1.5rem 1rem; text-align: center; cursor: pointer; transition: border-color .25s, background-color .25s; background: rgba(99,102,241,.03); }
.vx-vc-upload:hover, .vx-vc-upload.drag { border-color: var(--brand-purple); background: rgba(99,102,241,.07); }
.vx-vc-upload__icon { font-size: 1.75rem; margin-bottom: 0.5rem; }
.vx-vc-upload__hint { font-size: 0.875rem; font-weight: 700; }
.vx-vc-upload__sub  { font-size: 0.75rem; color: var(--muted-foreground); margin-top: 2px; }
.vx-vc-upload__sub strong { color: var(--brand-purple); }

.vx-vc-preview { display: flex; align-items: center; gap: 0.625rem; background: rgba(99,102,241,.07); border: 1.5px solid rgba(99,102,241,.2); border-radius: var(--r-xl); padding: 0.625rem 0.75rem; margin-top: 0.625rem; }
.vx-vc-preview__info { flex: 1; min-width: 0; }
.vx-vc-preview__name { font-size: 0.8125rem; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vx-vc-preview__size { font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-vc-preview__remove { background: none; border: none; color: var(--destructive, #ef4444); cursor: pointer; font-size: 1rem; padding: 0; flex-shrink: 0; }

.vx-vc-name-row { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.875rem; }
.vx-vc-name-badge { flex: 1; background: rgba(99,102,241,.08); border: 1.5px solid rgba(99,102,241,.2); border-radius: var(--r-xl); padding: 0.6875rem 0.875rem; font-size: 0.9375rem; font-weight: 800; color: var(--brand-purple, #6366f1); letter-spacing: .3px; }
.vx-vc-reroll { width: 2.5rem; height: 2.5rem; border-radius: var(--r-xl); background: rgba(99,102,241,.1); border: 1.5px solid rgba(99,102,241,.2); color: var(--brand-purple,#6366f1); cursor: pointer; font-size: 1.125rem; display: flex; align-items: center; justify-content: center; transition: transform .2s, background-color .2s; flex-shrink: 0; }
.vx-vc-reroll:hover { background: rgba(99,102,241,.18); transform: rotate(30deg); }

.vx-vc-queue { display: flex; align-items: flex-start; gap: 0.75rem; background: #fffbeb; border: 1.5px solid #fcd34d; border-radius: var(--r-2xl); padding: 0.875rem 1rem; margin-top: 0.875rem; }
.vx-vc-queue__icon { font-size: 1.375rem; flex-shrink: 0; }
.vx-vc-queue__title { font-size: 0.875rem; font-weight: 800; color: #92400e; }
.vx-vc-queue__sub { font-size: 0.75rem; color: #b45309; margin-top: 2px; }

.vx-vc-btn { width: 100%; padding: 0.9375rem; border-radius: var(--r-2xl); border: none; background: var(--gradient-brand, linear-gradient(135deg,#6366f1,#a855f7)); color: #fff; font-size: 0.9375rem; font-weight: 800; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-top: 0.875rem; transition: transform .25s, box-shadow .25s, opacity .25s; box-shadow: 0 6px 20px rgba(99,102,241,.35); letter-spacing: .2px; }
.vx-vc-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(99,102,241,.5); }
.vx-vc-btn:disabled { opacity: .5; cursor: not-allowed; transform: none; box-shadow: none; }
.vx-vc-btn__spin { width: 1.125rem; height: 1.125rem; border: 2.5px solid rgba(255,255,255,.3); border-top-color: #fff; border-radius: 50%; animation: vx-spin .6s linear infinite; display: none; flex-shrink: 0; }
@keyframes vx-spin { to { transform: rotate(360deg); } }

.vx-vc-active { margin-top: 1rem; background: rgba(255,255,255,.7); border-radius: var(--r-2xl); border: 1.5px solid rgba(99,102,241,.18); padding: 1rem; }
.vx-vc-active__head { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
.vx-vc-active__avatar { width: 2.625rem; height: 2.625rem; border-radius: var(--r-xl); background: var(--gradient-brand, linear-gradient(135deg,#6366f1,#a855f7)); display: flex; align-items: center; justify-content: center; font-size: 1.25rem; flex-shrink: 0; box-shadow: 0 4px 12px rgba(99,102,241,.35); }
.vx-vc-active__name { font-size: 0.9375rem; font-weight: 800; }
.vx-vc-active__sub { font-size: 0.6875rem; color: var(--muted-foreground); margin-top: 1px; }
.vx-vc-active__badge { margin-left: auto; font-size: 0.625rem; font-weight: 800; padding: 0.25rem 0.625rem; border-radius: var(--r-full); background: #ecfdf5; color: #059669; text-transform: uppercase; letter-spacing: .5px; flex-shrink: 0; }
.vx-vc-active__badge.warn { background: #fffbeb; color: #d97706; }

.vx-vc-timer-row { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.875rem; }
.vx-vc-timer-ring-wrap { position: relative; width: 5rem; height: 5rem; flex-shrink: 0; }
.vx-vc-ring-bg { fill: none; stroke: rgba(99,102,241,.12); stroke-width: 5; }
.vx-vc-ring-fg { fill: none; stroke: url(#vcgrad); stroke-width: 5; stroke-linecap: round; transition: stroke-dashoffset .8s linear; }
.vx-vc-timer-text { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 1.125rem; font-weight: 800; color: var(--brand-purple,#6366f1); }
.vx-vc-timer-info { flex: 1; }
.vx-vc-timer-label { font-size: 0.6875rem; color: var(--muted-foreground); font-weight: 600; text-transform: uppercase; letter-spacing: .5px; margin-bottom: 0.375rem; }
.vx-vc-bar-wrap { height: 4px; background: rgba(99,102,241,.1); border-radius: 99px; overflow: hidden; margin-bottom: 0.375rem; }
.vx-vc-bar { height: 100%; background: var(--gradient-brand, linear-gradient(135deg,#6366f1,#a855f7)); border-radius: 99px; transition: width .8s linear; }
.vx-vc-slot-count { font-size: 0.6875rem; color: var(--muted-foreground); font-weight: 600; }

.vx-vc-actions { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }
.vx-vc-use-btn { flex: 1; padding: 0.6875rem; border-radius: var(--r-xl); background: var(--gradient-brand, linear-gradient(135deg,#6366f1,#a855f7)); color: #fff; font-size: 0.8125rem; font-weight: 800; text-align: center; text-decoration: none; transition: transform .2s, box-shadow .2s; display: flex; align-items: center; justify-content: center; }
.vx-vc-use-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(99,102,241,.4); }
.vx-vc-extend { padding: 0.6875rem 0.875rem; border-radius: var(--r-xl); background: rgba(99,102,241,.08); border: 1.5px solid rgba(99,102,241,.2); color: var(--brand-purple,#6366f1); font-size: 0.8125rem; font-weight: 700; cursor: pointer; transition: background-color .2s; white-space: nowrap; }
.vx-vc-extend:hover { background: rgba(99,102,241,.15); }
.vx-vc-release { width: 100%; padding: 0.5rem; border-radius: var(--r-xl); background: none; border: 1.5px solid rgba(239,68,68,.25); color: #ef4444; font-size: 0.75rem; font-weight: 700; cursor: pointer; transition: background-color .2s; }
.vx-vc-release:hover { background: rgba(239,68,68,.06); }

.vx-activity-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.vx-activity-list li { display: flex; align-items: flex-start; gap: 0.75rem; }
.vx-activity-list__icon { display: grid; place-items: center; flex-shrink: 0; margin-top: 0.125rem; width: 1.75rem; height: 1.75rem; border-radius: var(--r-full); background: var(--secondary); color: var(--foreground); }
.vx-activity-list__body { min-width: 0; flex: 1; }
.vx-activity-list__body p:first-child { margin: 0; font-size: 0.875rem; }
.vx-activity-list__body p:first-child strong { font-weight: 600; }
.vx-activity-list__body p:first-child span { color: var(--muted-foreground); }
.vx-activity-list__body p:last-child { margin: 0; font-size: 0.6875rem; color: var(--muted-foreground); }

/* =================================================================
   Text-to-Speech page
   ================================================================= */
.vx-tts-grid { margin-top: 1.25rem; display: grid; gap: 1.25rem; }
@media (min-width: 1280px) { .vx-tts-grid { grid-template-columns: 1.35fr 1fr; } }
.vx-tts-grid__col { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }

.vx-text-input { border-radius: 24px; padding: 1rem; transition: box-shadow 0.3s ease; }
@media (min-width: 640px) { .vx-text-input { padding: 1.25rem; } }
.vx-text-input.is-focused { box-shadow: var(--shadow-elevated), 0 0 0 2px color-mix(in oklab, var(--primary) 25%, transparent); }
.vx-text-input__head { display: flex; align-items: center; justify-content: space-between; }
.vx-text-input__title { display: flex; align-items: center; gap: 0.5rem; }
.vx-text-input__title span:first-child { display: grid; place-items: center; width: 1.75rem; height: 1.75rem; border-radius: var(--r-xl); background: var(--secondary); color: var(--foreground); }
.vx-text-input__title p { margin: 0; font-size: 0.875rem; font-weight: 600; }
.vx-text-input__actions { display: flex; align-items: center; gap: 0.375rem; }
.vx-text-input__actions button { display: inline-flex; align-items: center; gap: 0.25rem; border-radius: var(--r-full); background: var(--secondary); border: none; padding: 0.25rem 0.625rem; font-size: 0.6875rem; font-weight: 600; color: var(--foreground); transition: transform 0.2s ease; cursor: pointer; }
.vx-text-input__actions button:hover { transform: scale(1.04); }
.vx-text-input__actions button:active { transform: scale(0.95); }
/* --- Text editor with pause-pill highlight overlay --- */
.vx-text-editor { position: relative; margin-top: 0.75rem; }
/* Highlight layer + textarea MUST share identical box metrics so text lines up. */
.vx-text-input .vx-text-highlight,
.vx-text-input textarea {
	box-sizing: border-box;
	width: 100%;
	margin: 0;
	border: 0;
	border-radius: var(--r-2xl);
	padding: 1rem;
	font-family: inherit;
	font-size: 0.9375rem;
	line-height: 1.6;
	letter-spacing: normal;
	min-height: 10rem;
	white-space: pre-wrap;
	overflow-wrap: break-word;
	word-break: break-word;
}
/* Behind layer: paints the pills. */
.vx-text-input .vx-text-highlight {
	position: absolute; inset: 0;
	background: #fff;
	color: var(--foreground);
	box-shadow: var(--shadow-soft), inset 0 0 0 1px var(--border);
	overflow: hidden;
	pointer-events: none;
	z-index: 1;
}
/* Front layer: transparent text so the pills show through, caret stays visible. */
.vx-text-input textarea {
	position: relative;
	display: block;
	resize: none;
	background: transparent;
	color: transparent;
	caret-color: var(--foreground);
	box-shadow: var(--shadow-soft), inset 0 0 0 1px var(--border);
	outline: none;
	transition: box-shadow 0.2s ease;
	z-index: 2;
}
.vx-text-input textarea::placeholder { color: var(--muted-foreground); }
.vx-text-input textarea:focus,
.vx-text-input textarea:focus + .vx-text-highlight { box-shadow: var(--shadow-soft), inset 0 0 0 1px color-mix(in oklab, var(--primary) 40%, transparent); }
/* The pause pill itself. */
.vx-pause-pill {
	background: color-mix(in oklab, var(--primary) 14%, transparent);
	color: var(--primary);
	border-radius: 6px;
	padding: 0.05em 0.3em;
	font-weight: 600;
	font-size: 0.85em;
	white-space: nowrap;
}
/* CORRECTION: this rule used to also carry
       box-decoration-break: clone;
       -webkit-box-decoration-break: clone;
   That property's entire job is deciding how a box's background/border
   repaints itself at each FRAGMENT when an inline element wraps across
   multiple lines. This pill is `white-space: nowrap` — it can never
   fragment, so the property was doing nothing for its actual purpose.
   What it WAS doing, on some Android Chrome/WebView builds, is triggering a
   known rendering bug where the box gets painted twice, offset by a few
   pixels, in two different colours — exactly the doubled/ghosted "<#1.5#>"
   look reported in dark mode. Removing a property that could never fire for
   the reason it exists costs nothing and removes the only plausible cause
   of a genuine double-paint in the rule. */
.vx-text-input__meta { margin-top: 0.625rem; display: flex; align-items: center; justify-content: space-between; font-size: 0.6875rem; font-weight: 500; color: var(--muted-foreground); }
.vx-text-input__meta .is-near-limit { color: var(--destructive); }
/* Pause insert button + menu. */
.vx-text-input__tools { position: relative; margin-top: 0.625rem; display: flex; align-items: center; gap: 0.5rem; }
.vx-pause-btn { display: inline-flex; align-items: center; gap: 0.375rem; border-radius: var(--r-full); background: var(--secondary); border: 1px solid var(--border); padding: 0.3rem 0.7rem; font-size: 0.75rem; font-weight: 600; color: var(--foreground); cursor: pointer; transition: transform 0.2s ease; }
.vx-pause-btn:hover { transform: scale(1.04); }
.vx-pause-btn:active { transform: scale(0.95); }
.vx-pause-btn__tag { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--primary); font-size: 0.7rem; }
.vx-pause-menu { position: absolute; bottom: calc(100% + 0.4rem); left: 0; z-index: 20; display: flex; flex-wrap: wrap; gap: 0.3rem; padding: 0.5rem; background: #fff; border-radius: var(--r-xl); box-shadow: var(--shadow-elevated), inset 0 0 0 1px var(--border); }
.vx-pause-menu button { border: none; background: var(--secondary); color: var(--foreground); border-radius: var(--r-full); padding: 0.25rem 0.6rem; font-size: 0.72rem; font-weight: 600; cursor: pointer; transition: transform 0.15s ease, background 0.15s ease; }
.vx-pause-menu button:hover { transform: scale(1.05); background: color-mix(in oklab, var(--primary) 16%, var(--secondary)); color: var(--primary); }

.vx-controls-card { margin-top: 0; position: relative; overflow: visible; }
.vx-controls-card > * + * { margin-top: 1.25rem; }
.vx-controls-card__head h3 { margin: 0.25rem 0 0; font-size: 1.125rem; font-weight: 600; }
.vx-controls-row { display: grid; gap: 0.75rem; }
.vx-controls-row > * { min-width: 0; }   /* let grid cells shrink so inner strips can scroll */
.vx-emotion-field { min-width: 0; }
@media (min-width: 640px) { .vx-controls-row { grid-template-columns: repeat(2, 1fr); } }
.vx-controls-row--sliders { gap: 1.25rem; }
.vx-field-label-sm { margin-bottom: 0.375rem; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted-foreground); }

/* Voice picker */
.vx-voice-picker { position: relative; }
.vx-voice-picker.is-open { z-index: 40; }
.vx-voice-picker__trigger { display: flex; width: 100%; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); border: 1px solid var(--border); background: #fff; padding: 0.625rem 0.75rem; text-align: left; box-shadow: var(--shadow-soft); transition: border-color 0.2s ease; cursor: pointer; }
.vx-voice-picker__trigger:hover { border-color: color-mix(in oklab, var(--ring) 40%, transparent); }
.vx-voice-picker__trigger .vx-voice-avatar { width: 2.5rem; height: 2.5rem; border-radius: var(--r-xl); }
.vx-voice-picker__trigger-body { min-width: 0; flex: 1; }
.vx-voice-picker__trigger-body span:first-child { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-voice-picker__trigger-body span:last-child { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-voice-picker__trigger svg.vx-chev { color: var(--muted-foreground); transition: transform 0.3s ease; }
.vx-voice-picker.is-open .vx-chev { transform: rotate(180deg); }

.vx-voice-picker__panel { position: absolute; z-index: 50; margin-top: 0.5rem; width: 100%; overflow: hidden; border-radius: var(--r-3xl); padding: 0.75rem; }
/* Remove the > * rule since we reverted to overflow:hidden */
/* CORRECTION: was a hard-coded `rgba(255,255,255,0.7)` — invisible against a
   dark panel, since it never went through any variable. Pre-existing gap
   from before dark mode was added; caught while wiring up the sibling
   .vx-lang-select__search below, which had the identical bug. */
.vx-voice-picker__search { display: flex; align-items: center; gap: 0.5rem; border-radius: var(--r-2xl); background: var(--surface); box-shadow: inset 0 0 0 1px var(--border); padding: 0.5rem 0.75rem; }
.vx-voice-picker__search svg { color: var(--muted-foreground); flex-shrink: 0; }
.vx-voice-picker__search input { width: 100%; background: transparent; border: none; font-size: 0.875rem; outline: none; }
.vx-voice-picker__cats { margin-top: 0.75rem; display: flex; gap: 0.375rem; overflow-x: auto; padding-bottom: 0.25rem; }
.vx-voice-picker__cats button { flex-shrink: 0; border-radius: var(--r-full); border: none; background: var(--secondary); padding: 0.25rem 0.75rem; font-size: 0.6875rem; font-weight: 600; color: var(--muted-foreground); cursor: pointer; transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; }
.vx-voice-picker__cats button.is-active { background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); }
.vx-voice-picker__list { margin-top: 0.75rem; max-height: 18rem; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; gap: 0.375rem; padding-right: 0.25rem; }
.vx-voice-picker__option { display: flex; width: 100%; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); border: none; background: none; padding: 0.625rem; text-align: left; cursor: pointer; transition: background-color 0.2s ease; }
.vx-voice-picker__option:hover { background: rgba(255,255,255,0.7); }
.vx-voice-picker__option.is-selected { background: rgba(255,255,255,0.8); box-shadow: 0 0 0 1px color-mix(in oklab, var(--primary) 30%, transparent); }
.vx-voice-picker__option .vx-voice-avatar { width: 2.5rem; height: 2.5rem; border-radius: var(--r-xl); }
.vx-voice-picker__option-body { min-width: 0; flex: 1; }
.vx-voice-picker__option-name-row { display: flex; align-items: center; gap: 0.5rem; }
.vx-voice-picker__option-name-row span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-voice-picker__option-cat { border-radius: var(--r-full); background: var(--secondary); padding: 0.0625rem 0.375rem; font-size: 0.5625rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted-foreground); }
.vx-voice-picker__option-meta { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-voice-picker__option-play { display: grid; place-items: center; width: 2rem; height: 2rem; border-radius: var(--r-full); background: var(--secondary); color: var(--foreground); flex-shrink: 0; opacity: 1; }
.vx-voice-picker__empty { padding: 1.5rem 0.5rem; text-align: center; font-size: 0.75rem; color: var(--muted-foreground); }

/* Language select */
.vx-lang-select { position: relative; }
.vx-lang-select.is-open { z-index: 40; }
.vx-lang-select__trigger { display: flex; width: 100%; align-items: center; justify-content: space-between; border-radius: var(--r-2xl); border: 1px solid var(--border); background: #fff; padding: 0.625rem 0.875rem; text-align: left; box-shadow: var(--shadow-soft); transition: border-color 0.2s ease; cursor: pointer; }
.vx-lang-select__trigger:hover { border-color: color-mix(in oklab, var(--ring) 40%, transparent); }
.vx-lang-select__trigger-value { display: flex; align-items: center; gap: 0.5rem; }
.vx-lang-select__trigger-value svg:first-child { color: var(--muted-foreground); }
.vx-lang-select__trigger-value span { font-size: 0.875rem; font-weight: 500; }
.vx-lang-select__trigger svg.vx-chev { color: var(--muted-foreground); transition: transform 0.3s ease; }
.vx-lang-select.is-open .vx-chev { transform: rotate(180deg); }
/* This component (and the JS wired to it) predates any markup that actually
   used it — first real use is the Translate feature below. Its two hard-
   coded-white values are fixed the same way the voice picker's twin bug
   just was, above. Its width is now capped (previously unlimited at 100% of
   the trigger, which is fine at ~360px but would stretch absurdly wide on a
   tablet-width app column) and it gets the same containment every other
   picker panel in the app already carries. */
.vx-lang-select__panel { position: absolute; z-index: 50; margin-top: 0.5rem; width: max(100%, 14rem); max-width: 20rem; overflow: hidden; border-radius: var(--r-2xl); padding: 0.5rem; contain: layout style; }
.vx-lang-select__search { display: flex; align-items: center; gap: 0.5rem; border-radius: var(--r-xl); background: var(--surface); box-shadow: inset 0 0 0 1px var(--border); padding: 0.375rem 0.625rem; }
.vx-lang-select__search input { width: 100%; background: transparent; border: none; font-size: 0.875rem; outline: none; color: var(--foreground); }
.vx-lang-select__search input::placeholder { color: var(--muted-foreground); }
.vx-lang-select__list { margin-top: 0.5rem; max-height: 16rem; overflow-y: auto; overscroll-behavior: contain; }
.vx-lang-select__option { display: flex; width: 100%; align-items: center; justify-content: space-between; border-radius: var(--r-xl); border: none; background: none; padding: 0.5rem 0.75rem; text-align: left; font-size: 0.875rem; color: var(--muted-foreground); cursor: pointer; transition: background-color 0.2s ease; }
.vx-lang-select__option:hover { background: var(--secondary); }
.vx-lang-select__option.is-selected { font-weight: 600; color: var(--foreground); }
.vx-lang-select__option-dot { width: 0.375rem; height: 0.375rem; border-radius: 50%; background: var(--brand-purple, var(--primary)); }

/* Slider row */
.vx-slider-row__head { display: flex; align-items: center; justify-content: space-between; }
.vx-slider-row__head p { margin: 0; font-size: 0.75rem; font-weight: 600; }
.vx-slider-row__value { border-radius: var(--r-full); background: var(--secondary); padding: 0.125rem 0.5rem; font-size: 0.625rem; font-weight: 600; }
.vx-slider-row__track-wrap { position: relative; margin-top: 0.5rem; height: 1.5rem; }
.vx-slider-row__track-bg { position: absolute; inset-inline: 0; top: 50%; height: 0.375rem; transform: translateY(-50%); border-radius: var(--r-full); background: var(--secondary); pointer-events: none; }
.vx-slider-row__track-fill { position: absolute; top: 50%; height: 0.375rem; transform: translateY(-50%); border-radius: var(--r-full); background-image: var(--gradient-brand); pointer-events: none; }
.vx-slider-row input[type="range"] { position: absolute; inset: 0; width: 100%; -webkit-appearance: none; appearance: none; background: transparent; margin: 0; z-index: 2; cursor: pointer; }
.vx-slider-row input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; height: 1.25rem; width: 1.25rem; border-radius: 50%; background: #fff; box-shadow: 0 2px 8px rgba(15,23,42,0.18); border: 1px solid var(--border); cursor: pointer; }
.vx-slider-row input[type="range"]::-moz-range-thumb { height: 1.25rem; width: 1.25rem; border-radius: 50%; background: #fff; box-shadow: 0 2px 8px rgba(15,23,42,0.18); border: 1px solid var(--border); cursor: pointer; }

/* Emotion / format pills */
.vx-pill-group {
	display: flex;
	flex-wrap: nowrap;
	min-width: 0;
	max-width: 100%;
	gap: 0.375rem;
	overflow-x: auto;
	overflow-y: hidden;
	-webkit-overflow-scrolling: touch;
	scroll-behavior: smooth;
	scrollbar-width: none;              /* Firefox */
	-ms-overflow-style: none;          /* old Edge */
	padding-bottom: 2px;               /* room so active shadow isn't clipped */
	scroll-snap-type: x proximity;
	-webkit-mask-image: linear-gradient(to right, transparent 0, #000 12px, #000 calc(100% - 18px), transparent 100%);
	        mask-image: linear-gradient(to right, transparent 0, #000 12px, #000 calc(100% - 18px), transparent 100%);
}
.vx-pill-group::-webkit-scrollbar { display: none; }  /* Chrome/Safari */
.vx-pill-group button {
	flex: 0 0 auto;                    /* never shrink, never wrap */
	white-space: nowrap;
	scroll-snap-align: start;
	border-radius: var(--r-full);
	border: none;
	background: var(--secondary);
	padding: 0.375rem 0.75rem;
	font-size: 0.6875rem;
	font-weight: 600;
	color: var(--muted-foreground);
	cursor: pointer;
	transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
.vx-pill-group button.is-active { background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); }
.vx-format-toggle { display: inline-flex; border-radius: var(--r-full); background: var(--secondary); padding: 0.25rem; }
.vx-format-toggle button { border-radius: var(--r-full); border: none; background: none; padding: 0.375rem 0.875rem; font-size: 0.6875rem; font-weight: 600; color: var(--muted-foreground); cursor: pointer; transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; }
.vx-format-toggle button.is-active { background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); }

/* Generate button (large, with inline progress fill) */
.vx-generate-lg { position: relative; width: 100%; overflow: hidden; border-radius: var(--r-2xl); background-image: var(--gradient-brand); border: none; padding: 1rem 1.5rem; font-size: 0.875rem; font-weight: 600; color: #fff; box-shadow: var(--shadow-glow); transition: transform 0.2s ease, opacity 0.2s ease; cursor: pointer; }
.vx-generate-lg:hover { transform: scale(1.01); }
.vx-generate-lg:active { transform: scale(0.99); }
.vx-generate-lg:disabled { opacity: 0.7; cursor: default; }
.vx-generate-lg__label { position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; }
.vx-generate-lg__fill { position: absolute; inset-block: 0; left: 0; background: rgba(255,255,255,0.15); transition: width 0.15s ease; width: 0%; }
.vx-generate-lg[data-busy="true"] .vx-generate-lg__idle { display: none; }
.vx-generate-lg:not([data-busy="true"]) .vx-generate-lg__busy { display: none; }

/* =================================================================
   Player
   ================================================================= */
.vx-player { position: relative; overflow: hidden; border-radius: 24px; padding: 1.25rem; }
@media (min-width: 640px) { .vx-player { padding: 1.5rem; } }
.vx-player__glow { position: absolute; pointer-events: none; right: -4rem; top: -4rem; width: 12rem; height: 12rem; border-radius: 999px; opacity: 0.3; filter: blur(64px); }
.vx-player__top { position: relative; display: flex; align-items: center; justify-content: space-between; }
.vx-player__voice { display: flex; min-width: 0; align-items: center; gap: 0.75rem; }
.vx-player__voice .vx-voice-avatar { width: 3rem; height: 3rem; border-radius: var(--r-2xl); }
.vx-player__voice-body { min-width: 0; }
.vx-player__voice-body p:first-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-player__voice-body p:last-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.75rem; color: var(--muted-foreground); }
.vx-player__status { border-radius: var(--r-full); background: var(--secondary); padding: 0.25rem 0.625rem; font-size: 0.625rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted-foreground); }
.vx-player__textbox { position: relative; margin-top: 1.25rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.7); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); padding: 1rem; }
.vx-player__textbox p { margin: 0; font-size: 0.875rem; line-height: 1.6; color: color-mix(in oklab, var(--foreground) 80%, transparent); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.vx-player__shimmer { position: absolute; inset: 1rem; flex-direction: column; gap: 0.625rem; }
.vx-player__shimmer:not([hidden]) { display: flex; }
.vx-player__shimmer span { height: 0.75rem; border-radius: var(--r-full); background: var(--muted); }
.vx-player__shimmer span:nth-child(1) { width: 75%; }
.vx-player__shimmer span:nth-child(2) { width: 50%; }
.vx-player__shimmer span:nth-child(3) { width: 66%; }
.vx-player__wave-wrap { margin-top: 1.25rem; }
.vx-player__progress { margin-top: 0.75rem; height: 0.375rem; width: 100%; overflow: hidden; border-radius: var(--r-full); background: var(--secondary); }
.vx-player__progress-bar { height: 100%; width: 0%; background-image: var(--gradient-brand); transition: width 0.15s ease; }
.vx-player__time { margin-top: 0.375rem; display: flex; align-items: center; justify-content: space-between; font-size: 0.6875rem; font-weight: 600; color: var(--muted-foreground); }
.vx-player__actions { margin-top: 1.25rem; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 0.625rem; }
.vx-player__actions .vx-play-btn { width: 3.5rem; height: 3.5rem; }
.vx-player__action-btn { display: inline-flex; align-items: center; gap: 0.375rem; border-radius: var(--r-full); background: var(--secondary); border: none; padding: 0.5rem 0.875rem; font-size: 0.75rem; font-weight: 600; color: var(--foreground); transition: transform 0.2s ease; cursor: pointer; }
.vx-player__action-btn:disabled { opacity: 0.6; cursor: default; }
.vx-player__action-btn:hover:not(:disabled) { transform: translateY(-2px); }
.vx-player__fav-btn { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; border-radius: var(--r-full); background: var(--secondary); color: var(--foreground); border: none; transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; cursor: pointer; }
.vx-player__fav-btn:active { transform: scale(0.9); }
.vx-player__fav-btn.is-active { background: #f43f5e; color: #fff; box-shadow: var(--shadow-soft); }

/* Waveform + play button (shared across Player / Voice Library / rail — duplicated from landing.css since app.css loads on different pages) */
.vx-waveform { display: flex; align-items: flex-end; gap: 3px; height: 3.5rem; flex: 1; min-width: 0; }
.vx-waveform__bar { width: 3px; border-radius: var(--r-full); background-image: var(--gradient-brand); transform-origin: center; transition: transform 0.3s ease; }
.vx-play-btn--xs { width: 1.75rem; height: 1.75rem; box-shadow: none; background: var(--secondary); color: var(--foreground); background-image: none; }
.vx-play-btn { display: grid; place-items: center; flex-shrink: 0; width: 3rem; height: 3rem; border-radius: var(--r-full); background-image: var(--gradient-brand); color: #fff; border: none; box-shadow: var(--shadow-glow); transition: transform 0.2s ease; cursor: pointer; }
.vx-play-btn:hover { transform: scale(1.05); }
.vx-play-btn:active { transform: scale(0.95); }
.vx-play-btn:disabled { opacity: 0.6; cursor: default; }

/* ── Play glyph centring ─────────────────────────────────────────────────
   The ONLY change to this button. The glyph sat slightly high inside the
   circle, on every page.

   Cause: there is no `svg { display: block }` anywhere in the theme, so an
   <svg> is inline-level — it sits on a text baseline. Each icon is wrapped in
   a <span> that grid blockifies, and that span's line box is `line-height`
   tall (1.5 from body) with descender space reserved UNDER the baseline. The
   grid centres the span, the span is taller than the glyph, and the glyph
   ends up above the middle of it.

   `line-height: 0` collapses the wrapper to the glyph, and `display: block`
   takes the SVG off the baseline entirely. Both are needed: the first fixes
   the wrapped icons, the second the bare ones on Favourites. */
.vx-play-btn > * { display: grid; place-items: center; line-height: 0; }

/* ------------------------------------------------------------------
   Icon-only wrapper spans.

   THE BUG: an <svg> is an inline element by default, so it sits on the
   TEXT BASELINE of whatever line box contains it. A line box reserves
   room under the baseline for descenders (the tails of g, y, p), and
   that reserved strip is counted as part of the span's height. The span
   then gets centred in its button — descender space and all — which
   pushes the glyph itself visibly ABOVE the button's true centre.

   That is why the Translate button's arrow and the voice card's play
   triangle both sat high: their wrapper spans had no styling at all, so
   each kept a full text line box around a graphic that has no text in
   it. `.vx-play-btn > *` above already solved this for the round play
   buttons; these spans were simply never given the same treatment.

   Making each wrapper a flex container removes the line box entirely —
   a flex item is blockified, so there is no baseline to sit on and the
   span's height becomes exactly the icon's height.
   ------------------------------------------------------------------ */
.vx-translate-go__arrow,
.vx-translate-go__spin,
.vx-preview-play,
.vx-preview-pause,
.vx-history-row__play-icon,
.vx-history-row__pause-icon,
.vx-fav-clip-row__play-icon,
.vx-fav-clip-row__pause-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	line-height: 0;
	flex: 0 0 auto;
}

/* `hidden` must keep winning over the inline-flex above — an attribute
   that says "this is not here" losing to a display rule is a classic
   source of both icons showing at once. */
.vx-translate-go__arrow[hidden],
.vx-translate-go__spin[hidden],
.vx-preview-play[hidden],
.vx-preview-pause[hidden],
.vx-history-row__play-icon[hidden],
.vx-history-row__pause-icon[hidden],
.vx-fav-clip-row__play-icon[hidden],
.vx-fav-clip-row__pause-icon[hidden] {
	display: none;
}
.vx-play-btn svg { display: block; }


/* =================================================================
   Voice Library page
   ================================================================= */
.vx-voices-toolbar { margin-top: 1.25rem; border-radius: 24px; padding: 1rem; }
@media (min-width: 640px) { .vx-voices-toolbar { padding: 1.25rem; } }
.vx-voices-toolbar > * + * { margin-top: 1rem; }
.vx-search-input { display: flex; align-items: center; gap: 0.5rem; border-radius: var(--r-2xl); background: #fff; box-shadow: var(--shadow-soft), inset 0 0 0 1px var(--border); padding: 0.625rem 0.875rem; }
.vx-search-input svg { color: var(--muted-foreground); flex-shrink: 0; }
.vx-search-input input { width: 100%; background: transparent; border: none; font-size: 0.875rem; outline: none; }
.vx-cat-filter { display: flex; gap: 0.375rem; overflow-x: auto; padding-bottom: 0.25rem; }
.vx-cat-filter button { flex-shrink: 0; border-radius: var(--r-full); border: none; background: var(--secondary); padding: 0.375rem 0.875rem; font-size: 0.6875rem; font-weight: 600; color: var(--muted-foreground); cursor: pointer; transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; }
.vx-cat-filter button:active { transform: scale(0.95); }
.vx-cat-filter button.is-active { background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); }

/* Toolbar meta row: the active language chip and the result count. The count
   element is new — app.js has always written to `#vx-voices-count`, but no
   template ever contained it, so the readout silently did nothing. */
.vx-voices-meta { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.vx-voices-chip {
	display: inline-flex; align-items: center; gap: 0.375rem;
	border-radius: var(--r-full); background-image: var(--gradient-brand); color: #fff;
	padding: 0.3125rem 0.625rem 0.3125rem 0.75rem;
	font-size: 0.75rem; font-weight: 600; text-decoration: none;
	transition: transform 0.2s ease;
}
.vx-voices-chip:active { transform: scale(0.96); }
#vx-voices-count { margin-left: auto; font-size: 0.75rem; font-weight: 500; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }

/* ── Voice library grid ──────────────────────────────────────────────────
   Two cards per row on every phone, three from 640px, four from 1100px.

   `minmax(0, 1fr)` rather than plain `1fr`: a grid track's default minimum is
   min-content, so a single long voice name would widen the column past the
   viewport and push the whole page sideways. This is the same failure that
   broke the Profile page, so it is spelled out here rather than left to the
   default.

   At 412px each card is ~182px wide with ~158px of usable space, and every
   size below is chosen against that number rather than scaled down from the
   old full-width card. */
.vx-voice-grid {
	margin-top: 1rem;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.75rem;
}
@media (min-width: 640px)  { .vx-voice-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; } }
@media (min-width: 1100px) { .vx-voice-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.vx-voice-card {
	position: relative;
	min-width: 0;
	display: flex;
	flex-direction: column;
	border-radius: var(--r-xl);
	background: var(--card);
	border: 1px solid var(--border);
	box-shadow: var(--shadow-soft);
	padding: 0.75rem;
	contain: layout style;
	transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.vx-voice-card:active { transform: scale(0.985); }
@media (hover: hover) and (pointer: fine) {
	.vx-voice-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-elevated); }
}

/* The `.vx-voice-card__glow` element is gone from the markup. It was a
   10rem box running `filter: blur(64px)`, repeated once per card — 25+ large
   blurred surfaces on this one page, for a tint barely visible behind the
   avatar. The card tint now comes from the avatar itself, which was always
   the thing people actually look at. */

.vx-voice-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }
.vx-voice-card__top .vx-voice-avatar { width: 3.25rem; height: 3.25rem; border-radius: var(--r-lg); box-shadow: var(--shadow-soft); }

.vx-voice-card__name {
	margin: 0.625rem 0 0;
	font-family: var(--font-display);
	font-size: 0.9375rem;
	font-weight: 600;
	letter-spacing: -0.01em;
	line-height: 1.25;
	overflow-wrap: anywhere;
}
.vx-voice-card__meta {
	margin: 0.125rem 0 0;
	font-size: 0.6875rem;
	font-weight: 500;
	color: var(--muted-foreground);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* 18 bars, not 42. At 158px of usable width 42 bars left under 3px each —
   a grey smear rather than a waveform. This also cuts the DOM on this page
   from 25x42 spans to 25x18. */
.vx-voice-card__wave {
	margin-top: 0.625rem;
	height: 2.25rem;
	border-radius: var(--r-md);
	background: var(--surface);
	border: 1px solid var(--border);
	padding: 0 0.5rem;
	overflow: hidden;
	display: flex;
	align-items: center;
}
/* The waveform itself is the flex row. `display: contents` would have worked
   too, but it removes the element's box — and with it the paint containment
   perf.css puts on `.vx-waveform`, which is what keeps a playing preview from
   invalidating the card around it. */
.vx-voice-card__wave .vx-waveform {
	height: 100%;
	align-items: center;
	justify-content: center;
	gap: 2px;
}

/* One icon button plus one full-width primary. Two text buttons side by side
   cannot fit 158px without truncating "Use voice". */
.vx-voice-card__actions { display: flex; gap: 0.375rem; margin-top: 0.625rem; }
.vx-voice-card__preview {
	width: 2.25rem; height: 2.25rem; flex-shrink: 0;
	display: grid; place-items: center;
	border: 0; cursor: pointer; color: var(--foreground);
	border-radius: var(--r-full); background: var(--secondary);
	transition: background-color 0.2s ease, transform 0.2s ease;
}
.vx-voice-card__preview:active { transform: scale(0.92); }
.vx-voice-card__use {
	flex: 1; min-width: 0;
	display: inline-flex; align-items: center; justify-content: center;
	border-radius: var(--r-full); background-image: var(--gradient-brand); color: #fff;
	padding: 0.5rem; font-size: 0.75rem; font-weight: 600;
	white-space: nowrap; text-decoration: none;
	box-shadow: var(--shadow-soft); transition: transform 0.2s ease;
}
.vx-voice-card__use:active { transform: scale(0.96); }

/* Kept in the DOM because app.js writes to it, hidden because the button is
   icon-only now. Clipped rather than `display: none` so it still acts as the
   button's accessible label. */
.vx-visually-hidden {
	position: absolute !important;
	width: 1px; height: 1px;
	margin: -1px; padding: 0; border: 0;
	clip: rect(0 0 0 0); clip-path: inset(50%);
	overflow: hidden; white-space: nowrap;
}

.vx-fav-toggle { display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: var(--r-full); background: var(--secondary); color: var(--foreground); border: none; transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; cursor: pointer; }
.vx-fav-toggle:active { transform: scale(0.9); }
.vx-fav-toggle.is-active { background: #f43f5e; color: #fff; box-shadow: var(--shadow-soft); }
.vx-voice-card__tags { position: relative; margin-top: 1rem; display: flex; flex-wrap: wrap; gap: 0.375rem; }
.vx-voice-card__tags span { border-radius: var(--r-full); background: var(--secondary); padding: 0.125rem 0.5rem; font-size: 0.625rem; font-weight: 600; }
.vx-voice-card__wave { position: relative; margin-top: 1rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.7); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); padding: 0.75rem; }
.vx-voice-card__actions { position: relative; margin-top: 1rem; display: flex; gap: 0.5rem; }
.vx-voice-card__preview { display: inline-flex; flex: 1; align-items: center; justify-content: center; gap: 0.375rem; border-radius: var(--r-full); background: var(--secondary); border: none; padding: 0.5rem 0.875rem; font-size: 0.75rem; font-weight: 600; color: var(--foreground); box-shadow: var(--shadow-soft); transition: transform 0.2s ease; cursor: pointer; }
.vx-voice-card__preview:hover { transform: scale(1.02); }
.vx-voice-card__preview:active { transform: scale(0.95); }
.vx-voice-card__use { display: inline-flex; flex: 1; align-items: center; justify-content: center; border-radius: var(--r-full); background-image: var(--gradient-brand); padding: 0.5rem 0.875rem; font-size: 0.75rem; font-weight: 600; color: #fff; text-decoration: none; box-shadow: var(--shadow-soft); transition: transform 0.2s ease; }
.vx-voice-card__use:hover { transform: scale(1.02); }
.vx-voice-card__use:active { transform: scale(0.95); }
.vx-voices-empty { grid-column: 1 / -1; border-radius: 24px; padding: 2.5rem; text-align: center; }
.vx-voices-empty p:first-of-type { margin: 0.5rem 0 0; font-family: var(--font-display); font-size: 1.125rem; font-weight: 600; }
.vx-voices-empty p:last-of-type { margin: 0.25rem 0 0; font-size: 0.875rem; color: var(--muted-foreground); }

/* =================================================================
   Audio History page
   ================================================================= */
.vx-audio-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem; }
.vx-audio-filters button { border-radius: var(--r-full); border: none; background: var(--secondary); padding: 0.375rem 0.875rem; font-size: 0.6875rem; font-weight: 600; color: var(--muted-foreground); cursor: pointer; transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; }
.vx-audio-filters button:active { transform: scale(0.95); }
.vx-audio-filters button.is-active { background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); }

.vx-audio-panel { margin-top: 1.25rem; border-radius: 24px; padding: 0.75rem; }
@media (min-width: 640px) { .vx-audio-panel { padding: 1rem; } }
.vx-audio-empty { padding: 2.5rem; text-align: center; }
.vx-audio-empty p:first-of-type { margin: 0.5rem 0 0; font-family: var(--font-display); font-size: 1.125rem; font-weight: 600; }
.vx-audio-empty p:last-of-type { margin: 0.25rem 0 0; font-size: 0.875rem; color: var(--muted-foreground); }
.vx-audio-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.vx-audio-row { position: relative; display: flex; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.6); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); padding: 0.75rem; transition: transform 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease; }
.vx-audio-row:hover { transform: translateY(-2px); background: rgba(255,255,255,0.8); box-shadow: var(--shadow-soft); }
.vx-audio-row__body { min-width: 0; flex: 1; }
.vx-audio-row__title { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-audio-row__meta { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-audio-row__format { display: none; border-radius: var(--r-full); background: var(--secondary); padding: 0.125rem 0.5rem; font-size: 0.625rem; font-weight: 600; }
@media (min-width: 640px) { .vx-audio-row__format { display: inline-block; } }
.vx-audio-row__tools { display: flex; align-items: center; gap: 0.125rem; }
@media (min-width: 640px) { .vx-audio-row__tools { gap: 0.25rem; } }
.vx-audio-row__tools .vx-icon-btn { width: 1.875rem; height: 1.875rem; }
@media (min-width: 640px) { .vx-audio-row__tools .vx-icon-btn { width: 2.25rem; height: 2.25rem; } }
.vx-audio-row__tools button:hover { color: inherit; background: var(--secondary); }
.vx-audio-row__tools button.is-danger:hover { background: #f43f5e; color: #fff; }
.vx-audio-row__tools .vx-audio-row__love.is-active { color: #f43f5e; }
.vx-audio-row__menu-wrap { display: none; }
.vx-audio-row__menu { position: absolute; right: 0; top: 2.5rem; z-index: 10; width: 10rem; border-radius: var(--r-2xl); padding: 0.375rem; }
.vx-audio-row__menu button { display: flex; width: 100%; align-items: center; gap: 0.5rem; border-radius: var(--r-xl); border: none; background: none; padding: 0.5rem 0.75rem; text-align: left; font-size: 0.75rem; font-weight: 600; cursor: pointer; color: var(--foreground) !important; opacity: 1; }
.vx-audio-row__menu button svg { stroke-width: 2.5; }
.vx-audio-row__menu button:hover { background: rgba(255,255,255,0.7); }
.vx-audio-row__menu button:disabled { opacity: 0.35 !important; cursor: default; pointer-events: none; color: var(--muted-foreground) !important; }
.vx-audio-row__menu button.is-danger { color: #e11d48 !important; }
.vx-audio-row__menu button.is-danger:hover { background: rgba(244,63,94,0.1); }

/* =================================================================
   Favorites page
   ================================================================= */
.vx-fav-section + .vx-fav-section { margin-top: 1.25rem; }
.vx-fav-voice-grid { margin-top: 1rem; display: grid; gap: 0.75rem; }
@media (min-width: 640px) { .vx-fav-voice-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1280px) { .vx-fav-voice-grid { grid-template-columns: repeat(3, 1fr); } }
.vx-fav-voice-card { position: relative; overflow: hidden; border-radius: var(--r-2xl); background: rgba(255,255,255,0.7); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6); padding: 1rem; transition: transform 0.3s ease, box-shadow 0.3s ease; }
.vx-fav-voice-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-soft); }
.vx-fav-voice-card__glow { position: absolute; pointer-events: none; right: -2.5rem; top: -2.5rem; width: 7rem; height: 7rem; border-radius: 999px; opacity: 0.25; filter: blur(48px); }
.vx-fav-voice-card__top { position: relative; display: flex; align-items: center; gap: 0.75rem; }
.vx-fav-voice-card__top .vx-voice-avatar { width: 2.75rem; height: 2.75rem; border-radius: var(--r-2xl); }
.vx-fav-voice-card__body { min-width: 0; flex: 1; }
.vx-fav-voice-card__body p:first-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-fav-voice-card__body p:last-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-fav-voice-card__row { position: relative; margin-top: 0.75rem; display: flex; align-items: center; gap: 0.5rem; }
.vx-fav-voice-card__preview { display: inline-flex; flex: 1; align-items: center; justify-content: center; gap: 0.375rem; border-radius: var(--r-full); background: var(--secondary); border: none; padding: 0.375rem 0.75rem; font-size: 0.6875rem; font-weight: 600; color: var(--foreground); transition: transform 0.2s ease; cursor: pointer; }
.vx-fav-voice-card__preview:hover { transform: scale(1.02); }
.vx-fav-voice-card__use { display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r-full); background-image: var(--gradient-brand); padding: 0.375rem 0.75rem; font-size: 0.6875rem; font-weight: 600; color: #fff; box-shadow: var(--shadow-soft); transition: transform 0.2s ease; }
.vx-fav-voice-card__use:hover { transform: scale(1.03); }

.vx-fav-clip-list { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.vx-fav-clip-row { display: flex; align-items: center; gap: 0.75rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.6); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); padding: 0.75rem; transition: transform 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease; }
.vx-fav-clip-row:hover { transform: translateY(-2px); background: rgba(255,255,255,0.8); box-shadow: var(--shadow-soft); }
.vx-fav-clip-row__body { min-width: 0; flex: 1; }
.vx-fav-clip-row__body p:first-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
.vx-fav-clip-row__body p:last-child { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.6875rem; color: var(--muted-foreground); }
.vx-fav-clip-row__tools { display: flex; align-items: center; gap: 0.25rem; }
/* The old rule here was `.vx-fav-clip-row .vx-play-btn svg:last-child
   { display: none }` — a CSS-only way of hiding the pause icon, with no
   matching state for "now playing".

   That is what broke the button. The click handler swapped the icons by
   writing inline styles: play got `display:none`, and pause got
   `display:''`. Clearing an inline style does not force an element
   visible, it just hands control back to the stylesheet — and the
   stylesheet said `display:none`. So after the first tap BOTH icons were
   hidden and the button became an empty coloured circle until reload.

   The markup now uses the same wrapper-span + [hidden] pattern the audio
   history rows already use, and the JS toggles the attribute rather than
   fighting a stylesheet rule with inline styles. */
.vx-unpin-btn { display: grid; place-items: center; flex-shrink: 0; width: 2.25rem; height: 2.25rem; border-radius: var(--r-full); background: #f43f5e; color: #fff; box-shadow: var(--shadow-soft); border: none; transition: transform 0.2s ease; cursor: pointer; }
.vx-unpin-btn:active { transform: scale(0.9); }

/* Empty state */
.vx-empty-state { margin-top: 1rem; border-radius: var(--r-2xl); background: rgba(255,255,255,0.55); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5); padding: 2rem; text-align: center; }
.vx-empty-state__icon { margin-inline: auto; display: grid; place-items: center; width: 3rem; height: 3rem; border-radius: var(--r-2xl); background-image: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-soft); }
.vx-empty-state__title { margin: 0.75rem 0 0; font-family: var(--font-display); font-size: 1rem; font-weight: 600; }
.vx-empty-state__subtitle { margin: 0.25rem 0 0; font-size: 0.875rem; color: var(--muted-foreground); }
.vx-empty-state__cta { margin-top: 1rem; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r-full); background-image: var(--gradient-brand); padding: 0.5rem 1rem; font-size: 0.75rem; font-weight: 600; color: #fff; box-shadow: var(--shadow-glow); transition: transform 0.2s ease; }
.vx-empty-state__cta:hover { transform: scale(1.03); }
.vx-empty-state__cta:active { transform: scale(0.95); }

/* =================================================================
   Settings page
   (No Lovable source exists for this screen yet — built to match the
   same design system/components as the rest of the app shell: glass
   cards, the same toggle/pill/slider patterns, same spacing scale.)
   ================================================================= */
.vx-settings-grid { margin-top: 1.25rem; display: grid; gap: 1.25rem; }
@media (min-width: 1024px) { .vx-settings-grid { grid-template-columns: 1.3fr 1fr; } }
.vx-settings-grid__col { display: flex; flex-direction: column; gap: 1.25rem; }

.vx-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.875rem 0; border-bottom: 1px solid color-mix(in oklab, var(--border) 60%, transparent); }
.vx-settings-row:last-child { border-bottom: none; padding-bottom: 0; }
.vx-settings-row:first-child { padding-top: 0; }
.vx-settings-row__label { min-width: 0; }
.vx-settings-row__label p:first-child { margin: 0; font-size: 0.875rem; font-weight: 600; }
.vx-settings-row__value { font-size: 0.8125rem; font-weight: 600; color: var(--foreground); white-space: nowrap; }
.vx-settings-row__action { display: inline-flex; align-items: center; border-radius: var(--r-full); background: var(--secondary); padding: 0.375rem 0.75rem; font-size: 0.75rem; font-weight: 600; color: var(--foreground); white-space: nowrap; transition: background-color 0.2s ease; }
.vx-settings-row__action:hover { background: var(--accent); }
.vx-settings-row__label p:last-child { margin: 0.125rem 0 0; font-size: 0.75rem; color: var(--muted-foreground); }

.vx-switch { position: relative; display: inline-flex; flex-shrink: 0; width: 2.75rem; height: 1.625rem; border-radius: var(--r-full); background: var(--secondary); border: none; padding: 0; cursor: pointer; transition: background-color 0.2s ease; }
.vx-switch.is-on { background-image: var(--gradient-brand); }
.vx-switch__thumb { position: absolute; top: 0.1875rem; left: 0.1875rem; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(15,23,42,0.25); transition: transform 0.2s ease; }
.vx-switch.is-on .vx-switch__thumb { transform: translateX(1.125rem); }



/* Single-column profile stack — matches profile-preview.html at every width.
   The two-column grid was reordering the sections on desktop. */
.vx-profile-stack { display: grid; gap: 1.5rem; max-width: 44rem; min-width: 0; }
/* Grid items default to `min-width: auto`, which means the column refuses to
   shrink below the widest item's min-content width. One long unwrapped value
   in a row was enough to push the whole page wider than the viewport and send
   everything off the right edge. `min-width: 0` lets the column shrink and the
   text wrap, which is what it should have had from the start. */
.vx-profile-stack > *,
.vx-profile-stack > * > .vx-card { min-width: 0; }

/* Match the preview's card geometry. Scoped to the profile stack on purpose:
   `.vx-card` is the shared surface for Settings, Dashboard, Text and
   Favorites, so changing its radius or padding globally would resize four
   other screens to fix one. */
.vx-profile-stack .vx-card { border-radius: var(--r-xl); padding: 1rem; }
.vx-profile-stack > div > .vx-card { margin-top: 0; }

/* ── Access gate ─────────────────────────────────────────────────────────
   The Profile page's account card. One card, two states.

   Visibility is driven entirely by the two `hidden` attributes app.js
   already toggles — `#vx-access-form-wrap` and `#vx-access-verified` — so
   the CSS never has to know which state it is in. The record block keys off
   the same signal with a sibling selector rather than needing a third hook,
   which is why no new JS was required for this redesign.

   The lock -> check crossfade is the one piece of motion here on purpose:
   unlocking is the page's entire job, so it is the only moment worth
   animating. Both icons are stacked and cross-faded rather than swapped, so
   there is no layout shift and nothing to re-measure. */
.vx-gate { padding: 1rem; }
.vx-gate__head { display: flex; align-items: center; gap: 0.75rem; }
.vx-gate__headings { min-width: 0; }
.vx-gate__title { margin: 0; font-size: 0.9375rem; font-weight: 600; }
.vx-gate__sub { margin: 0.1rem 0 0; font-size: 0.8125rem; color: var(--muted-foreground); }

.vx-gate__badge {
	position: relative; flex-shrink: 0;
	display: grid; place-items: center;
	width: 2.5rem; height: 2.5rem;
	border-radius: var(--r-md);
	background: var(--secondary); color: var(--muted-foreground);
	transition: background-color 0.45s cubic-bezier(0.22, 1, 0.36, 1), color 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
.vx-gate__icon { position: absolute; display: grid; place-items: center; transition: opacity 0.35s ease, transform 0.45s cubic-bezier(0.22, 1, 0.36, 1); }
.vx-gate__icon--check { opacity: 0; transform: scale(0.6); }
/* `.is-verified` is set by setGateState() in app.js. A class rather than
   :has() so the crossfade also works on Safari < 15.4 and Firefox < 121. */
.vx-gate.is-verified .vx-gate__badge { background: rgba(5, 150, 105, 0.10); color: #059669; }
.vx-gate.is-verified .vx-gate__icon--lock { opacity: 0; transform: scale(0.6); }
.vx-gate.is-verified .vx-gate__icon--check { opacity: 1; transform: scale(1); }

.vx-gate__status { margin-left: auto; flex-shrink: 0; display: inline-flex; align-items: center; gap: 0.3125rem; font-size: 0.8125rem; font-weight: 600; color: #059669; }
.vx-gate__status[hidden] { display: none; }

/* Locked */
.vx-codebar { display: flex; gap: 0.5rem; margin-top: 0.875rem; }
.vx-codebar input {
	flex: 1; min-width: 0; font-family: inherit; font-size: 0.9375rem; color: inherit;
	border-radius: var(--r-full); border: 1px solid var(--border); background: var(--surface);
	padding: 0.75rem 1rem; letter-spacing: 0.08em; outline: none;
	transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.vx-codebar input::placeholder { color: var(--muted-foreground); letter-spacing: 0; }
.vx-codebar input:focus { border-color: var(--brand-purple); box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.16); }
.vx-codebar__submit {
	flex-shrink: 0; border: 0; cursor: pointer; color: #fff;
	border-radius: var(--r-full); background-image: var(--gradient-brand);
	padding: 0.75rem 1.25rem; font-size: 0.875rem; font-weight: 600;
	box-shadow: var(--shadow-soft); transition: transform 0.2s ease, opacity 0.2s ease;
}
.vx-codebar__submit:hover:not(:disabled) { transform: translateY(-1px); }
.vx-codebar__submit:disabled { opacity: 0.5; cursor: default; }
.vx-codebar__submit .vx-access-submit__loading { display: inline-flex; align-items: center; gap: 0.375rem; }
.vx-codebar.is-loading input { opacity: 0.6; }
.vx-codebar.is-error input { border-color: var(--destructive); box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.14); }

.vx-gate__error { display: flex; align-items: center; gap: 0.4375rem; margin-top: 0.625rem; color: var(--destructive); font-size: 0.8125rem; }
.vx-gate__error p { margin: 0; }
.vx-gate__error[hidden] { display: none; }
.vx-gate__hint { margin: 0.625rem 0.25rem 0; font-size: 0.75rem; line-height: 1.45; color: var(--muted-foreground); overflow-wrap: anywhere; }

/* Verified — the record. Hidden until the form wrap is gone. */
.vx-gate__record { margin-top: 0.875rem; }
.vx-gate:not(.is-verified) .vx-gate__record { display: none; }

/* FIRST-PAINT STATE.
   `data-vx-access` is set on <html> by the inline script in functions.php,
   before the browser paints anything, from the same sessionStorage key that
   app.js reads later. Without it an already-verified user watched the "Enter
   your access code" box render and then get replaced a beat later, because
   app.js does not run until DOMContentLoaded.

   These rules only cover the gap between first paint and app.js booting.
   The moment app.js runs it sets `.is-verified` and toggles the `hidden`
   attributes as before, and those take over — so if storage is blocked or
   the key is stale, nothing here can leave the card stuck in the wrong
   state. */
[data-vx-access="verified"] .vx-gate:not(.is-verified) #vx-access-form-wrap { display: none; }
[data-vx-access="verified"] .vx-gate:not(.is-verified) .vx-gate__record { display: block; }
[data-vx-access="verified"] .vx-gate:not(.is-verified) .vx-gate__badge { background: rgba(5, 150, 105, 0.10); color: #059669; }
[data-vx-access="verified"] .vx-gate:not(.is-verified) .vx-gate__icon--lock { opacity: 0; transform: scale(0.6); }
[data-vx-access="verified"] .vx-gate:not(.is-verified) .vx-gate__icon--check { opacity: 1; transform: scale(1); }
.vx-gate__actions { display: flex; gap: 0.5rem; margin-top: 0.75rem; }
.vx-gate__ghost {
	flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 0.375rem;
	border: 0; cursor: pointer; color: inherit;
	border-radius: var(--r-full); background: var(--secondary);
	padding: 0.625rem 1rem; font-size: 0.8125rem; font-weight: 600;
	transition: background-color 0.2s ease;
}
.vx-gate__ghost:hover { background: var(--accent); }

/* ── Usage meter ────────────────────────────────────────────────────────
   Replaces the six big stat tiles. Those showed six numbers in six boxes
   without saying what any of them meant; a used/total bar answers the only
   question the number is really being asked ("how much have I got left?").
   The fill animates its WIDTH once on load rather than transform, because it
   runs a single time on a 8px-tall bar — cheap enough that a compositor
   layer would cost more than it saves. */
.vx-usage { margin-top: 1rem; }
.vx-usage__top { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.vx-usage__used { min-width: 0; font-family: var(--font-display); font-size: 1.75rem; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.vx-usage__of { flex-shrink: 0; font-size: 0.8125rem; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.vx-usage__track { margin-top: 0.75rem; height: 0.5rem; border-radius: var(--r-full); background: var(--secondary); overflow: hidden; }
.vx-usage__fill { display: block; height: 100%; border-radius: var(--r-full); background-image: var(--gradient-brand); transform-origin: left; animation: vx-usage-grow 0.9s cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes vx-usage-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.vx-usage__note { margin: 0.625rem 0 0; font-size: 0.75rem; color: var(--muted-foreground); }
.vx-usage__cta { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-top: 1rem; }

/* ── iOS inset grouped list ──────────────────────────────────────────────
   Read-only account data was being rendered as `readonly` <input> pills:
   32px-radius boxes with their own shadow, three of them stacked on a phone.
   That is a text field's appearance on something you cannot type into, and
   it is why the Profile page read as unfinished next to the box above it.

   iOS shows read-only rows as a grouped list instead: a single rounded
   container, label left, value right in a lighter weight, and hairlines that
   start at the label's inset rather than running edge to edge. That inset
   separator is the detail that makes a list feel native — it is drawn on the
   row rather than the container so the first row never gets one.

   New class names, so no existing screen is affected. Everything is built
   from --surface / --border / --muted-foreground, all of which dark.css
   already remaps, so dark mode needs no extra rules. */
.vx-ios-list {
	margin-top: 1rem;
	border-radius: var(--r-md);
	background: var(--surface);
	border: 1px solid var(--border);
	overflow: hidden;
}
.vx-ios-row {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	min-height: 2.875rem;
	padding: 0.6875rem 0.9375rem;
	color: inherit;
}
.vx-ios-row + .vx-ios-row::before {
	content: "";
	position: absolute;
	left: 0.9375rem;
	right: 0;
	top: 0;
	height: 1px;
	background: color-mix(in oklab, var(--border) 70%, transparent);
}
.vx-ios-row__label { min-width: 0; flex: 1; font-size: 0.875rem; font-weight: 500; overflow-wrap: anywhere; }
.vx-ios-row__label small {
	display: block;
	margin-top: 0.125rem;
	font-size: 0.75rem;
	font-weight: 400;
	line-height: 1.35;
	color: var(--muted-foreground);
}
.vx-ios-row__value {
	flex-shrink: 0;
	font-size: 0.875rem;
	font-weight: 500;
	color: var(--muted-foreground);
	text-align: right;
}
.vx-ios-row__value--strong { color: var(--foreground); font-weight: 600; }
.vx-ios-row__value--mono { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
.vx-ios-row__value--ok { color: #10b981; font-weight: 600; }

/* Tappable rows get the chevron and a pressed state, like a real list. */
.vx-ios-row--link { cursor: pointer; transition: background-color 0.2s ease; }
.vx-ios-row--link:hover { background: color-mix(in oklab, var(--border) 30%, transparent); }
.vx-ios-row--link:active { background: color-mix(in oklab, var(--border) 45%, transparent); }
.vx-ios-row__chevron { flex-shrink: 0; margin-left: -0.375rem; color: var(--muted-foreground); opacity: 0.7; }

.vx-settings-profile { display: flex; align-items: center; gap: 1rem; }
.vx-settings-profile__avatar { display: grid; place-items: center; width: 4rem; height: 4rem; border-radius: var(--r-2xl); background: linear-gradient(135deg, #c4b5fd, #e879f9); font-size: 1.25rem; font-weight: 700; color: #fff; box-shadow: var(--shadow-soft); flex-shrink: 0; }
.vx-settings-profile__body { min-width: 0; }
.vx-settings-profile__body p:first-child { margin: 0; font-family: var(--font-display); font-size: 1.125rem; font-weight: 600; }
.vx-settings-profile__body p:last-child { margin: 0.125rem 0 0; font-size: 0.8125rem; color: var(--muted-foreground); }
.vx-settings-form { margin-top: 1.25rem; display: grid; gap: 0.875rem; }
@media (min-width: 640px) { .vx-settings-form { grid-template-columns: repeat(2, 1fr); } }
.vx-settings-field input { width: 100%; border-radius: var(--r-2xl); border: 1px solid var(--border); background: #fff; padding: 0.625rem 0.875rem; font-size: 0.875rem; box-shadow: var(--shadow-soft); outline: none; transition: border-color 0.2s ease; font-family: inherit; }
.vx-settings-field input:focus { border-color: color-mix(in oklab, var(--ring) 50%, transparent); }

.vx-plan-card { display: flex; align-items: center; justify-content: space-between; gap: 1rem; border-radius: var(--r-2xl); background-image: var(--gradient-brand); padding: 1rem 1.25rem; color: #fff; box-shadow: var(--shadow-glow); }
.vx-plan-card p:first-child { margin: 0; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.85; }
.vx-plan-card p:last-child { margin: 0.125rem 0 0; font-family: var(--font-display); font-size: 1.125rem; font-weight: 700; }
.vx-plan-card a { flex-shrink: 0; border-radius: var(--r-full); background: #fff; padding: 0.5rem 1rem; font-size: 0.75rem; font-weight: 600; color: var(--primary); box-shadow: var(--shadow-soft); transition: transform 0.2s ease; }
.vx-plan-card a:hover { transform: scale(1.03); }

.vx-danger-zone { border-radius: var(--r-2xl); border: 1px solid rgba(244,63,94,0.25); background: rgba(244,63,94,0.06); padding: 1rem; }
.vx-danger-zone p:first-child { margin: 0; font-size: 0.875rem; font-weight: 600; color: #be123c; }
.vx-danger-zone p:last-child { margin: 0.25rem 0 0; font-size: 0.75rem; color: color-mix(in oklab, #be123c 80%, var(--muted-foreground)); }
.vx-danger-zone button { margin-top: 0.75rem; border-radius: var(--r-full); border: none; background: #f43f5e; padding: 0.5rem 1rem; font-size: 0.75rem; font-weight: 600; color: #fff; box-shadow: var(--shadow-soft); cursor: pointer; transition: transform 0.2s ease; }
.vx-danger-zone button:hover { transform: scale(1.03); }

/* ── Settings Lock Button ── */
.vx-settings-lock-btn {
  width: 2.25rem; height: 2.25rem;
  border-radius: var(--r-xl);
  border: 1.5px solid var(--border);
  background: var(--surface, rgba(255,255,255,0.6));
  color: var(--muted-foreground);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background-color .2s, border-color .2s, color .2s, box-shadow .2s;
  backdrop-filter: blur(8px);
}
.vx-settings-lock-btn:hover { border-color: var(--brand-purple,#6366f1); color: var(--brand-purple,#6366f1); }
.vx-settings-lock-btn.is-locked {
  background: var(--brand-purple,#6366f1);
  border-color: var(--brand-purple,#6366f1);
  color: #fff;
  box-shadow: 0 4px 12px rgba(99,102,241,.35);
}

/* ── Locked slider visual ── */
.vx-slider-row.is-locked { opacity: 0.55; pointer-events: none; }

/* FIRST-PAINT LOCK STATE.
   `data-vx-lock` is written to <html> by the inline script in functions.php,
   before the browser paints anything, from the same localStorage key app.js
   uses. Without it the sliders rendered at full opacity and the padlock
   rendered open, then both changed once app.js booted — the flash reported on
   every refresh.

   These only cover the gap before app.js runs; the moment it does, the
   `.is-locked` classes above take over, so a stale key can never leave the
   controls stuck dimmed. */
[data-vx-lock="on"] .vx-slider-row { opacity: 0.55; pointer-events: none; }
[data-vx-lock="on"] .vx-settings-lock-btn {
	background: var(--brand-purple, #6366f1);
	border-color: var(--brand-purple, #6366f1);
	color: #fff;
	box-shadow: 0 4px 12px rgba(99, 102, 241, .35);
}


/* ── Profile page: modifiers that must outrank their base rules ──────────
   `.vx-card--flush` and `.vx-ios-list--flush` were declared ABOVE
   `.vx-ios-list`. Same specificity, so the later base rule won and the
   Shortcuts card kept drawing its inner box. Order is the whole fix — they
   live at the end of the file now. */
.vx-card--flush,
.vx-profile-stack .vx-card--flush { padding: 0; overflow: hidden; }
.vx-ios-list--flush { margin-top: 0; border: 0; border-radius: 0; background: transparent; }

/* `.vx-btn` supplies no padding at all — every other button in the theme sets
   its own. The Upgrade CTA inherited nothing and rendered as a thin strip
   instead of the pill in the preview. */
.vx-usage__cta {
	padding: 0.8125rem 1.25rem;
	border-radius: var(--r-full);
	font-size: 0.875rem;
	font-weight: 600;
}

/* The preview's section labels are 700 / .1em and sit 8px above their card
   with a small left inset. The shared app eyebrow is 600 / .14em with
   `margin: 0`, which left the label welded to the top of the box. Scoped so
   Dashboard/Text/Voices/Audio/Favorites keep their own. */
.vx-profile-stack .vx-app-eyebrow {
	font-weight: 700;
	letter-spacing: 0.1em;
	margin: 0 0 0.5rem 0.25rem;
}

/* Card outline. `.vx-glass-panel` draws its hairline in `--glass-border`,
   which is white — invisible against a white card. The preview outlines its
   cards in `--border` (a grey hairline), and that faint edge is a good part
   of why the preview reads as crisper. Scoped to the profile stack so no
   other screen's cards gain an outline they never had. */
.vx-profile-stack .vx-card { border-color: var(--border); }


/* ══════════════════════════════════════════════════════════════════════
   SLIDE-OUT MENU
   ══════════════════════════════════════════════════════════════════════
   Opening and closing is one `transform: translate3d()` on one element.
   Animating `right`, `width` or `margin` instead would make the browser
   re-run layout on every frame of a 320ms transition, on top of whatever
   the page is already doing — which is exactly the kind of thing that
   makes a drawer feel like it is dragging.

   `visibility` is transitioned alongside the transform (not faded) so the
   panel stops being hit-testable and stops painting the moment it is
   closed, while still animating out rather than vanishing.
   ══════════════════════════════════════════════════════════════════════ */

.vx-menu-scrim {
	position: fixed;
	inset: 0;
	z-index: 80;
	background: rgba(15, 23, 42, 0.34);
	opacity: 0;
	visibility: hidden;
	transition: opacity 0.28s ease, visibility 0.28s;
}
.vx-menu-scrim[hidden] { display: block; }   /* animate rather than vanish */
[data-vx-menu="open"] .vx-menu-scrim { opacity: 1; visibility: visible; }

.vx-menu {
	position: fixed;
	top: 0; right: 0; bottom: 0;
	z-index: 90;
	width: min(86vw, 380px);
	display: flex;
	flex-direction: column;
	background: var(--card);
	border-left: 1px solid var(--border);
	box-shadow: -8px 0 40px -12px rgba(15, 23, 42, 0.22);
	/* Nothing inside the panel can invalidate the page behind it. */
	contain: layout style paint;
	transform: translate3d(100%, 0, 0);
	visibility: hidden;
	/* `visibility: hidden` skips painting but NOT layout, so every app page was
	   laying out ~150 nodes for a panel that is usually shut — and on a short
	   page like Packages that fixed cost is a large share of the whole render.
	   `content-visibility: hidden` skips the subtree's layout as well.

	   It is re-enabled on the `opening` state, which app.js sets one frame
	   BEFORE `open`. That ordering matters: the panel gets laid out on the
	   first frame and the transform runs on the second, so the slide still
	   animates instead of snapping. */
	content-visibility: hidden;
	transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1), visibility 0.32s;
}
[data-vx-menu="opening"] .vx-menu,
[data-vx-menu="open"] .vx-menu,
[data-vx-menu="closing"] .vx-menu { content-visibility: visible; }
.vx-menu[hidden] { display: flex; }          /* same reason as the scrim */
[data-vx-menu="open"] .vx-menu { transform: translate3d(0, 0, 0); visibility: visible; }

/* Promote only while it is actually moving. A permanent hint would hold a
   full-height texture for the whole life of every app page. */
[data-vx-menu="opening"] .vx-menu,
[data-vx-menu="closing"] .vx-menu { will-change: transform; }

.vx-menu__head {
	display: flex; align-items: center; gap: 0.75rem;
	padding: 1rem 1rem 0.75rem;
	border-bottom: 1px solid var(--border);
}
.vx-menu__avatar {
	width: 2.75rem; height: 2.75rem; flex-shrink: 0;
	border-radius: var(--r-lg);
	background-image: linear-gradient(135deg, #c4b5fd, #e879f9);
	display: grid; place-items: center;
	color: #fff; font-size: 0.875rem; font-weight: 700;
}
.vx-menu__who { flex: 1; min-width: 0; }
.vx-menu__name { margin: 0; font-family: var(--font-display); font-size: 0.9375rem; font-weight: 600; }
.vx-menu__plan { margin: 0.125rem 0 0; display: flex; align-items: center; gap: 0.3125rem; font-size: 0.75rem; color: var(--muted-foreground); }
.vx-menu__dot { width: 0.4375rem; height: 0.4375rem; flex-shrink: 0; border-radius: var(--r-full); background: var(--muted-foreground); }
.vx-menu__dot.is-live { background: #059669; }
.vx-menu__close {
	width: 2rem; height: 2rem; flex-shrink: 0;
	border: 0; cursor: pointer;
	border-radius: var(--r-full); background: var(--secondary); color: var(--muted-foreground);
	display: grid; place-items: center;
}

/* The one scrolling region. `overscroll-behavior: contain` stops a flick that
   reaches the end of the menu from continuing into the page underneath. */
.vx-menu__body {
	flex: 1; min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
	padding: 0.875rem 1rem 1.5rem;
}
.vx-menu__body .vx-app-eyebrow { margin: 1.125rem 0.25rem 0.5rem; font-weight: 700; letter-spacing: 0.1em; }
.vx-menu__body > .vx-app-eyebrow:first-child { margin-top: 0.25rem; }

.vx-menu__list { border-radius: var(--r-md); background: var(--surface); border: 1px solid var(--border); overflow: hidden; }
.vx-menu__row {
	position: relative;
	display: flex; align-items: center; gap: 0.75rem;
	min-height: 2.875rem; padding: 0.625rem 0.875rem;
	color: inherit;
	transition: background-color 0.18s ease;
}
.vx-menu__row + .vx-menu__row::before {
	content: ""; position: absolute; left: 0.875rem; right: 0; top: 0; height: 1px;
	background: color-mix(in oklab, var(--border) 70%, transparent);
}
.vx-menu__row:active { background: var(--secondary); }
.vx-menu__row.is-active { background: var(--accent); }
.vx-menu__ic {
	width: 1.75rem; height: 1.75rem; flex-shrink: 0;
	border-radius: var(--r-sm); background: var(--secondary); color: var(--muted-foreground);
	display: grid; place-items: center;
}
.vx-menu__row.is-active .vx-menu__ic { background-image: var(--gradient-brand); color: #fff; }
.vx-menu__t { flex: 1; min-width: 0; font-size: 0.875rem; font-weight: 500; overflow-wrap: anywhere; }
.vx-menu__t small { display: block; margin-top: 0.0625rem; font-size: 0.6875rem; font-weight: 400; color: var(--muted-foreground); }
.vx-menu__n { flex-shrink: 0; font-size: 0.75rem; font-weight: 600; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.vx-menu__chev { flex-shrink: 0; color: var(--muted-foreground); opacity: 0.6; }
.vx-menu__pill { flex-shrink: 0; border-radius: var(--r-full); background: rgba(5, 150, 105, 0.10); color: #059669; padding: 0.1875rem 0.5rem; font-size: 0.6875rem; font-weight: 700; }
.vx-menu__pill[hidden] { display: none; }

.vx-menu__total {
	/* Baseline alignment is typographically correct but visually wrong here:
	   against a 1.5rem number, a 0.75rem label sitting on the same baseline
	   reads as hanging off the bottom. Centring the two line boxes — both
	   pinned to `line-height: 1` below, so each box is exactly its own glyph
	   height — puts the label level with the number instead. */
	display: flex; align-items: center; gap: 0.5rem;
	margin-bottom: 0.5rem; padding: 0.75rem 0.875rem;
	border-radius: var(--r-md); background: var(--surface); border: 1px solid var(--border);
}
/* Both children need an explicit line-height. Left to inherit body's 1.5,
   each sits inside a half-leading box of a different size, and the small
   label ends up visually low next to the big number even though `baseline`
   alignment is doing exactly what it says. */
.vx-menu__total b { font-family: var(--font-display); font-size: 1.5rem; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.vx-menu__total span { font-size: 0.75rem; line-height: 1; color: var(--muted-foreground); }

.vx-menu__up { margin-top: 0.5rem; border-radius: var(--r-xl); padding: 1rem; color: #fff; background-image: var(--gradient-brand); box-shadow: var(--shadow-soft); }
.vx-menu__up h3 { margin: 0; display: flex; align-items: center; gap: 0.375rem; font-family: var(--font-display); font-size: 1rem; font-weight: 700; }
.vx-menu__up p { margin: 0.25rem 0 0; font-size: 0.75rem; line-height: 1.45; opacity: 0.92; }
.vx-menu__up ul { margin: 0.625rem 0 0; padding: 0; list-style: none; display: grid; gap: 0.3125rem; }
.vx-menu__up li { position: relative; padding-left: 1rem; font-size: 0.75rem; opacity: 0.95; }
.vx-menu__up li::before { content: "✓"; position: absolute; left: 0; font-weight: 700; }
.vx-menu__up-cta {
	display: flex; align-items: center; justify-content: center; gap: 0.375rem;
	margin-top: 0.75rem; padding: 0.625rem;
	border-radius: var(--r-full); background: #fff;
	font-size: 0.8125rem; font-weight: 700;
}
/* tokens.css carries `.vx a { color: inherit }` at (0,1,1) — type + class —
   which outranks a plain class. The link therefore inherited the card's white
   text and disappeared against its own white pill. Only dark mode looked
   right, because its rule happens to be (0,2,0) and wins. Matching that
   specificity here fixes light mode and keeps the two in step. */
.vx .vx-menu__up-cta { color: #4c1d95; }

.vx-menu__seg { display: flex; gap: 0.25rem; padding: 0.25rem; border-radius: var(--r-full); background: var(--secondary); }
.vx-menu__seg button {
	flex: 1; display: flex; align-items: center; justify-content: center; gap: 0.375rem;
	border: 0; cursor: pointer;
	border-radius: var(--r-full); padding: 0.5rem;
	font-size: 0.8125rem; font-weight: 600;
	/* Unselected is deliberately flat: no fill, no shadow, muted text. Both
	   halves previously read as raised buttons, so the control looked like two
	   choices rather than one choice with a selection. */
	background: transparent;
	color: var(--muted-foreground);
	box-shadow: none;
	transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
.vx-menu__seg button.is-active {
	background: var(--card);
	color: var(--foreground);
	box-shadow: var(--shadow-soft);
}

/* Scroll lock. The page keeps its position because JS pins it at the exact
   offset it was at; `overflow: hidden` alone lets the page jump to the top on
   several mobile browsers. */
body.vx-menu-locked { position: fixed; left: 0; right: 0; overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
	.vx-menu, .vx-menu-scrim { transition: none; }
}


/* ── Packages page ───────────────────────────────────────────────────────
   Everything else on this screen reuses `.vx-card` and `.vx-ios-list`; only
   the price header and the two small ornaments below are new. */
.vx-pack__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 0.875rem; }
.vx-pack__id { min-width: 0; }
.vx-pack__name { margin: 0; font-family: var(--font-display); font-size: 1.0625rem; font-weight: 700; letter-spacing: -0.01em; }
.vx-pack__desc { margin: 0.1875rem 0 0; font-size: 0.75rem; line-height: 1.4; color: var(--muted-foreground); }
.vx-pack__price { flex-shrink: 0; display: flex; align-items: baseline; gap: 0.125rem; }
.vx-pack__amount { font-family: var(--font-display); font-size: 1.625rem; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.vx-pack__per { font-size: 0.75rem; line-height: 1; color: var(--muted-foreground); }
.vx-pack__tick { flex-shrink: 0; display: grid; place-items: center; width: 1.25rem; height: 1.25rem; border-radius: var(--r-full); background: rgba(5, 150, 105, 0.10); color: #059669; }
.vx-pack__step { flex-shrink: 0; display: grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: var(--r-full); background-image: var(--gradient-brand); color: #fff; font-size: 0.6875rem; font-weight: 700; }
.vx-pack__cta { width: 100%; margin-top: 0.875rem; padding: 0.8125rem 1.25rem; }
.vx-pack.is-featured { box-shadow: var(--shadow-elevated), inset 0 0 0 1.5px var(--brand-purple); }


/* Marker for a voice the studio has pinned to the top of every picker
   (MSP_Voices::set_featured_ids). Without it the reordering is invisible —
   the list just looks arbitrarily sorted. */
.vx-voice-pin {
	flex-shrink: 0;
	border-radius: var(--r-full);
	background: color-mix(in oklab, var(--brand-purple) 14%, transparent);
	color: var(--brand-purple);
	padding: 0.125rem 0.4375rem;
	font-size: 0.625rem;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
}


/* ── Trending toggle (Voice Library) ─────────────────────────────────────
   One tap, one attribute write on the grid, zero network calls. Ranks are
   computed server-side (MSP_Voices::apply_trending_flags(), from usage
   already tracked on every generation) and baked into each card as
   --trend-order when the page renders — there is nothing to fetch and
   nothing to sort in JS.

   `order` is scoped to `.vx-voice-grid[data-trend="on"]`, so it only takes
   effect while the toggle is active; the default library view (pinned
   voices, then the rest) is completely unaffected the rest of the time. */
.vx-trend-toggle {
	display: inline-flex; align-items: center; gap: 0.375rem;
	border: 0; cursor: pointer;
	border-radius: var(--r-full);
	background: rgba(217, 119, 6, 0.10);
	color: #d97706;
	padding: 0.5rem 0.75rem;
	font-size: 0.8125rem; font-weight: 700;
	transition: background-color 0.2s ease, color 0.2s ease;
}
.vx-trend-toggle.is-active {
	background-image: linear-gradient(135deg, #f59e0b, #ef4444);
	color: #fff;
}

.vx-voice-grid[data-trend="on"] .vx-voice-card:not([data-trending]) { display: none; }
.vx-voice-grid[data-trend="on"] .vx-voice-card { order: var(--trend-order, 9999); }
.vx-voice-grid[data-trend="on"] .vx-voice-card__rank { display: grid; }

.vx-voice-card__rank {
	display: none;   /* shown only while Trending is active, via the rule above */
	position: absolute;
	/* Corner badge, hanging slightly off the card's top-left — the original
	 * look. Paint containment that would clip it (content-visibility) has
	 * been removed from the cards, so it renders whole again. */
	top: -0.5rem; left: -0.5rem;
	z-index: 1;
	place-items: center;
	min-width: 1.5rem; height: 1.5rem; padding: 0 0.375rem;
	border-radius: var(--r-full);
	background-image: linear-gradient(135deg, #f59e0b, #ef4444);
	color: #fff;
	font-size: 0.6875rem; font-weight: 800;
	box-shadow: var(--shadow-soft);
}

/* Empty state for "Trending, filtered to a language with no trending voices
   in it yet" — rare, but otherwise the grid would just look broken. */
.vx-voice-grid[data-trend="on"].is-trend-empty::after {
	content: "No trending voices in this view yet.";
	grid-column: 1 / -1;
	text-align: center;
	padding: 2rem 1rem;
	font-size: 0.8125rem;
	color: var(--muted-foreground);
}


/* ══════════════════════════════════════════════════════════════════════
   TRANSLATE (Text page) — new
   ══════════════════════════════════════════════════════════════════════
   The language dropdown mechanics above are the pre-existing .vx-lang-select
   component; only the row layout, the Translate button, and the two
   feedback banners below are new.
   ══════════════════════════════════════════════════════════════════════ */
.vx-toolrow-divider {
	margin: 0.875rem 0 0;
	border: 0;
	border-top: 1px solid var(--border);
}
.vx-toolrow {
	display: flex;
	align-items: stretch;
	gap: 0.5rem;
	margin-top: 0.75rem;
}
/* The dropdown wrapper takes the remaining space; the Translate button
   keeps its natural width — same split as the Pause/Voice rows elsewhere. */
.vx-toolrow .vx-lang-select { flex: 1; min-width: 0; }
.vx-toolrow .vx-lang-select__trigger { height: 100%; }
.vx-lang-select__trigger-value span {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.vx-translate-go {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	gap: 0.4375rem;
	border: none;
	cursor: pointer;
	border-radius: var(--r-2xl);
	background-image: var(--gradient-brand);
	color: #fff;
	padding: 0 1rem;
	font-size: 0.875rem;
	font-weight: 700;
	box-shadow: var(--shadow-soft);
	transition: transform 0.2s ease, opacity 0.2s ease;
}
.vx-translate-go:active:not(:disabled) { transform: scale(0.96); }
.vx-translate-go:disabled { opacity: 0.45; cursor: default; }
.vx-translate-go__spin { display: none; }
.vx-translate-go.is-loading .vx-translate-go__spin { display: inline-flex; }
.vx-translate-go.is-loading .vx-translate-go__arrow { display: none; }

.vx-translate-error {
	display: flex;
	align-items: center;
	gap: 0.4375rem;
	margin-top: 0.625rem;
	padding: 0.5rem 0.75rem;
	border-radius: var(--r-xl);
	background: rgba(220, 38, 38, 0.08);
	color: var(--destructive, #dc2626);
	font-size: 0.8125rem;
}
.vx-translate-error[hidden] { display: none; }
.vx-translate-error__text { flex: 1; min-width: 0; }

.vx-translate-result {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-top: 0.625rem;
	padding: 0.5rem 0.75rem;
	border-radius: var(--r-xl);
	background: rgba(5, 150, 105, 0.08);
	color: #059669;
	font-size: 0.75rem;
	font-weight: 600;
}
.vx-translate-result[hidden] { display: none; }
.vx-translate-result__text { flex: 1; min-width: 0; }
#vx-translate-undo {
	flex-shrink: 0;
	border: 0;
	background: none;
	cursor: pointer;
	color: #059669;
	font-weight: 700;
	text-decoration: underline;
	text-underline-offset: 2px;
	font-size: 0.75rem;
}

/* ── Voice Clone Studio: emoji replaced with SVG ──────────────────────────
   These four containers were sized for emoji, which are TEXT — their
   `font-size` rules did the sizing and their flex centring worked because
   a glyph sits on the baseline like any other character. An <svg> ignores
   font-size entirely and, with no `svg { display: block }` reset anywhere
   in this theme, sits on that same baseline with descender space under it —
   so it renders both wrong-sized and slightly high.

   `line-height: 0` collapses the leading; `display: block` on the SVG takes
   it off the baseline. Sizes come from the vx_icon() call itself now. */
.vx-vc-header__icon,
.vx-vc-upload__icon,
.vx-vc-reroll,
.vx-vc-active__avatar,
.vx-vc-btn__ic {
	line-height: 0;
}
.vx-vc-header__icon svg,
.vx-vc-upload__icon svg,
.vx-vc-reroll svg,
.vx-vc-active__avatar svg,
.vx-vc-btn__ic svg {
	display: block;
}
.vx-vc-header__icon { display: grid; place-items: center; color: var(--brand-purple, #6366f1); }
.vx-vc-upload__icon { color: var(--brand-purple, #6366f1); opacity: 0.75; display: grid; place-items: center; }
.vx-vc-active__avatar { color: #fff; }
.vx-vc-btn__ic { display: grid; place-items: center; color: currentColor; }

/* Play control on the upload preview — hear the sample you picked before
   spending a clone on it. `line-height: 0` on the icon wrappers only, never
   `display`: the show/hide of the play vs pause glyph runs on the `hidden`
   attribute, and setting a display here would override it. (That is exactly
   the mistake that put two icons in every circle on the homepage.) */
.vx-vc-preview__play {
	width: 2.125rem; height: 2.125rem;
	flex-shrink: 0;
	border: none; cursor: pointer;
	border-radius: var(--r-full);
	display: grid; place-items: center;
	background: var(--gradient-brand, linear-gradient(135deg, #6366f1, #a855f7));
	color: #fff;
	box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3);
	transition: transform 0.2s ease;
}
.vx-vc-preview__play:active { transform: scale(0.92); }
.vx-vc-preview__play > * { line-height: 0; }
.vx-vc-preview__play svg { display: block; }

.vx-vc-preview__remove {
	flex-shrink: 0;
	display: grid; place-items: center;
	width: 1.75rem; height: 1.75rem;
	border: none; cursor: pointer;
	border-radius: var(--r-full);
	background: transparent;
	color: var(--muted-foreground);
	line-height: 0;
	transition: background-color 0.2s ease, color 0.2s ease;
}
.vx-vc-preview__remove:hover { background: rgba(99, 102, 241, 0.12); color: var(--foreground); }
.vx-vc-preview__remove svg { display: block; }

/* ── Generate button: number-only busy label ──────────────────────────────
   While generating, the label is just the percentage — no icon, no word.
   `tabular-nums` keeps every digit the same width, so the button does not
   twitch as the count passes 9, 19, 99. */
.vx-generate-lg__busy {
	font-variant-numeric: tabular-nums;
	font-size: 1rem;
	font-weight: 700;
}

/* The fill moves on `transform`, not `width`. `width` is a layout property,
   so transitioning it forced the browser to re-measure on every one of the
   ~100 updates in a single run; scaleX is compositor-only. The element is
   laid out at full width once and scaled from the left. */
.vx-generate-lg__fill {
	width: 100%;
	transform: scaleX(0);
	transform-origin: left center;
	transition: transform 0.18s ease-out;
}
/* Promote ONLY while the button is actually counting. A permanent
   `will-change` here pinned a compositor layer for this fill on every load
   of the page, for an element that moves for a few seconds at a time. */
.vx-generate-lg[data-busy="true"] .vx-generate-lg__fill { will-change: transform; }

/* ── Converting line (player) ─────────────────────────────────────────────
   A 2px indeterminate line under the status badge. Convertio reports no
   progress at all, so this shows motion rather than a fabricated figure.
   Not rendered until converting starts, so an idle player costs nothing —
   and the animation is transform-only, on a single tiny element. */
.vx-player__convert {
	display: none;
	position: relative;
	overflow: hidden;
	height: 2px;
	margin-top: 0.75rem;
	border-radius: var(--r-full);
	background: var(--secondary);
}
.vx-player[data-phase="converting"] .vx-player__convert { display: block; }
.vx-player__convert::after {
	content: "";
	position: absolute;
	inset-block: 0;
	left: 0;
	width: 100%;
	border-radius: var(--r-full);
	background-image: var(--gradient-brand);
	transform-origin: left center;
	animation: vx-convert-slide 1.25s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes vx-convert-slide {
	0%   { transform: translateX(-40%) scaleX(0.35); }
	50%  { transform: translateX(55%)  scaleX(0.5); }
	100% { transform: translateX(160%) scaleX(0.35); }
}

/* ── Access-code gate message ── */
.vx-generate-gate {
	display: flex;
	align-items: center;
	gap: 0.4375rem;
	margin-top: 0.625rem;
	padding: 0.5625rem 0.75rem;
	border-radius: var(--r-xl);
	background: rgba(220, 38, 38, 0.10);
	color: #dc2626;
	font-size: 0.8125rem;
	font-weight: 500;
	line-height: 1.4;
}
.vx-generate-gate[hidden] { display: none; }
.vx-generate-gate svg { flex-shrink: 0; }
.vx-generate-gate__text { min-width: 0; }
.vx-generate-gate a { color: inherit; font-weight: 700; text-decoration: underline; text-underline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
	.vx-generate-lg__fill { transition: none; }
	.vx-player__convert::after { animation: none; transform: scaleX(0.5); }
}
