/*
 * VocabCue learner application — Build 08.2.
 *
 * Loaded only on learner routes (see functions.php), after site.css and runtime-card.css, so the
 * marketing pages cannot be affected by anything here. Tokens are site.css's; nothing new is
 * introduced except layout measures. The solid-orange CTA keeps its locked treatment: #FF4D00,
 * a #FFFFFF label, no text shadow.
 *
 * Layout model, chosen from the content rather than a framework's breakpoints:
 *   >= 1180px  full sidebar (232px): logo, labelled links, plans card, log out
 *   720-1179px compact rail (88px): Site Icon, icon-over-label links, same destinations
 *   <  720px   fixed bottom tab bar, safe-area aware; the rail is not rendered
 * Below 720px an 88px rail would leave under 632px for a card and its decisions, and a phone in
 * portrait is always under it; a tablet in either orientation is always over it.
 */

/* --------------------------------------------------------------------------------- Frame */
.vv-app-page,.vv-auth-page{background:var(--vv-bg)}
/* The rail is fixed to the left edge so its surface always runs the full height, however long the
   page; the main column is offset by the rail's width instead of sharing a grid row with it. */
.vv-app{--vv-rail-w:232px;--vv-tabbar-h:64px;min-height:100vh;min-height:100dvh;padding-left:var(--vv-rail-w)}
.vv-app-main{min-width:0;padding:32px clamp(24px,3.4vw,48px) 48px}
.vv-app-main:focus{outline:none}

/* ------------------------------------------------------------------------------- Sidebar */
.vv-app-rail{position:fixed;top:0;bottom:0;left:0;z-index:40;width:var(--vv-rail-w);display:flex;flex-direction:column;gap:28px;
  padding:24px 16px 20px;border-right:1px solid var(--vv-border);background:var(--vv-surface);overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}
.vv-app-brand{display:flex;align-items:center;min-height:48px;padding:0 10px;text-decoration:none;color:var(--vv-ink);border-radius:var(--vv-radius-control)}
.vv-app-brand__full{display:inline-flex;align-items:center;gap:10px;font-family:var(--vv-font-heading);font-weight:700;font-size:18px;letter-spacing:-.045em}
.vv-app-brand__full .vv-brand-logo{display:block;height:40px;width:auto;max-width:176px}
.vv-app-brand__compact{display:none}
.vv-brand-icon{display:block;width:36px;height:36px;border-radius:10px}
.vv-brand-mark--rail{display:block;height:24px;width:9px;border-radius:4px;background:var(--vv-accent);transform:rotate(14deg)}

.vv-app-nav{display:grid;gap:4px}
.vv-icon{display:block;width:22px;height:22px;flex:none}
.vv-app-nav__link{position:relative;display:flex;align-items:center;gap:12px;min-height:46px;padding:10px 12px;
  border-radius:var(--vv-radius-control);color:var(--vv-secondary);text-decoration:none;font:500 15px/1.2 var(--vv-font-body);
  transition:background-color .15s ease,color .15s ease}
.vv-app-nav__link:hover{background:var(--vv-subtle);color:var(--vv-ink)}
/* Current location: tint, weight, accent bar and aria-current together — never colour alone. */
.vv-app-nav__link.is-current{background:var(--vv-accent-wash);color:var(--vv-ink);font-weight:700}
.vv-app-nav__link.is-current::before{content:"";position:absolute;left:-16px;top:9px;bottom:9px;width:4px;border-radius:0 4px 4px 0;background:var(--vv-accent)}
.vv-app-nav__link.is-current .vv-app-nav__link__icon{color:var(--vv-accent-text)}
.vv-app-nav__link:focus-visible,.vv-app-brand:focus-visible{outline:3px solid var(--vv-focus);outline-offset:2px}
.vv-app-rail__foot{margin-top:auto;display:grid;gap:12px}
.vv-app-nav__link--quiet{color:var(--vv-muted)}

/* ------------------------------------------------------------------------------- Tab bar */
.vv-app-tabbar{display:none}

/* ----------------------------------------------------------------------------- Page head */
.vv-page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:24px}
.vv-page-head__title{font-size:clamp(26px,2.4vw,34px);letter-spacing:-.035em;line-height:1.15}
.vv-page-head__title span{color:var(--vv-accent)}
.vv-page-head__lead{margin-top:6px;color:var(--vv-secondary);font-size:15px}

/* ------------------------------------------------------------------------------ Overview */
.vv-dash{display:grid;gap:20px;max-width:1040px;margin-inline:auto}
.vv-dash>.vv-page-head{margin-bottom:4px}
.vv-dash .vv-dash-today{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;justify-items:stretch;gap:24px 32px;
  padding:28px 32px;background:var(--vv-surface);border:1px solid var(--vv-border);border-radius:var(--vv-radius-panel);
  box-shadow:0 14px 38px rgba(22,21,15,.055);overflow:hidden}
.vv-dash .vv-dash-today::after{content:"";position:absolute;top:-60px;right:-40px;width:220px;height:220px;border-radius:50%;background:var(--vv-accent-wash);opacity:.8;pointer-events:none}
.vv-dash-today__body,.vv-dash-today__action{position:relative;z-index:1;min-width:0}
.vv-dash .vv-dash-today .vv-label{margin:0;color:var(--vv-accent-text)}
.vv-dash .vv-dash-today h2{margin-top:6px;font-size:clamp(22px,2.1vw,28px);letter-spacing:-.03em}
.vv-dash-today__lead{margin-top:8px;color:var(--vv-secondary);max-width:540px}
.vv-dash-today__action .vv-primary-cta{margin:0}
.vv-dash .vv-learn-today--done{background:var(--vv-success-wash);border-color:rgba(15,107,69,.18)}
.vv-dash .vv-learn-today--done::after,.vv-dash .vv-learn-today--blocked::after{display:none}
.vv-dash .vv-learn-today--done .vv-label{color:var(--vv-success)}
.vv-dash .vv-learn-today--blocked{background:var(--vv-warning-wash);border-color:rgba(138,90,0,.18)}
.vv-meter{height:8px;margin-top:20px;max-width:420px;border-radius:var(--vv-radius-pill);background:var(--vv-subtle);overflow:hidden}
.vv-meter__fill{display:block;height:100%;width:var(--vv-fill,0%);border-radius:inherit;background:var(--vv-accent)}
.vv-learn-today--done .vv-meter__fill{background:var(--vv-success)}
.vv-learn-today--done .vv-meter{background:rgba(15,107,69,.12)}
.vv-meter__caption{margin-top:8px;font-size:13.5px;color:var(--vv-secondary)}
.vv-meter__caption strong{color:var(--vv-ink);font-variant-numeric:tabular-nums}
.vv-dash-note{margin-top:-8px;font-size:13.5px;color:var(--vv-secondary)}


.vv-dash-panel{display:grid;gap:12px;justify-items:start;padding:24px 28px;background:var(--vv-surface);border:1px solid var(--vv-border);border-radius:var(--vv-radius-card)}
.vv-dash-panel h2{font-size:19px;letter-spacing:-.02em}
.vv-dash-panel p{color:var(--vv-secondary)}
.vv-learn--locked .vv-learn-section{margin-bottom:0}
.vv-learn--locked .vv-access-banner{margin-bottom:0}

/* ------------------------------------------------------------------------- Upgrade nudge */
.vv-upgrade{position:relative;display:block;min-height:172px;margin-top:36px;padding:16px;border:1px solid rgba(255,77,0,.2);
  border-radius:var(--vv-radius-card);background:var(--vv-accent-wash)}
.vv-upgrade__copy{position:relative;z-index:1;display:grid;gap:8px;justify-items:start;max-width:58%}
.vv-upgrade__title{font:700 15px/1.25 var(--vv-font-heading);letter-spacing:-.02em;color:var(--vv-ink)}
.vv-upgrade__body{font-size:12.5px;line-height:1.45;color:var(--vv-secondary)}
.vv-upgrade__cta{display:inline-flex;align-items:center;gap:6px;min-height:40px;margin-top:4px;padding:8px 14px;border-radius:var(--vv-radius-pill);
  background:var(--vv-accent);color:var(--vv-on-accent);font:700 13px/1 var(--vv-font-heading);text-decoration:none;white-space:nowrap;transition:background-color .15s ease}
.vv-upgrade__cta:hover{background:var(--vv-accent-hover)}
.vv-upgrade__arrow{width:16px;height:16px}
/* The character stands on the card's floor and rises above its top edge, as in the brief's
   reference. No box behind it: the PNG's own transparency is the whole effect. */
.vv-upgrade__art{position:absolute;right:2px;bottom:0;height:206px;width:auto;max-width:none;pointer-events:none;user-select:none}
.vv-upgrade--dashboard{display:none}
/* In the narrow sidebar the character stands on top of the card rather than beside the words. */
.vv-upgrade--sidebar{min-height:0;margin-top:112px;padding:62px 16px 16px}
.vv-upgrade--sidebar .vv-upgrade__copy{max-width:none}
.vv-upgrade--sidebar .vv-upgrade__art{top:-104px;bottom:auto;right:auto;left:50%;transform:translateX(-50%);height:156px}
/* No plans destination yet: a status line, deliberately not shaped like a button. */
.vv-upgrade__soon{display:inline-flex;align-items:center;margin-top:2px;padding:6px 12px;border:1px dashed rgba(255,77,0,.45);border-radius:var(--vv-radius-pill);
  font:700 12.5px/1.2 var(--vv-font-heading);color:var(--vv-accent-text)}
/* Administrator review render only (?vv_preview_upgrade=1). Never shown to a learner. */
.vv-upgrade__flag{position:relative;z-index:2;display:inline-block;margin:0 0 8px;padding:3px 8px;border-radius:var(--vv-radius-pill);background:var(--vv-ink);color:#FFFFFF;font:700 10.5px/1.3 var(--vv-font-body);letter-spacing:.04em;text-transform:uppercase}

/* ------------------------------------------------------------------------------- Account */
.vv-account .vv-page-head{margin-bottom:24px}
.vv-app-page .vv-account{max-width:1040px;margin-inline:auto}
.vv-account-logout{margin-top:24px;gap:10px}
.vv-account-logout .vv-icon{width:18px;height:18px}

/* ----------------------------------------------------------------------------- Discovery */
/*
 * Build 09C.1: the illustration is content, and content does not yield.
 *
 * What was here before was a one-screen rule. `.vv-discovery` was given a hard `height`, the media
 * box was the one flex item allowed to shrink, and everything fitted. The cost only becomes visible
 * when you measure it: the image is `object-fit:contain` inside a 16:9 box, so squeezing the box's
 * *height* letterboxes the picture and shrinks it on **both** axes. A media box pressed to its old
 * 120px floor inside a 520px card drew the illustration about 213px wide - roughly 17% of the area
 * it was drawn at. That is not a slightly smaller picture; it is a thumbnail, and the illustration
 * is how this product communicates meaning.
 *
 * So the media box no longer shrinks at all: natural 16:9, the full inner width of the card, at
 * every size. The card is as tall as its content, and if that is taller than the viewport the page
 * scrolls, which is what pages do. What replaces the fold as the thing keeping controls reachable
 * is stickiness - the decisions on a phone, the action rail on a desktop - so the learner never has
 * to hunt for the control they were just asked to use.
 */
.vv-route-learn-discover .vv-app-main{padding-top:20px;padding-bottom:20px}
.vv-app-page .vv-discovery{display:flex;flex-direction:column;gap:12px;min-height:calc(100dvh - 40px);max-width:none;margin-inline:auto}
/*
 * The one row above the card: chips, counter, and the place a decision is acknowledged.
 *
 * Three bands used to live here - the chip rail, a full-width meter with a counter beside it, and
 * a reserved row for the undo message - which together took about ninety pixels off the top of
 * every Discovery screen. They are one line now. The chips take the space that is left and scroll
 * within it; the counter and the undo message are flex:none at the end, outside the scroller, so
 * neither can scroll out of reach.
 */
.vv-app-page .vv-discovery__head{display:flex;align-items:center;gap:10px;width:100%;max-width:480px;
  min-height:44px;margin-inline:auto}
.vv-app-page .vv-discovery__head .vv-category{flex:1 1 auto;min-width:0;max-width:none;margin:0}
.vv-app-page .vv-discovery__progress{flex:none;font:700 14px/1.2 var(--vv-font-heading);color:var(--vv-secondary);font-variant-numeric:tabular-nums;white-space:nowrap}
/* The undo message takes the counter's slot while it is showing. Same row, same height, no jump -
   and it is the counter's own space, which is where a report about the last card belongs. */
.vv-app-page .vv-discovery__status:not([hidden]) ~ .vv-discovery__progress{display:none}
.vv-app-page .vv-discovery__stage{flex:1 1 auto;display:flex;flex-direction:column;justify-content:center;margin:0}
.vv-app-page .vv-discovery__card>.vv-runtime-card{width:100%;max-width:480px}
.vv-app-page .vv-discovery__actions{max-width:480px;gap:12px;margin:0 auto}
.vv-app-page .vv-discovery__summary{margin-block:auto}

/*
 * The decision acknowledgement, and the offer to take it back.
 *
 * This used to be a floating black toast pinned to the foot of the viewport, which on a phone sat
 * squarely over the lower third of the illustration for eight seconds after every card. It is now
 * a line in the session band: in the layout, above the card, clear of the decisions and clear of
 * the tab bar. The band reserves its height, so this appearing and expiring shifts nothing.
 */
/* A hairline before it, so the message and the chip rail read as two regions of one row rather
   than as one crowded line once the rail has been squeezed to make space. */
.vv-discovery__status{flex:none;display:flex;align-items:center;gap:8px;min-width:0;
  padding-left:10px;border-left:1px solid var(--vv-border);
  animation:vv-status-in var(--vv-motion-duration-standard) var(--vv-motion-ease-enter) both}
.vv-discovery__status-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  color:var(--vv-secondary);font-size:13.5px}
.vv-discovery__undo{flex:none;min-height:32px;padding:6px 14px;border:1px solid var(--vv-border-strong);
  border-radius:var(--vv-radius-pill);background:var(--vv-surface);color:var(--vv-ink);
  font:700 13px/1 var(--vv-font-heading)}
.vv-discovery__undo:hover{background:var(--vv-subtle);border-color:var(--vv-ink)}
.vv-discovery__undo:disabled{opacity:.6}
.vv-discovery__undo:focus-visible{outline:3px solid var(--vv-focus);outline-offset:2px}
@keyframes vv-status-in{from{opacity:0}to{opacity:1}}
@media(prefers-reduced-motion:reduce){.vv-discovery__status{animation:none}}

/* Discovery density: the same card, tighter vertical rhythm. Nothing is removed. */
.vv-discovery .vv-runtime-card__copy{padding:20px 22px 16px}
.vv-discovery .vv-runtime-card__target{font-size:clamp(24px,2.2vw,30px)}
.vv-discovery .vv-runtime-card__copy>header+*{margin-top:10px}
.vv-discovery .vv-runtime-card__copy>:is(.vv-runtime-card__meaning,.vv-runtime-card__example){margin-top:12px}
.vv-discovery .vv-runtime-card__pronunciation{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px}
.vv-discovery .vv-runtime-card__switch{margin-bottom:6px}
.vv-discovery .vv-runtime-card__example{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;column-gap:12px}
.vv-discovery .vv-runtime-card__example>.vv-label{margin:0}
.vv-discovery .vv-runtime-card__audio-row{margin-top:0}
.vv-discovery .vv-runtime-card__example-copy{grid-column:1/-1;margin-top:8px}

/* Wide workspace (desktop, laptop, landscape tablet): the decisions flank the card, left for
   "I know this" and right for "Learn this" — the same directions as the swipe. The actions wrapper
   steps out of the layout (display:contents) so each button takes its own column; it is still the
   same element, with the same buttons in the same order for keyboard and screen readers. */
@media (min-width:1000px){
  .vv-app-page .vv-discovery{display:grid;grid-template-columns:minmax(150px,1fr) minmax(0,480px) minmax(150px,1fr);
    grid-template-rows:auto auto;grid-template-areas:"head head head" "known stage learning" "fail fail fail";column-gap:32px;row-gap:14px;max-width:1120px}
  .vv-app-page .vv-discovery__stage{grid-area:stage}
  .vv-app-page .vv-discovery__failure{grid-area:fail;justify-self:center;max-width:480px;width:100%}
  .vv-app-page .vv-discovery__summary{grid-column:1/-1;grid-row:2;justify-self:center;align-self:center;width:100%;max-width:560px}
  .vv-app-page .vv-discovery__actions{display:contents}
  /* Wider than before, because the label is now 19px bold: WCAG large text is the one size at
     which the brand's white-on-#FF4D00 clears its contrast bar, and the button is sized to it. */
  .vv-app-page .vv-decision{position:sticky;top:24px;align-self:center;width:100%;max-width:240px;min-height:58px}
  .vv-app-page .vv-decision--known{grid-area:known;justify-self:end}
  .vv-app-page .vv-decision--learning{grid-area:learning;justify-self:start}
  /* A touch laptop can still get the swipe hint; with no wrapper box to sit above, it floats at
     the foot of the screen instead. */
  .vv-app-page .vv-swipe-hint{position:fixed;left:50%;bottom:24px;width:min(480px,calc(100vw - 48px));transform:translateX(-50%)}
}

/* ------------------------------------------------------------------ Guidance and coach marks */
.vv-coach{position:fixed;z-index:120;width:min(320px,calc(100vw - 32px));padding:18px 18px 14px;border-radius:16px;
  background:var(--vv-ink);color:#FFFFFF;box-shadow:0 18px 44px rgba(22,21,15,.28);font-size:14px;line-height:1.5}
.vv-coach__title{font:700 16px/1.3 var(--vv-font-heading);letter-spacing:-.02em;color:#FFFFFF}
.vv-coach__body{margin-top:6px;color:rgba(255,255,255,.86)}
.vv-coach__body strong{color:#FFFFFF}
.vv-coach__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:14px}
.vv-coach__count{font-size:12px;color:rgba(255,255,255,.7);font-variant-numeric:tabular-nums}
.vv-coach__actions{display:flex;align-items:center;gap:8px}
.vv-coach__skip{min-height:40px;padding:8px 10px;border:0;background:transparent;color:rgba(255,255,255,.82);font:600 13px/1 var(--vv-font-body);text-decoration:underline;text-underline-offset:3px}
.vv-coach__next{min-height:40px;padding:8px 16px;border:0;border-radius:var(--vv-radius-pill);background:#FFFFFF;color:var(--vv-ink);font:700 13px/1 var(--vv-font-heading)}
.vv-coach button:focus-visible{outline:3px solid #9CC3E6;outline-offset:2px}
.vv-coach--center{left:50%;top:50%;transform:translate(-50%,-50%);width:min(400px,calc(100vw - 32px));padding:28px 26px 20px;background:var(--vv-surface);color:var(--vv-ink);border:1px solid var(--vv-border)}
.vv-coach--center .vv-coach__title{font-size:22px;color:var(--vv-ink)}
.vv-coach--center .vv-coach__body{color:var(--vv-secondary)}
.vv-coach--center .vv-coach__count{color:var(--vv-muted)}
.vv-coach--center .vv-coach__skip{color:var(--vv-secondary)}
.vv-coach--center .vv-coach__next{background:var(--vv-accent);color:var(--vv-on-accent)}
.vv-coach--center button:focus-visible{outline-color:var(--vv-focus)}
.vv-coach__badge{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;margin-bottom:14px;border-radius:14px;background:var(--vv-accent-wash);color:var(--vv-accent-text)}
.vv-coach-scrim{position:fixed;inset:0;z-index:110;background:rgba(22,21,15,.32)}
.vv-coach-target{outline:3px solid var(--vv-accent)!important;outline-offset:4px!important;border-radius:var(--vv-radius-control)}
.vv-coach__arrow{position:absolute;width:12px;height:12px;background:inherit;transform:rotate(45deg)}

/* Swipe hint: floats just above the decision buttons over the foot of the card — the illustration,
   never the words — and does not reflow the card while it is open. */
.vv-app-page .vv-discovery__actions{position:relative}
.vv-swipe-hint{position:absolute;left:0;right:0;bottom:calc(100% + 10px);z-index:5;display:grid;grid-template-columns:1fr auto;align-items:end;gap:8px 12px;
  padding:12px 14px;border-radius:16px;background:var(--vv-ink);color:#FFFFFF;box-shadow:0 12px 30px rgba(22,21,15,.24)}
.vv-swipe-hint .vv-swipe-hint__row{grid-column:1/-1}
.vv-swipe-hint__row{display:grid;grid-template-columns:1fr 1fr;gap:12px;font:700 15px/1.25 var(--vv-font-heading)}
.vv-swipe-hint__row span{display:grid;gap:2px}
.vv-swipe-hint__row small{font:600 12px/1.3 var(--vv-font-body);color:rgba(255,255,255,.78)}
.vv-swipe-hint__row span:last-child{text-align:right}
.vv-swipe-hint__note{font-size:12.5px;color:rgba(255,255,255,.8)}
.vv-swipe-hint__close{min-height:40px;padding:8px 16px;border:0;border-radius:var(--vv-radius-pill);background:#FFFFFF;color:var(--vv-ink);font:700 13px/1 var(--vv-font-heading)}
.vv-swipe-hint__close:focus-visible{outline:3px solid #9CC3E6;outline-offset:2px}
.vv-discovery__slide.is-demo{transition:transform .45s ease}

/* ------------------------------------------------------------------------------ Auth frame */
.vv-auth-shell{min-height:100vh;min-height:100dvh;display:grid;grid-template-rows:auto 1fr auto;padding:24px 16px calc(24px + env(safe-area-inset-bottom))}
.vv-auth-shell__top{display:flex;justify-content:center;padding:8px 0 28px}
.vv-auth-shell__brand{display:inline-flex;align-items:center;gap:10px;min-height:44px;text-decoration:none;font-family:var(--vv-font-heading);font-weight:700;font-size:20px;letter-spacing:-.045em}
.vv-auth-shell__brand .vv-brand-logo{display:block;height:42px;width:auto;max-width:200px}
.vv-auth-shell__main{width:100%;max-width:460px;margin-inline:auto}
.vv-auth-shell__main:focus{outline:none}
.vv-auth-shell .vv-auth-layout{display:block;max-width:none;margin:0}
.vv-auth-shell .vv-auth-intro{max-width:none;margin-bottom:20px;text-align:center}
.vv-auth-shell .vv-auth-intro h1{font-size:30px;letter-spacing:-.035em}
.vv-auth-shell .vv-auth-intro h1 span{color:var(--vv-accent)}
.vv-auth-shell .vv-auth-intro p:not(.vv-label){margin-top:8px;color:var(--vv-secondary)}
.vv-auth-shell .vv-auth-intro .vv-label{margin-bottom:8px}
.vv-auth-shell .vv-auth-panel{padding:28px;border:1px solid var(--vv-border);border-radius:var(--vv-radius-panel);background:var(--vv-surface);box-shadow:0 18px 50px rgba(22,21,15,.07)}
.vv-auth-shell__foot{padding-top:28px;text-align:center;font-size:12.5px;color:var(--vv-muted)}
.vv-auth-shell__foot a{color:var(--vv-secondary)}

/* ============================================================================ Short screens */
@media (max-height:780px) and (min-width:1180px){
  /* A laptop: the card stays in the lower sidebar, with a smaller character and tighter copy. */
  .vv-upgrade--sidebar{margin-top:74px;padding:46px 14px 14px}
  .vv-upgrade--sidebar .vv-upgrade__art{top:-70px;height:108px}
  .vv-upgrade--sidebar .vv-upgrade__copy{gap:6px}
  .vv-upgrade--sidebar .vv-upgrade__body{font-size:12px}
}
@media (max-height:640px) and (min-width:1180px){
  .vv-upgrade--sidebar{display:none}
  .vv-upgrade--dashboard{display:block}
}

/* ============================================================================ Tablet rail */
@media (max-width:1179px){
  .vv-app{--vv-rail-w:88px}
  .vv-app-rail{gap:20px;padding:18px 10px}
  .vv-app-brand{justify-content:center;padding:0}
  .vv-app-brand__full{display:none}
  .vv-app-brand__compact{display:flex}
  .vv-app-nav{gap:6px}
  .vv-app-nav__link{flex-direction:column;justify-content:center;gap:4px;min-height:62px;padding:8px 4px;font-size:11.5px;font-weight:600;text-align:center}
  .vv-app-nav__link.is-current::before{left:-10px}
  .vv-app-main{padding:28px 28px 40px}
  .vv-upgrade--sidebar{display:none}
  .vv-upgrade--dashboard{display:block}
}

/* Shared dashboard-placement layout wherever it shows. */
.vv-upgrade--dashboard{min-height:150px;margin-top:28px;padding:22px 24px}
.vv-upgrade--dashboard .vv-upgrade__copy{max-width:min(420px,64%)}
.vv-upgrade--dashboard .vv-upgrade__title{font-size:18px}
.vv-upgrade--dashboard .vv-upgrade__body{font-size:14px}
.vv-upgrade--dashboard .vv-upgrade__art{right:16px;height:196px}

/* ============================================================================= Phone */
@media (max-width:719px){
  .vv-app{padding-left:0}
  .vv-app-rail{display:none}
  .vv-app-main{padding:16px 16px calc(var(--vv-tabbar-h) + env(safe-area-inset-bottom) + 24px)}
  .vv-app-tabbar{position:fixed;left:0;right:0;bottom:0;z-index:60;display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:4px;
    min-height:var(--vv-tabbar-h);padding:6px 10px calc(6px + env(safe-area-inset-bottom));
    background:rgba(255,255,255,.97);border-top:1px solid var(--vv-border);box-shadow:0 -8px 24px rgba(22,21,15,.06)}
  @supports (backdrop-filter:blur(12px)){.vv-app-tabbar{background:rgba(255,255,255,.9);backdrop-filter:saturate(1.1) blur(12px)}}
  .vv-app-tab{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;min-height:52px;border-radius:14px;
    color:var(--vv-muted);text-decoration:none;font:600 11.5px/1.2 var(--vv-font-body)}
  .vv-app-tab__icon{box-sizing:content-box;padding:3px 16px;border-radius:var(--vv-radius-pill);transition:background-color .15s ease}
  /* Current tab: filled pill, darker label and bolder weight — never colour alone. */
  .vv-app-tab.is-current{color:var(--vv-ink);font-weight:700}
  .vv-app-tab.is-current .vv-app-tab__icon{background:var(--vv-accent-wash);color:var(--vv-accent-text)}
  .vv-app-tab:focus-visible{outline:3px solid var(--vv-focus);outline-offset:-3px}

  .vv-page-head{margin-bottom:16px}
  .vv-page-head__title{font-size:24px}
  .vv-page-head__lead{font-size:14px}
  .vv-dash{gap:14px}
  .vv-dash .vv-dash-today{grid-template-columns:1fr;gap:18px;padding:22px 20px}
  .vv-dash .vv-dash-today::after{width:160px;height:160px;top:-70px;right:-60px}
  /* The approved Build 08.2.3 mobile CTA geometry. It follows the recommended action to its new
     wrapper: the panel around it was renamed, the sizing rule it carries was not up for review. */
  .vv-dash-today__action,.vv-action__cta{text-align:center}
  .vv-dash-today__action .vv-button.vv-primary-cta:not(.vv-button--outline):not(.vv-button--dark),
  .vv-action__cta .vv-button.vv-primary-cta:not(.vv-button--outline):not(.vv-button--dark){width:auto;max-width:100%;min-height:56px;padding:7px 8px 7px 18px;justify-content:center;gap:14px}
  .vv-dash-today__action .vv-primary-cta__chip,
  .vv-action__cta .vv-primary-cta__chip{flex-basis:42px;width:42px;height:42px}
  .vv-meter{max-width:none}
  .vv-upgrade--dashboard{min-height:156px;margin-top:40px;padding:18px}
  .vv-upgrade--dashboard .vv-upgrade__copy{max-width:60%}
  .vv-upgrade--dashboard .vv-upgrade__title{font-size:16px}
  .vv-upgrade--dashboard .vv-upgrade__body{font-size:13px}
  .vv-upgrade--dashboard .vv-upgrade__art{right:6px;height:190px}

  .vv-route-learn-discover .vv-app-main{padding-top:10px}
  .vv-app-page .vv-discovery{gap:10px;min-height:0}
  .vv-discovery__title{font-size:19px}
  .vv-discovery .vv-runtime-card__copy{padding:16px 16px 14px}
  .vv-discovery .vv-runtime-card__target{font-size:25px}
  .vv-discovery .vv-runtime-card__copy>:is(.vv-runtime-card__meaning,.vv-runtime-card__example){margin-top:12px}
  .vv-app-page .vv-discovery__stage{margin-top:0}
  /*
   * The decisions stay on the screen instead of the card being shrunk until they fit on it.
   * That is the whole trade this build makes: the card is as tall as its content, the page scrolls
   * if it has to, and the two controls ride just above the tab bar wherever the learner has read
   * to. The strip carries the page background so the card does not show through behind it.
   *
   * And it is as short as it can honestly be. 48px is the comfortable touch height; the label is
   * 19px bold because that is the size at which the brand's white-on-orange clears its contrast
   * bar, and it cannot go lower. Everything around it can: the strip's own padding is down to
   * 3px above and 2px below - the flex gap above it already separates it from the card, so the
   * padding was buying nothing but height. On a phone this row is competing with the meaning
   * illustration for the same pixels, and the illustration is the thing that teaches.
   *
   * Build 09C.2 goes further, but by collapsing rather than shrinking: see the adaptive controls
   * at the foot of this file. The label stays 19px in both states, because the accessible size of
   * white on #FF4D00 is not a variable that a layout problem gets to spend.
   */
  .vv-app-page .vv-discovery__actions{position:sticky;bottom:calc(var(--vv-tabbar-h) + env(safe-area-inset-bottom) + 6px);
    z-index:5;gap:8px;margin-bottom:0;padding:3px 0 2px;background:var(--vv-bg)}
  .vv-app-page .vv-decision{min-height:48px;padding:8px 10px;gap:7px}
  .vv-app-page .vv-decision .vv-decision__mark .vv-icon{width:18px;height:18px}
  /* Label and both example buttons on one row: slimmer buttons, same 44px tap height. */
  .vv-discovery .vv-runtime-card__example .vv-runtime-card__audio-row{flex-wrap:nowrap;gap:6px}
  .vv-discovery .vv-runtime-card__audio{padding:7px 9px;gap:4px}
  .vv-discovery .vv-runtime-card__audio svg{width:24px;height:20px}

  .vv-account .vv-page-head{margin-bottom:16px}
}
@media (max-width:374px){
  /* Too narrow for the label and both example buttons on one line: buttons get their own row. */
  .vv-discovery .vv-runtime-card__example{grid-template-columns:1fr;row-gap:6px}
  .vv-discovery .vv-runtime-card__target{font-size:23px}
  .vv-discovery .vv-runtime-card__copy{padding:14px 14px 12px}
}
@media (max-width:480px){
  .vv-auth-shell{padding-top:16px}
  .vv-auth-shell__top{padding-bottom:20px}
  .vv-auth-shell .vv-auth-panel{padding:22px 18px}
  .vv-auth-shell .vv-auth-intro h1{font-size:26px}
}

@media (prefers-reduced-motion:reduce){
  .vv-discovery__slide.is-demo,.vv-app-nav__link,.vv-app-tab__icon{transition:none}
}
@media print{
  .vv-app-rail,.vv-app-tabbar,.vv-coach,.vv-coach-scrim,.vv-swipe-hint{display:none!important}
}

/* Build 09: Recall is the canonical card in retrieval and reveal states. */
.vv-recall{display:flex;flex-direction:column;gap:12px;min-height:calc(100dvh - 40px);max-width:520px;margin-inline:auto}
.vv-recall__stage{display:block;margin:auto 0;padding:0;list-style:none}
.vv-recall__slide:not([hidden]){display:grid;gap:12px}
.vv-recall .vv-runtime-card{width:100%;max-width:520px}
.vv-runtime-card--recall .vv-runtime-card__copy{padding:20px 22px 18px}
.vv-runtime-card--recall .vv-runtime-card__media{aspect-ratio:16/9}
.vv-recall-card__instruction{margin:6px 0 0;color:var(--vv-secondary);font-weight:600}
.vv-recall-card__answer{display:flex;align-items:center;gap:12px;margin-top:14px;padding:13px 14px;border:1px dashed var(--vv-border-strong);border-radius:var(--vv-radius-control);background:var(--vv-bg)}
.vv-recall-card__answer>span{display:grid;place-items:center;flex:0 0 34px;height:34px;border-radius:50%;background:var(--vv-subtle);font:700 18px/1 var(--vv-font-heading)}
.vv-recall-card__answer p{margin:0;color:var(--vv-secondary);font-size:14px}
.vv-recall-card__hint{margin-top:12px;padding:12px 14px;border-radius:var(--vv-radius-control);background:var(--vv-focus-wash)}
.vv-recall-card__hint p:last-child{margin:5px 0 0;font:700 18px/1.35 var(--vv-font-heading);letter-spacing:.04em;overflow-wrap:anywhere}
/*
 * The pre-Reveal controls. A flex row, not a fixed two-column grid.
 *
 * The grid was `1fr 1.35fr`, and `recall.js` hides Show hint once it has been used - which left
 * Reveal alone in the *narrow* column, about 141px wide, and "Reveal answer" broke onto two lines.
 * Laid out as a flex row the remaining control simply takes the row, at any width, with no rule
 * needed for the one-child case and nothing shrunk to make the label fit.
 */
.vv-recall__pre-actions{display:flex;flex-wrap:wrap;gap:10px}
.vv-recall__pre-actions .vv-button{flex:1 1 auto;min-height:48px;justify-content:center;white-space:nowrap}
.vv-recall__pre-actions [data-vv-reveal]{flex:2 1 auto}
.vv-recall__ratings button:hover{border-color:var(--vv-ink);background:var(--vv-bg)}
.vv-recall__ratings button:focus-visible{outline:3px solid var(--vv-focus);outline-offset:2px}

.vv-recall__failure{padding:12px 14px;border-radius:var(--vv-radius-control);background:var(--vv-error-wash);color:var(--vv-error)}
.vv-recall__failure p{margin:0 0 8px}
.vv-recall__complete,.vv-recall-empty{margin:auto 0;padding:30px;border:1px solid var(--vv-border);border-radius:var(--vv-radius-panel);background:var(--vv-surface);box-shadow:0 18px 50px rgba(22,21,15,.07)}
.vv-recall__complete h2,.vv-recall-empty h2{margin:6px 0 0}
.vv-recall__complete>p:not(.vv-label),.vv-recall-empty>p{color:var(--vv-secondary)}
.vv-recall__complete dl{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:22px 0}
.vv-recall__complete dl div{padding:14px;border-radius:var(--vv-radius-control);background:var(--vv-bg);text-align:center}
.vv-recall__complete dt{color:var(--vv-secondary);font-size:13px}.vv-recall__complete dd{margin:4px 0 0;font:700 24px/1 var(--vv-font-heading)}
.vv-recall__complete [data-vv-review-list]{padding-top:18px;border-top:1px solid var(--vv-border)}
.vv-recall__complete [data-vv-review-list] h3{font-size:16px}.vv-recall__complete [data-vv-review-list] ul{margin:8px 0 0;padding-left:20px}
.vv-recall__complete-actions{margin-top:22px}

@media (max-width:719px){
  .vv-recall{min-height:calc(100dvh - 24px - var(--vv-tabbar-h) - env(safe-area-inset-bottom));padding-bottom:8px}
  .vv-runtime-card--recall .vv-runtime-card__copy{padding:16px}
  .vv-recall-card__instruction{font-size:14px}.vv-recall-card__answer{padding:10px 12px}
  .vv-recall__pre-actions{position:sticky;bottom:calc(var(--vv-tabbar-h) + env(safe-area-inset-bottom) + 6px);z-index:4;padding:6px;background:var(--vv-bg)}
  .vv-recall__ratings{position:sticky;bottom:calc(var(--vv-tabbar-h) + env(safe-area-inset-bottom) + 6px);z-index:4;padding:6px;background:var(--vv-bg)}
  .vv-recall__ratings button{min-height:58px}
  .vv-recall__complete,.vv-recall-empty{padding:24px 20px}
}
@media (max-width:374px){.vv-recall__ratings{gap:6px}.vv-recall__ratings button{padding-inline:5px}}

/* ==================================================== Build 09B: engagement and interaction */
/*
 * Everything below is presentation. No figure here is computed in the browser, no state is stored
 * in a class, and if this stylesheet failed to load the learner would still see every number,
 * every control and every completion - in plainer form. Durations and curves come from the motion
 * tokens in site.css so reduced motion is handled once, at the token, rather than rule by rule.
 */

/* ------------------------------------------------------------------------------ The streak */
/* A surface card, not an orange one: the accent marks the days that were earned and nothing else.
   Three distinct facts, in three distinct registers - the run, the last seven days, what's next. */
.vv-streak{display:grid;gap:14px;padding:24px 28px;background:var(--vv-surface);
  border:1px solid var(--vv-border);border-radius:var(--vv-radius-panel)}
.vv-streak__head{display:flex;align-items:center;gap:14px}
.vv-streak__mark{display:grid;place-items:center;flex:none;width:44px;height:44px;border-radius:14px;
  background:var(--vv-accent-wash);color:var(--vv-accent-text)}
.vv-streak__mark .vv-icon{width:24px;height:24px}
.vv-streak__headings{min-width:0}
.vv-streak .vv-label{margin:0;color:var(--vv-muted)}
.vv-streak__value{margin-top:2px;font:700 30px/1.05 var(--vv-font-heading);letter-spacing:-.04em;font-variant-numeric:tabular-nums}
.vv-streak__value span{font:600 15px/1 var(--vv-font-body);letter-spacing:0;color:var(--vv-secondary)}
.vv-streak__value--none{font:600 17px/1.3 var(--vv-font-body);letter-spacing:0;color:var(--vv-secondary)}
.vv-streak__state{color:var(--vv-secondary);font-size:15px}

/* The seven days. State is carried by the mark inside the box - a check, an open ring or nothing -
   so it survives greyscale, high contrast and any colour-vision difference. */
.vv-streak__week{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:8px;
  margin:2px 0 0;padding:0;list-style:none;max-width:420px}
.vv-streak__day{display:grid;justify-items:center;gap:6px}
.vv-streak__day-initial{font:600 11.5px/1 var(--vv-font-body);color:var(--vv-muted)}
.vv-streak__day.is-today .vv-streak__day-initial{color:var(--vv-ink);font-weight:700}
.vv-streak__summary{font-size:13.5px;color:var(--vv-secondary)}
.vv-streak__summary,.vv-streak__next{font-variant-numeric:tabular-nums}
.vv-streak__next{padding-top:12px;border-top:1px solid var(--vv-border);font-size:13.5px;color:var(--vv-muted)}

/* The milestone. Acknowledgement, not a prize: one line of real text, the completion mark beside
   it, and nothing that can be collected, counted or lost. */
.vv-streak__milestone{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:var(--vv-radius-control);
  background:var(--vv-accent-wash);color:var(--vv-ink);font-size:14px;line-height:1.45}
.vv-streak__milestone strong{font-family:var(--vv-font-heading);letter-spacing:-.01em}
.vv-streak__milestone-mark{display:grid;place-items:center;flex:none;color:var(--vv-accent-text)}
.vv-complete-mark--inline{width:30px;height:30px}

/* ------------------------------------------------------------------------- Completion mark */
/* One bespoke shape for every "you finished something" moment: the ring closes, then the check
   draws. Two dashed strokes and two keyframes - no runtime, no asset request, no library. */
.vv-complete-mark{width:64px;height:64px;color:var(--vv-accent)}
.vv-complete-mark__slot{display:block;margin-bottom:8px}

/* Completion panels arrive rather than appear. Local to the component; the page does not move. */
.vv-discovery__summary:not([hidden]),.vv-recall__complete:not([hidden]){
  animation:vv-rise var(--vv-motion-duration-emphasis) var(--vv-motion-ease-enter) both}
@keyframes vv-rise{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* --------------------------------------------------------------------- Saving, and arriving */
/* A learner who acts on a card waits for a server round trip. Before this build that wait had no
   appearance at all: the button simply went dead. The control they pressed now holds a pressed,
   working state and keeps its full contrast; everything else in the group recedes. The spinner is
   the only looping animation in the product, it exists only while a request is in flight, and it
   is removed - not merely hidden - under reduced motion, where the word does the work. */
.is-pending .vv-decision:disabled,.is-pending .vv-recall__ratings button:disabled,
.is-pending .vv-recall__pre-actions .vv-button:disabled{opacity:.45}
/* Specific enough to beat the dimming rule above it, which is why each group is named rather
   than relying on .is-saving alone: :disabled costs as much specificity as a class. */
.is-pending .vv-decision.is-saving,.is-pending .vv-recall__ratings button.is-saving,
.is-pending .vv-recall__pre-actions .vv-button.is-saving{opacity:1;transform:scale(var(--vv-motion-press-scale))}
.is-saving{position:relative}
.vv-decision.is-saving{border-color:var(--vv-accent);background:var(--vv-accent);color:var(--vv-on-accent)}
.vv-recall__ratings button.is-saving{border-color:var(--vv-accent);background:var(--vv-accent-wash)}
.vv-recall__pre-actions .vv-button.is-saving{box-shadow:inset 0 0 0 2px var(--vv-accent)}
.is-saving .vv-spinner{display:inline-block}
.vv-spinner{display:none;flex:none;width:15px;height:15px;margin-left:8px;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;vertical-align:-2px;
  animation:vv-spin 620ms linear infinite}
@keyframes vv-spin{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){.vv-spinner{display:none!important}}

/* The revealed answer enters; the cue that preceded it never moves. */
.vv-recall__slide[data-revealed] .vv-runtime-card{
  animation:vv-reveal var(--vv-motion-duration-standard) var(--vv-motion-ease-enter) both}
@keyframes vv-reveal{from{opacity:0;transform:translateY(var(--vv-motion-distance-sm))}to{opacity:1;transform:none}}

/* --------------------------------------------------------------------- Progress and the shell */
/* Genuine progress only: the width changes when a persisted decision moves the counter, never
   before, and the Home meter is drawn at its real value on load. */
.vv-meter__fill{transition:width var(--vv-motion-duration-emphasis) var(--vv-motion-ease-emphasis)}
.vv-app-nav__link,.vv-app-tab__icon{
  transition:background-color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
             color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard)}

@media(max-width:1179px){.vv-streak__week{max-width:none}}
@media(max-width:719px){
  .vv-streak{padding:20px;gap:12px}
  .vv-streak__value{font-size:26px}
  .vv-streak__week{gap:6px}
  .vv-streak__state,.vv-streak__milestone{font-size:14px}
  .vv-complete-mark{width:52px;height:52px}
}
@media(max-width:374px){
  .vv-streak{padding:16px}
  .vv-streak__day-initial{font-size:10.5px}
}
@media print{.vv-spinner{display:none!important}}

/* On a wide screen the streak card becomes two columns rather than one long strip in a mostly
   empty row: the run and its message on the left, the seven days and what comes next on the
   right. Same markup, same order for a screen reader; only the visual arrangement changes. */
@media(min-width:1000px){
  .vv-streak{grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);align-items:center;column-gap:36px;row-gap:12px;padding:24px 30px}
  .vv-streak__head,.vv-streak__state,.vv-streak__milestone{grid-column:1}
  .vv-streak__week,.vv-streak__summary,.vv-streak__next{grid-column:2}
  .vv-streak__week{margin-top:2px;max-width:none}
  .vv-streak__next{margin-top:2px}
}

/* ================================================ Build 09C: composition, control and continuity */
/*
 * Everything below is presentation. No figure here is computed in the browser, no state lives in a
 * class, and with this stylesheet missing a learner would still see every number, every control and
 * every completion in plainer form. Durations and curves come from the motion tokens in site.css,
 * so reduced motion is handled once at the token rather than rule by rule.
 */

/* ---------------------------------------------------------------- Home: three levels of weight */
/*
 * The problem this replaces: Home was six surfaces with the same white fill, the same 1px border
 * and the same radius, stacked in one column - so the thing to do next looked exactly like the
 * reference figures beside it, and two identical orange buttons could appear at once with nothing
 * saying which mattered. Level is now carried by elevation, fill and type scale, not by repetition.
 *
 *   1. the recommended action - raised, warm, the only primary CTA on the page
 *   2. the available action   - flat, outlined, clearly secondary
 *   3. continuity and counts  - quiet surfaces with no border at all
 */
.vv-action{position:relative;border-radius:var(--vv-radius-panel)}
.vv-action--primary{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:24px 32px;
  padding:30px 34px;background:var(--vv-surface);border:1px solid rgba(255,77,0,.22);overflow:hidden;
  box-shadow:0 18px 44px rgba(22,21,15,.07),0 4px 14px rgba(255,77,0,.06)}
.vv-action--primary::after{content:"";position:absolute;top:-70px;right:-50px;width:240px;height:240px;
  border-radius:50%;background:var(--vv-accent-wash);opacity:.9;pointer-events:none}
.vv-action__body,.vv-action__cta{position:relative;z-index:1;min-width:0}
.vv-action__eyebrow{margin:0}
.vv-action--primary .vv-action__eyebrow{color:var(--vv-accent-text)}
.vv-action--primary h2{margin-top:6px;font-size:clamp(23px,2.2vw,30px);letter-spacing:-.03em}
.vv-action__lead{margin-top:8px;color:var(--vv-secondary);max-width:540px}
.vv-action--primary .vv-primary-cta{margin:0}

/* Available, not recommended. No fill, no shadow, smaller heading: the page's second voice. */
.vv-action--secondary{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:14px 24px;
  padding:18px 24px;background:transparent;border:1px solid var(--vv-border)}
.vv-action--secondary .vv-action__eyebrow{color:var(--vv-muted)}
.vv-action--secondary h2{margin-top:4px;font-size:18px;letter-spacing:-.02em}

/* Nothing to do there yet. A line of status, deliberately not shaped like a button. */
.vv-status{display:flex;align-items:center;gap:10px;padding:13px 18px;border-radius:var(--vv-radius-card);
  background:var(--vv-subtle);color:var(--vv-secondary);font-size:14px}
.vv-status__mark{display:grid;place-items:center;flex:none;color:var(--vv-muted)}
.vv-status__mark .vv-icon{width:18px;height:18px}
.vv-status__text strong{color:var(--vv-ink);font-family:var(--vv-font-heading);letter-spacing:-.01em}

/* Both halves finished. One panel that says so, rather than two that say nothing. */
.vv-action--done{padding:30px 34px;background:var(--vv-success-wash);border:1px solid rgba(15,107,69,.18)}
.vv-action--done .vv-label{color:var(--vv-success)}
.vv-action--done .vv-complete-mark{color:var(--vv-success)}
.vv-action--done h2{margin-top:6px;font-size:clamp(22px,2.1vw,28px);letter-spacing:-.03em}
.vv-action__done-detail{display:grid;gap:6px;margin-top:18px;padding-top:16px;
  border-top:1px solid rgba(15,107,69,.18);font-size:14px;color:var(--vv-secondary)}
.vv-action__done-detail strong{color:var(--vv-ink);font-family:var(--vv-font-heading);letter-spacing:-.01em}

/* Level three. Three counts, one quiet strip, no card chrome and no legend dots. */
.vv-summary{padding:16px 22px;border-radius:var(--vv-radius-card);background:var(--vv-subtle)}
.vv-summary__list{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:16px;margin:0}
.vv-summary__figure{display:flex;align-items:baseline;gap:9px;min-width:0}
.vv-summary__label{font:600 11.5px/1.4 var(--vv-font-body);letter-spacing:.1em;text-transform:uppercase;color:var(--vv-muted)}
.vv-summary__value{margin:0;font:700 23px/1 var(--vv-font-heading);letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;color:var(--vv-ink)}

/* A learner who has not started yet: what happens next, instead of a wall of zeroes. */
.vv-firstrun{padding:22px 26px;border-radius:var(--vv-radius-card);background:var(--vv-subtle)}
.vv-firstrun__title{font-size:16px;letter-spacing:-.01em}
.vv-firstrun__steps{display:grid;gap:9px;margin:12px 0 0;padding-left:20px;color:var(--vv-secondary);font-size:14px;line-height:1.5}
.vv-firstrun__steps strong{color:var(--vv-ink)}

/* The learner application's primary CTA renders at 19px bold, which is WCAG large text - the one
   size at which the approved #FFFFFF on #FF4D00 (3.33:1) clears its 3:1 bar. The marketing pages
   are untouched: this build fixes the controls it touches, not the whole palette. */
.vv-app-page .vv-button.vv-primary-cta:not(.vv-button--outline):not(.vv-button--dark){font-size:19px}

/* ------------------------------------------------------------------ Discovery: choosing a kind */
.vv-category{width:100%;max-width:480px;margin-inline:auto}
.vv-category__list{display:flex;gap:8px;margin:0;padding:2px 0;list-style:none;
  overflow-x:auto;scrollbar-width:none;overscroll-behavior-x:contain}
.vv-category__list::-webkit-scrollbar{display:none}
.vv-category__item{flex:none}
.vv-category__chip{display:inline-flex;align-items:center;gap:7px;min-height:40px;padding:8px 14px;
  border:1px solid var(--vv-border);border-radius:var(--vv-radius-pill);background:var(--vv-surface);
  color:var(--vv-secondary);font:600 13px/1 var(--vv-font-body);text-decoration:none;white-space:nowrap;
  transition:background-color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
             border-color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
             color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard)}
.vv-category__chip .vv-icon{width:17px;height:17px;stroke-width:1.9;flex:none}
.vv-category__chip:hover{background:var(--vv-subtle);color:var(--vv-ink)}
.vv-category__chip:focus-visible{outline:3px solid var(--vv-focus);outline-offset:2px}
/* Selected is carried by weight and fill together, never by colour alone. */
.vv-category__chip.is-current{background:var(--vv-accent-wash);border-color:rgba(255,77,0,.4);
  color:var(--vv-accent-text);font-weight:700}

/* A category the learner has finished, while the library still has plenty elsewhere. */
.vv-discovery--caught-up{display:flex;flex-direction:column;gap:18px;justify-content:center;
  min-height:calc(100dvh - 80px);max-width:1120px;margin-inline:auto}
.vv-discovery__caught-up{margin-inline:auto;width:100%;max-width:480px;padding:34px 30px;text-align:center;
  background:var(--vv-surface);border:1px solid var(--vv-border);border-radius:var(--vv-radius-panel);
  box-shadow:0 18px 50px rgba(22,21,15,.07)}
.vv-discovery__caught-up .vv-complete-mark__slot{display:flex;justify-content:center}
.vv-discovery__caught-up h2{margin-top:6px;font-size:24px;letter-spacing:-.03em}
.vv-discovery__caught-up p:not(.vv-label){margin-top:8px;color:var(--vv-secondary)}
.vv-discovery__caught-up-actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin-top:22px}
.vv-discovery__caught-up-actions .vv-button .vv-icon{color:inherit}

/* -------------------------------------------------------------------------------------- Undo */
/*
 * A decision used to be permanent from the learner's side: an accidental "I know this" removed an
 * expression from the product for good, with no surface anywhere that could bring it back. This is
 * that recovery. Non-modal, out of the card's way, and it expires on its own if ignored.
 */
/*
 * Build 09C.1 moved this out of a floating toast and into the Discovery session band, where it is
 * part of the layout rather than something laid over it. The styles live beside that band, with
 * the counter it shares a row with; see `.vv-discovery__status` above.
 */

/* ------------------------------------------------------- Recall: a canvas and an action rail */
/*
 * Recall was a 520px column centred in about a thousand: half the desktop empty while the learner
 * scrolled to reach the controls, and the controls themselves jumped down the page at Reveal
 * because the revealed card is much taller than the cue. The card keeps its exact canonical width
 * and identity; what changes is that the controls now live beside it, in one place, and Reveal
 * swaps what that place says instead of moving it.
 */
/* The counter, in the rail beside the task rather than in a band of its own above the card. */
.vv-recall__count{margin:0;font:700 13px/1.2 var(--vv-font-heading);letter-spacing:.02em;
  font-variant-numeric:tabular-nums;color:var(--vv-muted)}
.vv-recall__rail{display:grid;align-content:start;gap:13px}
.vv-recall__rail-title{font:700 16px/1.3 var(--vv-font-heading);letter-spacing:-.02em}
.vv-recall__rail-lead{color:var(--vv-secondary);font-size:14px;line-height:1.5}
.vv-recall__rail-state{display:grid;gap:13px;
  transition:opacity var(--vv-motion-duration-standard) var(--vv-motion-ease-enter),
             transform var(--vv-motion-duration-standard) var(--vv-motion-ease-enter)}
.vv-recall__rail-state.is-arriving{opacity:0;transform:translateY(4px);transition:none}

/* Three identical controls. The key cap is the only thing that tells them apart, and it carries no
   judgement at all - no colour coding, no icon, nothing that says one of them is the right one. */
.vv-recall__ratings{display:grid;grid-template-columns:1fr;gap:8px}
.vv-recall__ratings button{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:11px;
  min-height:58px;padding:10px 14px;border:1px solid var(--vv-border-strong);border-radius:var(--vv-radius-control);
  background:var(--vv-surface);color:var(--vv-ink);font:inherit;text-align:left;cursor:pointer}
.vv-recall__key{display:grid;place-items:center;flex:none;width:24px;height:24px;border-radius:7px;
  background:var(--vv-subtle);color:var(--vv-secondary);font:700 12px/1 var(--vv-font-heading)}
.vv-recall__outcome{display:grid;gap:2px;min-width:0}
.vv-recall__ratings strong{display:block;font:700 14px/1.2 var(--vv-font-heading)}
/* Kept at every width. Hiding this on a phone removed the only explanation of how Almost differs
   from Got it, on the device where the least experienced learners are. */
.vv-recall__outcome-note{display:block;color:var(--vv-secondary);font-size:12px;line-height:1.35}
.vv-recall__ratings button:hover{border-color:var(--vv-ink);background:var(--vv-bg)}
.vv-recall__ratings button:hover .vv-recall__key{background:var(--vv-border)}

/*
 * The typed answer. Always present, never required, and nothing typed here is ever sent or stored.
 *
 * Build 09C.1 removed the session switch that used to govern this. Typing is not a mode: the field
 * is simply part of Recall, and leaving it empty is a complete way to practise that leads to the
 * three self-assessment controls exactly as it always did.
 */
.vv-type{display:grid;gap:6px}
.vv-type__label{font:600 11.5px/1.4 var(--vv-font-body);letter-spacing:.1em;text-transform:uppercase;color:var(--vv-muted)}
/* 16px so a phone does not zoom the viewport when the field takes focus. */
.vv-type__input{width:100%;min-height:48px;padding:11px 14px;border:1px solid var(--vv-border-strong);
  border-radius:var(--vv-radius-control);background:var(--vv-surface);color:var(--vv-ink);
  font:500 16px/1.4 var(--vv-font-body)}
.vv-type__input:focus-visible{outline:3px solid var(--vv-focus);outline-offset:2px}
.vv-type__note{font-size:12px;color:var(--vv-muted)}
.vv-type-result{display:grid;gap:11px;padding:13px 15px;border-radius:var(--vv-radius-control);background:var(--vv-bg)}
.vv-type-result .vv-label{margin-bottom:3px}
.vv-type-result__value{font:500 15px/1.4 var(--vv-font-body);overflow-wrap:anywhere}
.vv-type-result__value--answer{font:700 17px/1.35 var(--vv-font-heading);letter-spacing:-.02em}
/* Accent, not green. "Exact match" is a fact about two strings, and must not start to look like a
   mark out of ten for a learner who is about to judge themselves. */
.vv-type-result__match{display:inline-flex;align-items:center;gap:6px;color:var(--vv-accent-text);
  font:600 13px/1 var(--vv-font-body)}
.vv-type-result__match .vv-icon{width:16px;height:16px;stroke-width:2.1}

/* The rated card leaves and the next arrives. Quieter than Discovery: this is a judgement moment. */
.vv-recall__slide{transition:opacity var(--vv-motion-duration-standard) var(--vv-motion-ease-standard),
  transform var(--vv-motion-duration-standard) var(--vv-motion-ease-standard)}
.vv-recall__slide.is-leaving{opacity:0;transform:translateY(-4px);pointer-events:none}
.vv-recall__slide.is-entering{opacity:0;transform:translateY(4px);transition:none}
@media(prefers-reduced-motion:reduce){
  .vv-recall__slide,.vv-recall__slide.is-leaving,.vv-recall__rail-state{transition:none}
  .vv-recall__slide.is-entering{opacity:1;transform:none}
}

/* ------------------------------------------------------- The streak, as the brand's own mark */
/*
 * Seven rounded boxes read as a row of form controls. Seven bars on the brand's 14 degree tilt read
 * as a week of learning, and connect into a sequence.
 *
 * Build 09C.1 corrects what an inactive day looks like. It was a short stub at 12% black, which did
 * not read as "a day you did not learn" - it read as missing data, as though the product had lost
 * the record. Every day now occupies the same full-height footprint, which is what makes the seven
 * read as one timeline; what changes between them is fill against outline:
 *
 *   inactive past day  full height, light outline, no fill      - it happened, nothing was learned
 *   today, not earned  full height, stronger outline            - still open
 *   active day         full height, solid #FF4D00               - earned
 *
 * Never colour alone: fill versus outline survives greyscale and every colour-vision difference.
 *
 * And deliberately nothing else. There is no frost, no snowflake and no fourth state, because the
 * product has no streak-protection semantics: an inactive day is not a protected day, and drawing
 * it as one would be the interface telling the learner something untrue. The full-height footprint
 * leaves an obvious place for that treatment when Build 10 decides what protection actually means.
 */
.vv-streak__week{position:relative;align-items:end;gap:10px}
.vv-streak__week::after{content:"";position:absolute;left:2px;right:2px;bottom:21px;height:1px;
  background:var(--vv-border);pointer-events:none}
.vv-streak__day{position:relative;gap:7px}
.vv-streak__bar-slot{display:grid;place-items:end center;height:56px}
/* Eleven wide against forty tall is the brand mark's own proportion. Any thinner and seven of them
   stop reading as bars and start reading as tally marks. */
.vv-streak__bar{display:block;width:26px;height:56px;overflow:visible}
.vv-streak__bar-shape{fill:none;stroke:var(--vv-muted);stroke-width:1.5;
  transform-box:view-box;transform-origin:13px 50px;transform:rotate(14deg) scaleY(var(--vv-bar,1));
  transition:transform var(--vv-motion-duration-emphasis) var(--vv-motion-ease-emphasis),
             fill var(--vv-motion-duration-standard) var(--vv-motion-ease-standard)}
.vv-streak__day.is-active .vv-streak__bar-shape{fill:var(--vv-accent);stroke:none}
.vv-streak__day.is-today .vv-streak__bar-shape{fill:none;stroke:var(--vv-ink);stroke-width:2}
.vv-streak__day.is-today.is-active .vv-streak__bar-shape{fill:var(--vv-accent);stroke:var(--vv-accent)}
.vv-streak__milestone{animation:vv-rise var(--vv-motion-duration-emphasis) var(--vv-motion-ease-enter) both}

/* --------------------------------------------------------- The completion mark, as VocabCue */
.vv-complete-mark__bar{stroke-dasharray:1;stroke-dashoffset:1;
  animation:vv-mark-bar var(--vv-motion-duration-emphasis) var(--vv-motion-ease-enter) forwards}
.vv-complete-mark__bar--2{animation-delay:110ms}
.vv-complete-mark__bar--3{animation-delay:220ms}
@keyframes vv-mark-bar{to{stroke-dashoffset:0}}
@media(prefers-reduced-motion:reduce){
  /* The finished mark, drawn. site.css stops every animation under this query with !important,
     which would leave all three strokes at full dash offset - that is, invisible - so the offset
     has to be wound back here or the mark simply vanishes for the learners who asked for less. */
  .vv-complete-mark__bar{animation:none;stroke-dashoffset:0}
}

/* ================================================================== Wide: the two-column Recall */
/* The same 1000px boundary Discovery already uses. Below it, one column and the existing model. */
@media(min-width:1000px){
  .vv-recall{max-width:1120px}
  .vv-recall__slide:not([hidden]){display:grid;grid-template-columns:minmax(0,520px) minmax(270px,330px);
    align-items:start;justify-content:center;column-gap:32px;row-gap:0}
  .vv-recall__canvas{min-width:0}
  /*
   * The rail sticks instead of the card shrinking.
   *
   * The previous build capped the canvas at `100dvh - 150px` so that a revealed card would always
   * fit a 768px laptop - and the only thing in the card that could give up the difference was the
   * illustration, which `object-fit:contain` then shrank on both axes. The cap is gone. The card is
   * its natural height, the page scrolls when it needs to, and the controls stay in reach because
   * the rail travels with the learner rather than because the picture was made smaller.
   *
   * Normal document scroll: no nested scroller, no independent overflow, no scroll trap.
   */
  .vv-recall__rail{position:sticky;top:24px;padding:22px 24px;background:var(--vv-surface);
    border:1px solid var(--vv-border);border-radius:var(--vv-radius-panel)}
  .vv-recall__pre-actions{flex-direction:column}
  .vv-recall__pre-actions .vv-button{width:100%}
  .vv-recall__complete,.vv-recall-empty{max-width:560px;margin-inline:auto}
}
@media(min-width:1000px) and (max-height:820px){
  /* A laptop in landscape. The card keeps its full illustration; what gives is the assumption that
     the whole page has to fit, and the sticky rail above is what makes that safe. */
  .vv-recall{min-height:0}
}

/* ------------------------------------------------------------- Wide: Discovery gains a selector */
@media(min-width:1000px){
  /* The caught-up surface is not a session: it has no card to flank and no decisions to place, so
     it steps out of Discovery's three-column grid rather than being dealt into the left gutter. */
  .vv-app-page .vv-discovery--caught-up{display:flex;flex-direction:column;justify-content:center;
    grid-template-columns:none;grid-template-areas:none}
  .vv-app-page .vv-discovery--caught-up .vv-category{grid-area:auto;max-width:none}
  .vv-app-page .vv-discovery__head{grid-area:head;max-width:none;justify-content:center}
  /* Room to breathe on a wide screen: the chips centre and wrap, and the counter follows them. */
  .vv-app-page .vv-discovery__head .vv-category{flex:0 1 auto}
  .vv-app-page .vv-discovery .vv-category__list{justify-content:center;flex-wrap:wrap}
  .vv-app-page .vv-discovery__summary{grid-row:2}
}

/* ============================================================================ Tablet and phone */
@media(max-width:1179px){
  .vv-action--primary{padding:26px 28px}
}
/*
 * Below the two-column breakpoint the rail is not a panel, it is just the controls in flow - so it
 * steps out of the layout entirely. That matters for more than tidiness: `position:sticky` is
 * bounded by its parent's box, and a wrapper exactly as tall as the ratings leaves them nowhere to
 * stick. With the wrappers out of the way the Build 09 sticky rules below work again and the
 * outcome controls stay above the tab bar on a phone, where the card is far taller than the screen.
 */
@media(max-width:999px){
  .vv-recall__rail,.vv-recall__rail-state{display:contents}
  /*
   * `.vv-recall__manual` is Build 09C.1's wrapper around the three outcome controls, and it has to
   * step out of the layout for exactly the reason the two above it do: `position:sticky` is bounded
   * by the parent's box, and a wrapper the same height as the ratings leaves them nowhere to stick.
   * Leaving it in place put them under the tab bar on a 390px phone - found by the axe sweep.
   */
  /*
   * The verdict is a panel rather than loose controls, so it stays a box and sticks as one. It
   * carries the outcome and the single Next, and on a phone the card above it is far taller than
   * the screen: without this the learner would reveal an answer and have to hunt for the result.
   */
  .vv-recall__rail-state .vv-verdict{position:sticky;bottom:calc(var(--vv-tabbar-h) + env(safe-area-inset-bottom) + 6px);z-index:4}
}
/*
 * Tablet portrait, where there is no tab bar to clear and no second column to put the rail in.
 * A 768x1024 viewport is tall, but so is the card, and the ratings were landing about thirty pixels
 * below the fold - so the learner had to scroll to find the controls they had just been asked to
 * use. Sticky here for the same reason as on a phone, with the offset a phone needs subtracted.
 */
@media(min-width:720px) and (max-width:999px){
  .vv-recall__pre-actions,.vv-recall__ratings{position:sticky;bottom:12px;z-index:4;
    padding:8px 6px;background:var(--vv-bg);border-radius:var(--vv-radius-control)}
}


@media(max-width:719px){
  .vv-action--primary{grid-template-columns:1fr;gap:18px;padding:22px 20px}
  .vv-action--primary::after{width:170px;height:170px;top:-75px;right:-60px}
  .vv-action__cta{text-align:center}
  .vv-action--secondary{grid-template-columns:1fr;gap:12px;padding:16px 18px}
  .vv-action--secondary .vv-action__cta{justify-self:start}
  .vv-action--done{padding:22px 20px}
  .vv-summary{padding:14px 16px}
  .vv-summary__label{font-size:10.5px;letter-spacing:.08em}
  .vv-summary__value{font-size:20px}
  .vv-firstrun{padding:18px 18px 18px 16px}
  .vv-category{max-width:none}
  .vv-category__chip{padding-inline:12px;font-size:12.5px}
  .vv-discovery__status-text{font-size:13px}
  .vv-recall__rail{gap:11px}
  .vv-recall__ratings button{min-height:54px;padding:8px 12px;gap:9px}
  .vv-streak__bar-slot{height:46px}
  .vv-streak__bar{width:22px;height:46px}
  .vv-streak__week::after{bottom:18px}
}
@media(max-width:519px){
  .vv-summary__list{grid-auto-flow:row;gap:8px}
  .vv-summary__figure{justify-content:space-between}
}
@media(max-width:374px){
  .vv-action--primary{padding:18px 16px}
  .vv-recall__ratings button{padding-inline:10px}
  .vv-recall__outcome-note{font-size:11.5px}
  .vv-streak__bar{width:19px}
}


/* ======================================================= Build 09C.1: the owner review corrections */
/*
 * Everything below is presentation. It carries no state, computes no figure, and with this
 * stylesheet missing a learner would still see every control, every count and every outcome in
 * plainer form.
 */

/* ---------------------------------------------------------------- Solid orange carries white */
/*
 * The brand rule, restored and made to work rather than worked around.
 *
 * Build 09C put near-black labels on solid #FF4D00 because #FFFFFF on #FF4D00 is 3.33:1, which
 * fails AA's 4.5:1 for normal-size text. That solved contrast by changing the brand. The correct
 * reconciliation is the other lever in the same success criterion: 3.33:1 passes the **3:1 bar for
 * large text**, and large text is >= 18.66px bold. So every solid-orange control in the learner
 * application renders its label at 19px bold and keeps its white label. Nothing about #FF4D00
 * changes, no second "button orange" is invented, and the geometry of each control is widened to
 * carry the larger type rather than the type shrunk to fit the control.
 *
 * Specificity note: site.css sets these buttons to 16px late in the file, and to 15px again under
 * 600px. `.vv-app-page` is one class more specific and this file loads after it, so both are beaten
 * without an `!important` - and the marketing pages, which are out of scope, are untouched.
 */
/*
 * Scope: plain `.vv-button` only.
 *
 * `.vv-primary-cta` and `.vv-secondary-cta` carry the `.vv-button` class but they are composed
 * controls with a complete colour system of their own, including states that deliberately invert -
 * the primary CTA swaps to a white fill with an orange label on hover, the secondary one is a light
 * surface with a dark-orange label at rest. A blanket "the label is white" rule reaches into both
 * and produces white on white: it is what made Home's primary action render as an empty pill in the
 * owner's review, and the same collision had already cost the secondary CTA once.
 *
 * So the rule names them out. It governs the ordinary solid-orange buttons - Learn this, Reveal
 * answer, Continue with Mix, Back to home - and leaves the two designed CTAs to their own file.
 */
.vv-app-page .vv-button:not(.vv-button--outline):not(.vv-button--dark):not(.vv-primary-cta):not(.vv-secondary-cta),
.vv-app-page .vv-button--small:not(.vv-button--outline):not(.vv-button--dark):not(.vv-primary-cta):not(.vv-secondary-cta){
  color:var(--vv-on-accent);font-size:19px;font-weight:700;line-height:1.25}
.vv-app-page .vv-button:not(.vv-button--outline):not(.vv-button--dark):not(.vv-primary-cta):not(.vv-secondary-cta):hover,
.vv-app-page .vv-button:not(.vv-button--outline):not(.vv-button--dark):not(.vv-primary-cta):not(.vv-secondary-cta):focus-visible{
  color:var(--vv-on-accent)}
.vv-app-page .vv-button:not(.vv-button--outline):not(.vv-button--dark):not(.vv-primary-cta):not(.vv-secondary-cta) .vv-icon{color:inherit}
/* Hover lifts rather than darkening. #E04400 under white is 4.20:1 - still over the large-text bar,
   but moving the background under a label to signal hover is the wrong tool when elevation says it
   without touching contrast at all. */
@media(hover:hover) and (pointer:fine){
  .vv-app-page .vv-button:not(.vv-button--outline):not(.vv-button--dark):not(.vv-primary-cta):not(.vv-secondary-cta):hover{
    background:var(--vv-accent);box-shadow:0 7px 18px rgba(255,77,0,.28);transform:translateY(-1px)}
}
@media(prefers-reduced-motion:reduce){
  .vv-app-page .vv-button:not(.vv-button--outline):not(.vv-button--dark):not(.vv-primary-cta):not(.vv-secondary-cta):hover{transform:none}
}

/* The two Discovery decisions, at the same large-text geometry. */
.vv-app-page .vv-decision{font-size:19px;line-height:1.25}
.vv-app-page .vv-decision--learning,
.vv-app-page .vv-decision--learning:hover,
.vv-app-page .vv-decision--learning:focus-visible{color:var(--vv-on-accent)}
.vv-app-page .vv-decision--learning .vv-decision__mark{color:var(--vv-on-accent);opacity:1}
/*
 * "I know this" is not the lesser choice and must not look like the greater one either. Its hover
 * used to invert to a solid ink fill, which put a near-black primary-weight button beside the
 * orange one and made the pair read as a warning and a confirmation. It now tints and firms its
 * border - clearly hovered, clearly clickable, clearly the same weight as its sibling.
 */
@media(hover:hover) and (pointer:fine){
  .vv-app-page .vv-decision--known:hover{background:var(--vv-subtle);border-color:var(--vv-ink);
    color:var(--vv-ink);box-shadow:0 6px 16px rgba(22,21,15,.12)}
}

/* ------------------------------------------------------------- The category scroll affordance */
/*
 * A fade on the edge the chips continue past, and only while they do. app.js measures the scroller
 * and sets these classes; with JavaScript off the list simply scrolls as it always did. The mask
 * lifts while a chip inside has focus, so a focus ring near the edge is never what gets faded.
 */
.vv-category__list.has-more-end{
  -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 34px),transparent);
  mask-image:linear-gradient(to right,#000 calc(100% - 34px),transparent)}
.vv-category__list.has-more-start{
  -webkit-mask-image:linear-gradient(to right,transparent,#000 34px);
  mask-image:linear-gradient(to right,transparent,#000 34px)}
.vv-category__list.has-more-start.has-more-end{
  -webkit-mask-image:linear-gradient(to right,transparent,#000 34px,#000 calc(100% - 34px),transparent);
  mask-image:linear-gradient(to right,transparent,#000 34px,#000 calc(100% - 34px),transparent)}
.vv-category__list:focus-within{-webkit-mask-image:none;mask-image:none}

/* -------------------------------------------------------------------- The typed-answer verdict */
/*
 * What VocabCue makes of a typed answer, shown only once the outcome is genuinely recorded.
 *
 * The palette is the product's own: charcoal, the accent, and the brand's tilted bar. There is no
 * green and no red anywhere in here, no percentage, no cross and no tick out of ten - this is
 * retrieval practice, and an outcome is a report of what happened, not a mark. Every state is
 * carried by the words first and by fill-versus-outline second, so nothing depends on colour and
 * nothing depends on the animation running.
 */
.vv-verdict{display:grid;gap:12px;padding:16px 18px;border-radius:var(--vv-radius-control);
  background:var(--vv-bg);border:1px solid var(--vv-border)}
.vv-verdict__head{display:flex;align-items:center;gap:12px;margin:0}
.vv-verdict__bars{display:flex;flex:none}
.vv-verdict__mark{display:block;width:56px;height:34px;overflow:visible}
.vv-verdict__title{font:700 20px/1.2 var(--vv-font-heading);letter-spacing:-.025em;color:var(--vv-ink)}
.vv-verdict__reason{color:var(--vv-secondary);font-size:14px;line-height:1.45}
.vv-verdict[aria-busy="true"] .vv-verdict__title{color:var(--vv-muted);font-size:15px;font-weight:600}
/* The bars. Pending is a quiet outline; each outcome fills as many of the three as it has earned. */
.vv-verdict__bar{fill:none;stroke:var(--vv-border-strong);stroke-width:1.5}
.vv-verdict.is-got_it .vv-verdict__bar{fill:var(--vv-accent);stroke:none}
.vv-verdict.is-almost .vv-verdict__bar--1,.vv-verdict.is-almost .vv-verdict__bar--2{fill:var(--vv-accent);stroke:none}
.vv-verdict.is-almost .vv-verdict__bar--3{fill:none;stroke:var(--vv-ink);stroke-width:2}
.vv-verdict.is-forgot .vv-verdict__bar{fill:none;stroke:var(--vv-ink);stroke-width:2}
/* The bars draw upward from their own feet, one after another. Nothing is communicated by this. */
.vv-verdict.is-got_it .vv-verdict__bar,.vv-verdict.is-almost .vv-verdict__bar,.vv-verdict.is-forgot .vv-verdict__bar{
  transform-box:fill-box;transform-origin:50% 100%;
  animation:vv-verdict-bar var(--vv-motion-duration-emphasis) var(--vv-motion-ease-enter) both}
.vv-verdict__bar--2{animation-delay:70ms}
.vv-verdict__bar--3{animation-delay:140ms}
@keyframes vv-verdict-bar{from{transform:scaleY(.25);opacity:.4}to{transform:none;opacity:1}}
@media(prefers-reduced-motion:reduce){
  .vv-verdict .vv-verdict__bar{animation:none;transform:none;opacity:1}
}
/* The difference, marked token by token. Emphasis, not a grade: `mark` reaches assistive
   technology as emphasis too, and the accent wash never reads as a red pen. */
.vv-verdict__diff{padding:1px 4px;border-radius:5px;background:var(--vv-accent-wash);
  color:var(--vv-accent-text);font-weight:700}
.vv-verdict__next{width:100%}
.vv-verdict .vv-type-result{padding:0;background:transparent}

/* Only ever one of these two is on screen: the verdict for a typed answer, the three controls for
   a blank one. Asking a learner to grade what the product has already judged would be asking them
   to do the work twice. */
.vv-recall__manual{display:grid;gap:13px}
/*
 * Below the two-column breakpoint it steps out of the layout, for the same reason the rail and the
 * rail state already do: `position:sticky` is bounded by the parent's box, and a wrapper exactly as
 * tall as the ratings leaves them nowhere to stick. This has to be declared *after* the rule above
 * rather than beside its siblings higher up the file - written there it lost on order and the
 * outcome controls went back under the tab bar, which is how the axe sweep found it.
 */
@media(max-width:999px){.vv-recall__manual{display:contents}}

/* ------------------------------------------------------------------------- Narrow decisions */
/*
 * They stay side by side all the way down to 360px, and both keep their mark.
 *
 * The first pass stacked them under 380px, which bought a fitting label at the cost of 56px of
 * vertical space on the narrowest screens - the exact screens where the illustration has least
 * room to begin with, and where two full-width buttons read as a much heavier footer than the
 * decision deserves. Measured instead: at 360px each control has 160px, and "I know this" needs
 * 110px of label, an 18px mark, a 7px gap and 20px of padding - 155px. It fits.
 *
 * Below 360px it genuinely does not, and there the mark gives way rather than the layout: the
 * label always carries the meaning, the icon only reinforces it. That rule already exists in
 * site.css and is left where it is.
 */


/* ==================================================== Build 09C.2: less chrome, more card */
/*
 * Everything below is presentation. It carries no state, computes no figure, sends no request, and
 * with this stylesheet missing a learner would still see every control, every label and every
 * outcome in plainer form. The adaptive states are driven by two attributes and one class that
 * discovery.js sets; none of them is stored anywhere but the tab.
 */

/* ------------------------------------------------ Discovery decisions, as an adaptive pair */
/*
 * The problem, stated honestly: on a 390px phone the two decision controls and their strip were
 * 58px of permanent furniture under a card whose illustration is the reason the screen exists.
 * They earn that space the first time - they are how a new learner discovers there is a choice at
 * all, and what the two halves of the choice mean. They stop earning it the moment the learner
 * has swiped a card, because from then on the words are describing a gesture the learner is
 * already using.
 *
 * So they teach, then they get out of the way, and the trigger is proof rather than a guess: one
 * swipe decision that reached the server. Not a drag, not a cancelled gesture, not a tap - a tap
 * demonstrates that the button was found, which is the thing the labels were there to help with.
 */
.vv-decision__label{white-space:nowrap;
  transition:max-width var(--vv-motion-duration-standard) var(--vv-motion-ease-standard),
    opacity var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
    margin-inline-start var(--vv-motion-duration-standard) var(--vv-motion-ease-standard)}
.vv-decision{transition:background var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
  color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
  border-color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
  opacity var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
  transform var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
  padding-inline var(--vv-motion-duration-standard) var(--vv-motion-ease-standard)}

/*
 * The collapsed state. Phone and tablet only: a desktop learner has a pointer, no swipe to have
 * demonstrated and no scarcity of vertical space, so §17's reduction there is a smaller button
 * with its words intact, further down this file.
 *
 * The label is collapsed by width, not by `display:none`, for two reasons. It animates - the word
 * genuinely slides back in when the control is used, which is what makes the compact state read as
 * the same control rather than a different one. And a zero-width element with `overflow:hidden`
 * stays in the accessibility tree, so even without the `aria-label` the markup also carries, the
 * name would still be the sentence and never "check button".
 */
@media(max-width:999px){
  [data-vv-compact] .vv-discovery__actions{grid-template-columns:auto auto;justify-content:space-between;
    padding:2px 0}
  /* padding-block as well as inline: without it a portrait tablet keeps the base 12px and the
     control settles at 51.8px, so the band contracts horizontally and not vertically. 48px is the
     touch target and the compact control is exactly that, at every width it exists. */
  .vv-app-page [data-vv-compact] .vv-decision{min-width:48px;min-height:48px;
    padding-block:8px;padding-inline:0;gap:0;border-radius:999px}
  [data-vv-compact] .vv-decision .vv-decision__mark .vv-icon{width:21px;height:21px}
  [data-vv-compact] .vv-decision__label{max-width:0;opacity:0;overflow:hidden}
  /*
   * Used: the word comes back for as long as it takes to read, riding out with the card that is
   * already leaving. The decision was sent before this class was added - it is a report, not a
   * step, and nothing waits for it.
   */
  [data-vv-compact] .vv-decision.is-confirming{padding-inline:16px}
  [data-vv-compact] .vv-decision.is-confirming .vv-decision__label{max-width:9em;opacity:1;margin-inline-start:8px}
}

/*
 * The pair answers the gesture.
 *
 * While a swipe is past its commit threshold the decision it is heading for lifts and says its
 * name; the other recedes without disappearing, because the learner may still change their mind
 * and a control that vanished mid-drag would be a control they could not return to. Cancelling
 * clears both, instantly, and nothing has been decided.
 *
 * This is why the cue on the card is now a mark alone: the words are here, on the control that
 * will carry them out, and two sentences competing for one glance taught nothing twice as loudly.
 */
[data-vv-drag] .vv-decision{opacity:.45}
[data-vv-drag="known"] .vv-decision--known,
[data-vv-drag="learning"] .vv-decision--learning{opacity:1;transform:translateY(-2px)}

/* The cue on the card: the mark, on its own, where the eye already is. */
.vv-discovery__cue{padding:0;display:grid;place-items:center;width:34px;height:34px;border-radius:999px}
.vv-discovery__cue .vv-icon{width:19px;height:19px}
@media(max-width:719px){.vv-discovery__cue{width:30px;height:30px}.vv-discovery__cue .vv-icon{width:17px;height:17px}}

@media(prefers-reduced-motion:reduce){
  /* Every adaptive state still happens; none of it travels. The label appears and disappears, the
     leaning control is still the emphasised one, the ring still fills, and nothing is lost but the
     movement - the ring is driven by the gesture itself, so it tracks the finger with no
     interpolation to remove. */
  .vv-decision,.vv-decision__label{transition:none}
  [data-vv-drag="known"] .vv-decision--known,
  [data-vv-drag="learning"] .vv-decision--learning{transform:none}
}

/* ------------------------------------------------------- Desktop decisions, right-sized */
/*
 * §17: the labels stay, the mass comes down. They were 240px wide and 58px tall in a 150px-minimum
 * gutter, stretched to fill space rather than sized to their content - which made two supporting
 * controls look like the page's primary actions, beside a card they are supposed to serve.
 *
 * Measured rather than guessed: "I know this" at 19px/700 Montserrat is about 112px, the mark is
 * 20px, the gap 10px and the padding 2x16px - 174px. 196px leaves the wider of the two a
 * comfortable margin and no wrap, and 52px is still well above the 44px target.
 */
@media(min-width:1000px){
  .vv-app-page .vv-decision{max-width:196px;min-height:52px;padding:10px 16px}
}

/* --------------------------------------------------- Recall: smaller controls, a bigger card */
/*
 * §60-62. The pre-Reveal pair was two equal flex children, each at least half the rail, each with
 * the same 48px height and the same generous padding - so a supporting action and the primary one
 * looked like two primary ones, and together they took a third of the rail on a phone.
 *
 * Show hint is sized to its own label now. It is an outline control, which means ink on white,
 * which means it never needed the 19px the brand's white-on-orange does - that size exists to
 * clear a contrast threshold, and applying it to a control that already clears it by a mile was
 * accessibility theatre that cost real space. 14px, its own width, still a 48px touch target.
 *
 * Reveal keeps every part of its accessible treatment: solid #FF4D00, a white label, 19px bold,
 * one line. What it gives up is width it was not using and padding it did not need.
 */
.vv-recall__pre-actions{gap:8px;align-items:stretch}
/* `.vv-app-page` and not just the block, because site.css sets these buttons to 40px through a
   selector carrying two `:not()`s - which counts as three classes. A two-class rule loses to it
   silently, which is exactly how Build 09C.1's ordering bugs happened. Match the weight and win
   on file order instead of guessing. */
.vv-app-page .vv-recall__pre-actions .vv-button{min-height:48px}
.vv-app-page .vv-recall__pre-actions [data-vv-show-hint]{flex:0 0 auto;padding:10px 16px;font-size:14px;gap:8px}
.vv-app-page .vv-recall__pre-actions [data-vv-reveal]{flex:1 1 auto;max-width:264px;padding:10px 18px;gap:10px}
@media(min-width:1000px){
  /* The rail is a column of blocks, but these two are one decision - a row keeps them read as a
     pair and stops the primary action spanning a 330px panel for a two-word label. */
  .vv-recall__pre-actions{flex-direction:row;flex-wrap:wrap}
  .vv-recall__pre-actions .vv-button{width:auto}
  .vv-app-page .vv-recall__pre-actions [data-vv-reveal]{max-width:none}
}

/* ------------------------------------------------------------ Speaking the answer: the control */
/*
 * The microphone belongs to the answer field, not to a mode. It sits on the field's own row, at
 * the field's own height, and it appears only where the browser can actually listen - a disabled
 * microphone on every other browser would be a permanent advertisement for something unavailable.
 */
.vv-type__row{display:flex;align-items:stretch;gap:8px}
.vv-type__row .vv-type__input{flex:1 1 auto;min-width:0}
.vv-mic{flex:none;display:grid;place-items:center;width:48px;min-height:48px;
  border:1px solid var(--vv-border-strong);border-radius:var(--vv-radius-control);
  background:var(--vv-surface);color:var(--vv-secondary);
  transition:background-color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
    color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard),
    border-color var(--vv-motion-duration-fast) var(--vv-motion-ease-standard)}
.vv-mic__art{display:grid;place-items:center}
.vv-mic .vv-icon{width:21px;height:21px}
.vv-mic:hover{border-color:var(--vv-ink);color:var(--vv-ink)}
.vv-mic:focus-visible{outline:3px solid var(--vv-focus);outline-offset:2px}
.vv-mic:disabled{opacity:.5}
/* Listening is carried by fill as well as by colour, and by the word beside it as well as by both. */
.vv-mic[data-state="listening"]{background:var(--vv-accent);border-color:var(--vv-accent);color:var(--vv-on-accent)}
.vv-mic[data-state="error"]{border-color:var(--vv-border-strong);color:var(--vv-muted)}

/* The state line. One row, under the control, never a panel. */
.vv-type__status{display:flex;align-items:center;gap:8px;min-height:20px;
  font-size:13px;color:var(--vv-secondary)}
.vv-type__status[data-state="error"]{color:var(--vv-ink)}
.vv-mic__bars-slot{display:none;flex:none}
.vv-type__status[data-state="listening"] .vv-mic__bars-slot{display:block}
.vv-mic__bars{display:block;width:26px;height:18px}
.vv-mic__bar{fill:var(--vv-accent)}
/*
 * A state indicator, not a meter. The three bars rise and settle on the brand's tilt while the
 * browser is listening; they are driven by a keyframe and are not connected to any audio level,
 * which is exactly why they are three abstract bars and not a waveform pretending to be one.
 * They exist only in the listening state, so nothing loops once recognition has stopped.
 */
.vv-type__status[data-state="listening"] .vv-mic__bar{transform-box:fill-box;transform-origin:50% 100%;
  animation:vv-listen 1s var(--vv-motion-ease-standard) infinite}
.vv-type__status[data-state="listening"] .vv-mic__bar--2{animation-delay:.14s}
.vv-type__status[data-state="listening"] .vv-mic__bar--3{animation-delay:.28s}
@keyframes vv-listen{0%,100%{transform:scaleY(.55)}50%{transform:scaleY(1)}}
@media(prefers-reduced-motion:reduce){
  /* Still visibly the listening state - the bars are drawn, the button is filled, the word is
     there. Only the movement goes, and no meaning goes with it. */
  .vv-type__status[data-state="listening"] .vv-mic__bar{animation:none;transform:none}
}

/* ------------------------------------------------------- The card stack, and how it advances */
/*
 * §64-71. One motif, one meaning: bring the next vocabulary card forward. It replaces the right
 * arrow on Recall's continuation control and the paper plane on Home's - both of which said
 * "navigate somewhere", which is not what either action does.
 *
 * The motion is the meaning, drawn: on hover and focus the front card eases out of the stack, and
 * on press it advances. Six pixels, once, no loop, no spring. Under reduced motion the stack is
 * simply a stack, and the icon still says what it says.
 */
.vv-cards__front,.vv-cards__back{
  transition:transform var(--vv-motion-duration-standard) var(--vv-motion-ease-standard)}
/* Build 10B: Recall's Next card is now a `.vv-primary-cta--cards` control and moves by Home's rules
   below; the Recall-only stack motion that used to live here is gone with its two-card glyph. */
@media(prefers-reduced-motion:reduce){
  .vv-cards__front,.vv-cards__back{transition:none;transform:none!important}
}

/* --------------------------------------------------------------- The continuation control */
/*
 * §63, §65. It was a full-width button carrying a generic arrow, under a result the learner is
 * meant to read - mostly empty orange, in the place where the product should look most like
 * itself. Sized to its content now, at the foot of the verdict, with the stack beside the words.
 */
.vv-app-page .vv-verdict .vv-verdict__next{width:auto;max-width:max-content;justify-self:start;
  min-height:46px;padding:10px 18px;gap:10px}
.vv-verdict__next .vv-icon{width:20px;height:20px}
@media(max-width:379px){
  /* Only where the row genuinely runs out: the control spans rather than wrapping its label.
     At (0,6,0) because the composed CTA's own layout rule in site.css sits at (0,4,0). */
  .vv-app-page .vv-verdict .vv-verdict__next{width:100%;max-width:none;justify-content:center}
  .vv-app-page .vv-verdict .vv-button.vv-primary-cta.vv-verdict__next:not(.vv-button--outline):not(.vv-button--dark){justify-content:center}
}


/* ============================ Build 09C.3: a floating dock, a progress ring, and a real card stack */
/*
 * Two cosmetic corrections from the owner's review of the deployed 09C.2 build. Presentation only:
 * no state, no figure, no request, and with this stylesheet missing every control still works.
 */

/* ------------------------------------------------ The compact controls stop being a dock at all */
/*
 * The problem, in the owner's words: the circles were fine, the white strip they sat in was not.
 *
 * A sticky band painted with the page background is the obvious way to keep controls above the tab
 * bar, and it is what 09C.2 shipped. On a phone it reads as a fixed footer panel - a full-width
 * opaque surface whose only content is two small circles and a great deal of nothing, sitting under
 * a card whose illustration is the reason the screen exists.
 *
 * So in the compact state the wrapper keeps its job and loses its appearance. It still positions,
 * it still sticks, and it is completely transparent; only the two circles are drawn, and they float
 * over the lower corners of the card rather than reserving a strip beneath it. The labelled state
 * is untouched, because there the band genuinely is the control.
 *
 * `pointer-events` matters as much as the paint. A transparent full-width layer that still caught
 * pointers would swallow the swipe it exists to support, so the wrapper passes them through and
 * only the buttons take them back.
 */
@media(max-width:999px){
  .vv-app-page [data-vv-compact] .vv-discovery__actions{
    background:transparent;padding:0;pointer-events:none;
    /* Pulled up over the card's last 48px: the circles overlap the illustration's lower corners,
       which the owner has accepted, and the footer of empty page underneath goes away. */
    margin-top:-56px}
  .vv-app-page [data-vv-compact] .vv-decision{pointer-events:auto;
    box-shadow:0 6px 18px rgba(22,21,15,.16)}
  /* Solid, because they are now over artwork rather than over the page background. */
  .vv-app-page [data-vv-compact] .vv-decision--known{background:var(--vv-surface)}
  /*
   * ...but not while it is saving, and this is the second half of the owner's missing check.
   *
   * `.vv-decision.is-saving` repaints the control accent and sets its label to white. The rule above
   * is one class more specific, so on the Known control the background stayed white while the icon
   * turned white with it - an invisible check on a white disc, which is exactly what was
   * photographed. The saving state now repaints the whole control or none of it.
   */
  .vv-app-page [data-vv-compact] .vv-decision.is-saving{
    background:var(--vv-accent);border-color:var(--vv-accent);color:var(--vv-on-accent)}
}
/*
 * The confirmation is not a pending state.
 *
 * Every control is disabled while the card leaves, and disabled means 45% - which left the one
 * control that had just reported a result washed out over the illustration, at the only moment it
 * had something to say. It is naming a decision that is already stored, so it stays legible.
 */
.vv-app-page .is-pending .vv-decision.is-confirming:disabled{opacity:1}

/* --------------------------------------------------------------- Swipe progress, as a stroke */
/*
 * What a swipe shows instead of a label.
 *
 * Build 09C.2 reopened the compact control's label while the learner dragged towards it, which
 * undid the collapse on every card for the rest of the session - the interface repeating a lesson
 * the learner had already passed. A stroke filling around the mark says the same thing (this
 * gesture is nearly committed) inside the space the control already occupies.
 *
 * It is a progress stroke and nothing else: no head, no arrowhead, no gap that could read as a
 * refresh glyph. `pathLength="1"` normalises the circle, so `--vv-swipe` is a plain 0-to-1 number
 * straight from the gesture - displacement over commit threshold - and the stylesheet never needs
 * to know the radius.
 *
 * Only the control being swiped towards draws one. The other stays quiet.
 *
 * Inset rather than outset, on purpose. Around the outside the stroke crosses whatever the floating
 * button happens to be over, and the Learn control's white ring disappeared entirely against a pale
 * illustration. Drawn inside, every pixel of it sits on the control's own fill, so its contrast is
 * a property of the component rather than of the artwork behind it.
 */
.vv-decision{position:relative}
.vv-decision__ring{position:absolute;inset:0;display:none;opacity:0;pointer-events:none;
  transition:opacity var(--vv-motion-duration-fast) var(--vv-motion-ease-standard)}
.vv-decision__ring svg{display:block;width:100%;height:100%;overflow:visible;
  transform:rotate(-90deg)}
.vv-decision__ring-track{stroke:var(--vv-border);stroke-width:2.5;fill:none}
.vv-decision__ring-fill{stroke:var(--vv-accent);stroke-width:3;fill:none;
  stroke-dasharray:1;stroke-dashoffset:calc(1 - var(--vv-swipe,0))}
[data-vv-drag="known"] .vv-decision--known .vv-decision__ring,
[data-vv-drag="learning"] .vv-decision--learning .vv-decision__ring{opacity:1}

/*
 * ------------------------------------------- The in-flight moment, on the ring instead of a spinner
 *
 * This is the defect the owner photographed, and it was never the ring.
 *
 * `markSaving()` appends a `.vv-spinner` to the control that was used. In the labelled state that is
 * fine - the control is a wide pill with room beside the label. In the compact state the control is
 * a 48px circle with no horizontal padding, so the spinner became a second flex child: it pushed the
 * mark off centre and overflowed the button's own edge. Measured on the deployed build, mid-request:
 * the Learn control's `+` sat at the left of the circle with a revolving arc spilling out to the
 * right, and the Known control's check was pushed past the circular border entirely and clipped, so
 * the control rendered as a plain white disc. Those are exactly the two screenshots.
 *
 * So the spinner does not enter a circular control at all, and the wait is reported by the one thing
 * already concentric with the icon: the ring, drawn complete. It does not rotate, it has no head and
 * it never covers the centre - the mark stays exactly where it is, at full size, throughout.
 *
 * The labelled state keeps its spinner, where it has always been correct.
 */
@media(max-width:999px){
  [data-vv-compact] .vv-decision .vv-spinner{display:none}
  [data-vv-compact] .vv-decision.is-saving .vv-decision__ring{opacity:1}
  [data-vv-compact] .vv-decision.is-saving .vv-decision__ring-fill{stroke-dashoffset:0}
}
/* Saving repaints the control accent, so the stroke follows it onto that surface. */
.vv-decision.is-saving .vv-decision__ring-track{stroke:rgba(255,255,255,.35)}
.vv-decision.is-saving .vv-decision__ring-fill{stroke:var(--vv-on-accent)}
/* On the orange control the accent would vanish into its own fill. */
.vv-decision--learning .vv-decision__ring-track{stroke:rgba(255,255,255,.35)}
.vv-decision--learning .vv-decision__ring-fill{stroke:var(--vv-on-accent)}
/* The ring belongs to the compact circles: a labelled pill is not a circle and cannot carry one,
   and on a wide screen the controls sit in the gutters where there is no gesture to report. */
@media(max-width:999px){
  [data-vv-compact] .vv-decision__ring{display:block}
}

/* ----------------------------------------------------------- Home: the card stack, and its hover */
/*
 * The rest state keeps the approved composition - solid #FF4D00, a white label, a light rounded
 * tile, the motif in accent inside it. `--vv-cta-chip-bg` is what the two rear cards are filled
 * with, so each card has its own edge instead of three outlines sharing one; it follows the tile
 * through every state.
 */
.vv-primary-cta--cards .vv-primary-cta__chip{--vv-cta-chip-bg:var(--vv-on-accent)}

/*
 * Hover and focus.
 *
 * The shared CTA inverts to a white fill with an orange label, which is right on the marketing
 * pages where it is a moment of emphasis on a quiet surface. On Home it put a large white pill in
 * the middle of an orange panel and read as the button emptying out. So the learner CTA keeps its
 * colour and earns its feedback from elevation and from the icon instead - the two rear cards fan
 * out of the stack, a few pixels, as though the deck were being opened.
 *
 * Deliberately scoped to `--cards`, which only the learner application uses. The marketing CTA's
 * inversion is untouched, and the rule sits at (0,3,0) to beat site.css's own hover without
 * `!important`.
 */
@media(hover:hover) and (pointer:fine){
  .vv-app-page .vv-button.vv-primary-cta--cards:not(.vv-button--outline):not(.vv-button--dark):hover{
    background:var(--vv-accent);color:var(--vv-on-accent);border-color:var(--vv-accent);
    box-shadow:0 10px 24px rgba(255,77,0,.30);transform:translateY(-1px)}
  .vv-app-page .vv-primary-cta--cards:hover .vv-primary-cta__chip{
    background:var(--vv-on-accent);color:var(--vv-accent)}
}
.vv-app-page .vv-button.vv-primary-cta--cards:not(.vv-button--outline):not(.vv-button--dark):focus-visible{
  background:var(--vv-accent);color:var(--vv-on-accent);
  box-shadow:0 10px 24px rgba(255,77,0,.30);transform:translateY(-1px)}
.vv-app-page .vv-primary-cta--cards:focus-visible .vv-primary-cta__chip{
  background:var(--vv-on-accent);color:var(--vv-accent)}
.vv-app-page .vv-button.vv-primary-cta--cards:not(.vv-button--outline):not(.vv-button--dark):active{
  transform:translateY(0);box-shadow:0 3px 10px rgba(255,77,0,.22)}

/* The stack opening. Two cards, a few pixels, one degree, once. */
.vv-cards__back,.vv-cards__mid,.vv-cards__front{
  transition:transform var(--vv-motion-duration-standard) var(--vv-motion-ease-standard)}
.vv-primary-cta--cards:hover .vv-cards__back,
.vv-primary-cta--cards:focus-visible .vv-cards__back{transform:translate(1.6px,-1.4px) rotate(1.5deg)}
.vv-primary-cta--cards:hover .vv-cards__mid,
.vv-primary-cta--cards:focus-visible .vv-cards__mid{transform:translate(.8px,-.7px) rotate(.75deg)}
/* The front card is the one being dealt: it holds its place while the deck opens behind it. */
.vv-primary-cta--cards:active .vv-cards__front{transform:translate(-.8px,.6px)}
@media(prefers-reduced-motion:reduce){
  .vv-cards__back,.vv-cards__mid,.vv-cards__front{transition:none;transform:none!important}
  .vv-app-page .vv-button.vv-primary-cta--cards:hover,
  .vv-app-page .vv-button.vv-primary-cta--cards:focus-visible{transform:none}
}
