/*
 * Design system foundations — tokens, reset, shell layout and the shared
 * component classes reused by every assets/styles/screens/*.css file.
 * Source of truth: the "school planner" redesign (pastels, Fustat, hachured
 * tracks, black pill tab bar) — see the project's design reference docs.
 * Screen-specific layout must NOT be added here — it belongs in
 * assets/styles/screens/<zone>.css.
 */

/* ------------------------------------------------------------------- */
/* Fonts — Fustat, self-hosted variable font (300-800), two subsets     */
/* ------------------------------------------------------------------- */

@font-face {
    font-family: 'Fustat';
    src: url("../fonts/fustat-latin-zsJONjL.woff2") format('woff2-variations'),
         url("../fonts/fustat-latin-zsJONjL.woff2") format('woff2');
    font-weight: 300 800;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Fustat';
    src: url("../fonts/fustat-latin-ext-WysWm1C.woff2") format('woff2-variations'),
         url("../fonts/fustat-latin-ext-WysWm1C.woff2") format('woff2');
    font-weight: 300 800;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------------------- */
/* Reset                                                                */
/* ------------------------------------------------------------------- */

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* The [hidden] attribute must always win over an author `display` rule.
   Many components toggle `hidden` on elements that also carry a `display:
   flex/grid` rule (scenario groups, sheet panels, …); without this, the
   UA-stylesheet `[hidden] { display: none }` loses on specificity and the
   element stays visible. One reset instead of per-element overrides. */
[hidden] {
    display: none !important;
}

html, body {
    height: 100%;
}

body {
    /* Letterboxing behind the centered 430px app column on wide viewports. */
    background: #e4e6e9;
    font-family: 'Fustat', system-ui, sans-serif;
    color: var(--ink);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

a {
    color: inherit;
    text-decoration: none;
}

button, input, select, textarea {
    font-family: inherit;
    color: inherit;
}

button {
    background: none;
    border: none;
    cursor: pointer;
}

ul, ol {
    list-style: none;
}

img, svg {
    display: block;
    max-width: 100%;
}

/* ------------------------------------------------------------------- */
/* Tokens                                                               */
/* ------------------------------------------------------------------- */

:root {
    --bg: #eceef0;
    --surface: #ffffff;
    --surface-2: #f4f5f7;

    --ink: #17171c;
    --ink-2: #75777c;
    --ink-disabled: #9a9ca1;

    --violet: #a28ef9;
    --violet-soft: #e7e1fd;

    --green: #a4f5a6;
    --green-border: #59c65e;
    --green-soft: #e4fbe4;
    --green-ink: #2e7d33;

    --yellow: #ffd89d;
    --yellow-soft: #ffefd8;

    --coral: #ffb3a7;
    --coral-ink: #b3402e;
    --coral-ink-dark: #7a2417;

    --flame: #ff9d47;

    --border: #d9dbe0;
    --border-dashed: #c4c7cc;
    --separator: #eceef0;

    --radius-card: 24px;
    --radius-card-sm: 20px;
    --radius-tile: 16px;
    --radius-sheet: 28px;
    --radius-pill: 999px;

    /* Conversation feature (docs/CONVERSATION-UX-BRIEF.md): secondary text
       links ("changer", "personnaliser") readable on white, unlike --violet
       alone; and a 2nd, paler violet fill for "selected" cards, distinct from
       --violet-soft's banner/badge use. */
    --link: #6a54c7;
    --violet-selected: #f6f3ff;

    --shadow-tabbar: 0 12px 32px rgba(23, 23, 28, .25);

    --ease: cubic-bezier(.4, 0, .2, 1);

    /* Floating tab bar height + breathing room, used as .app-content's
       bottom padding so scrolled content never sits under it. */
    --tabbar-clearance: 104px;
}

/* ------------------------------------------------------------------- */
/* App shell                                                            */
/* ------------------------------------------------------------------- */

.app-viewport {
    max-width: 430px;
    margin: 0 auto;
    min-height: 100dvh;
    background: var(--bg);
    position: relative;
    display: flex;
    flex-direction: column;
}

.app-content {
    flex: 1;
    /* Top inset clears the notch/Dynamic Island on iPhones (0 elsewhere). */
    padding: calc(20px + env(safe-area-inset-top, 0px)) 20px calc(var(--tabbar-clearance) + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Focus mode: session player, full-screen recorder-driven screens — no tab
   bar, so no reserved clearance at the bottom. */
.app-content.is-focus {
    /* No tab bar in focus mode, but bottom actions must still clear the
       iOS home indicator. */
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
}

/* ------------------------------------------------------------------- */
/* Typography                                                           */
/* ------------------------------------------------------------------- */

h1, h2, h3 {
    font-weight: 800;
    line-height: 1.2;
}

/* Shared screen-level title (design-tokens.md typography scale: 28/800),
   used across zones (practice, progress, admin…) — not screen-specific. */
.screen-title {
    font-size: 28px;
    font-weight: 800;
    line-height: 1.2;
}

/* Small caps label above a section of cards (design-tokens.md: 12 · caps ·
   gray). Used across home, progress, admin, profile… — kept generic here so
   the many templates that already reference it stop rendering as plain text. */
.section-label {
    font-size: 12px;
    font-weight: 800;
    letter-spacing: .03em;
    text-transform: uppercase;
    color: var(--ink-2);
}

p {
    line-height: 1.5;
}

.text-body {
    font-size: 15px;
    line-height: 1.5;
}

.text-secondary {
    font-size: 13px;
    color: var(--ink-2);
}

.text-muted {
    color: var(--ink-disabled);
}

.text-flame {
    color: var(--flame);
}

/* Visually hidden but still announced — names icon-only controls (back links,
   step indicators) that would otherwise reach a screen reader unlabelled,
   since <twig:Icon> always renders aria-hidden. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
}

/* Tabular figures so aligned numeric columns (stats, costs, timers) don't jitter. */
.tabular-nums {
    font-variant-numeric: tabular-nums;
}

/* Bilingual content marker: English content is set in italic Fustat 500
   (synthetic italic — Fustat ships no true italic on Google Fonts). */
.en {
    font-style: italic;
    font-weight: 500;
}

/* ------------------------------------------------------------------- */
/* Buttons                                                              */
/* ------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: 52px;
    padding: 0 24px;
    border-radius: var(--radius-pill);
    font-size: 16px;
    font-weight: 700;
    border: none;
    white-space: nowrap;
    transition: transform .15s var(--ease);
}

.btn:active {
    transform: scale(.97);
}

.btn:disabled,
.btn.is-disabled {
    opacity: .35;
    pointer-events: none;
}

/* Primary: one per screen, ink background, white text. */
.btn-primary {
    background: var(--ink);
    color: #fff;
}

/* Accent: violet background, ink text (never white on pastel). */
.btn-accent {
    background: var(--violet);
    color: var(--ink);
}

/* Secondary: white, neutral border. */
.btn-secondary {
    background: #fff;
    color: var(--ink);
    border: 1.5px solid var(--border);
    height: 48px;
    font-size: 15px;
    font-weight: 600;
}

/* Tertiary: flat neutral surface. */
.btn-tertiary {
    background: var(--surface-2);
    color: var(--ink);
    height: 48px;
    font-size: 15px;
    font-weight: 600;
}

.btn-small {
    height: 40px;
    padding: 0 18px;
    font-size: 14px;
}

/* Round 48px icon button — used standalone (back/close/settings), not
   nested inside .btn. */
.btn-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-pill);
    background: #fff;
    border: 1.5px solid var(--separator);
    color: var(--ink);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: transform .15s var(--ease);
}

.btn-icon:active {
    transform: scale(.94);
}

/* ------------------------------------------------------------------- */
/* Cards & tiles                                                        */
/* ------------------------------------------------------------------- */

.card {
    background: var(--surface);
    border-radius: var(--radius-card);
    padding: 20px;
}

.tile {
    background: var(--surface-2);
    border-radius: var(--radius-tile);
    padding: 14px 16px;
}

/* ------------------------------------------------------------------- */
/* Chips                                                                */
/* ------------------------------------------------------------------- */

.chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 26px;
    padding: 0 12px;
    border-radius: var(--radius-pill);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink);
    white-space: nowrap;
}

.chip-neutral {
    background: var(--surface-2);
}

.chip-neutral.is-active {
    background: var(--ink);
    color: #fff;
}

.chip-violet {
    background: var(--violet-soft);
}

.chip-en {
    background: var(--violet);
    font-weight: 700;
    font-style: italic;
}

/* ------------------------------------------------------------------- */
/* Badges — 6-state AI pipeline pill. The AI wait is always named, never  */
/* a bare spinner; transcribing/analyzing blink slowly to read as "busy". */
/* ------------------------------------------------------------------- */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 26px;
    padding: 0 12px;
    border-radius: var(--radius-pill);
    font-size: 12.5px;
    font-weight: 700;
    white-space: nowrap;
}

.badge-pending {
    background: var(--surface-2);
    color: var(--ink-2);
}

.badge-transcribing,
.badge-analyzing {
    background: var(--yellow);
    color: var(--ink);
    animation: dsBlink 1.6s ease-in-out infinite;
}

.badge-completed {
    background: var(--green);
    color: var(--ink);
}

.badge-failed {
    background: var(--coral);
    color: var(--coral-ink-dark);
}

.badge-queued-budget {
    background: var(--violet-soft);
    color: var(--ink);
}

/* ------------------------------------------------------------------- */
/* Track — signature diagonal-hachure progress indicator                */
/* ------------------------------------------------------------------- */

.track {
    border-radius: var(--radius-pill);
    border: 1.5px solid var(--border);
    background: repeating-linear-gradient(45deg, transparent, transparent 4px, var(--border) 4px, var(--border) 5px);
    overflow: hidden;
}

.track-fill {
    height: 100%;
    border-radius: var(--radius-pill);
    border: 1.5px solid var(--green-border);
    background: var(--green) repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(89, 198, 94, .55) 4px, rgba(89, 198, 94, .55) 5px);
    transition: width .4s var(--ease);
}

.track-segments {
    display: flex;
    gap: 6px;
}

.track-seg {
    flex: 1;
    height: 8px;
    border-radius: var(--radius-pill);
    border: 1.5px solid var(--border);
    background: repeating-linear-gradient(45deg, transparent, transparent 4px, var(--border) 4px, var(--border) 5px);
}

.track-seg.is-done {
    border-color: var(--green-border);
    background: var(--green) repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(89, 198, 94, .55) 4px, rgba(89, 198, 94, .55) 5px);
}

.track-seg.is-current {
    border-color: var(--violet);
    background: var(--violet);
}

/* ------------------------------------------------------------------- */
/* AudioPlayer — pill player (Recorder preview, progress audio history)  */
/* ------------------------------------------------------------------- */

.audio-player {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    background: var(--surface);
    border: 1.5px solid var(--border);
    border-radius: var(--radius-pill);
    padding: 6px 14px 6px 6px;
    box-sizing: border-box;
}

.audio-player-toggle {
    width: 44px;
    height: 44px;
    min-width: 44px;
    border-radius: var(--radius-pill);
    background: var(--ink);
    border: none;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
}

.audio-player-toggle:active {
    transform: scale(.97);
}

.audio-player-icon {
    display: flex;
}

/* Same UA-stylesheet override as `.recorder-panel[hidden]` below: an
   author-origin `display` rule always wins over `[hidden] { display: none }`
   regardless of specificity, so without this the play/pause icons
   audio_player_controller.js toggles via `hidden` would both render at once. */
.audio-player-icon[hidden] {
    display: none;
}

.audio-player-track {
    flex: 1;
    height: 12px;
}

.audio-player-time {
    font-size: 13px;
    font-weight: 700;
    color: var(--ink);
    flex-shrink: 0;
}

/* ------------------------------------------------------------------- */
/* Day tile — week strip (home) & progress calendar, shared vocabulary   */
/* ------------------------------------------------------------------- */

.day-tile {
    width: 52px;
    height: 64px;
    border-radius: var(--radius-tile);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    background: var(--surface-2);
    color: var(--ink);
}

.day-tile-dow {
    font-size: 11px;
    font-weight: 600;
    color: var(--ink-2);
}

.day-tile-num {
    font-size: 16px;
    font-weight: 700;
}

.day-tile-icon {
    height: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.day-tile-half {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: linear-gradient(90deg, var(--green-border) 50%, transparent 50%);
}

.day-tile-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #fff;
}

.day-tile.is-done {
    background: var(--green);
    border: 1.5px solid var(--green-border);
}

.day-tile.is-done .day-tile-dow,
.day-tile.is-done .day-tile-num {
    color: var(--ink);
}

.day-tile.is-partial {
    background: var(--green-soft);
    border: 1.5px solid var(--green-border);
}

.day-tile.is-today {
    background: var(--violet);
    border: 1.5px solid var(--violet);
}

.day-tile.is-today .day-tile-dow,
.day-tile.is-today .day-tile-num {
    color: var(--ink);
}

/* Extra combo, not part of the 6-value contract: today's session already
   completed — a done tile with the violet "today" ring on top. */
.day-tile.is-today-done {
    border: 1.5px solid var(--violet);
}

.day-tile.is-rest {
    background: var(--surface-2);
    color: var(--ink-2);
}

.day-tile.is-missed {
    background: transparent;
    border: 1.5px dashed var(--border-dashed);
    color: var(--ink-disabled);
}

.day-tile.is-future {
    background: var(--surface-2);
    color: var(--border-dashed);
}

/* ------------------------------------------------------------------- */
/* Streak pill                                                          */
/* ------------------------------------------------------------------- */

.streak-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 34px;
    padding: 0 14px;
    border-radius: var(--radius-pill);
    background: var(--surface-2);
}

.streak-pill-flame {
    color: var(--flame);
}

.streak-pill.is-zero .streak-pill-flame {
    color: var(--ink-disabled);
}

.streak-pill-count {
    font-size: 15px;
    font-weight: 800;
}

.streak-pill-label {
    font-size: 13px;
    color: var(--ink-2);
}

/* ------------------------------------------------------------------- */
/* Forms                                                                */
/* ------------------------------------------------------------------- */

.field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.field label {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-2);
}

.input {
    height: 52px;
    border-radius: var(--radius-tile);
    border: 1.5px solid var(--border);
    background: var(--surface);
    padding: 0 16px;
    /* 16px is a hard floor, not a style choice: Safari iOS zooms the viewport
       when a focused field is under 16px, and the user has to pinch back out
       after every tap. */
    font-size: 16px;
    color: var(--ink);
    transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}

.input:focus {
    outline: none;
    border-color: var(--violet);
    box-shadow: 0 0 0 3px var(--violet-soft);
}

/* Invalid state, driven by the same aria-invalid the screen reader reads —
   no extra class to keep in sync with the ARIA attribute. */
.input[aria-invalid="true"] {
    border-color: var(--coral);
}

.input[aria-invalid="true"]:focus {
    border-color: var(--coral-ink);
    box-shadow: 0 0 0 3px var(--coral);
}

.field-error {
    font-size: 13px;
    font-weight: 600;
    color: var(--coral-ink);
}

/* Inline alert: a message that belongs in the flow of a screen, next to what
   it talks about. Distinct from .flash, which is the transient overlay posted
   by a redirect and lives in base.html.twig's fixed container — using .flash
   inline makes two messages on the same screen look like two unrelated
   systems. */
.alert {
    border-radius: var(--radius-tile);
    padding: 12px 14px;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.4;
}

.alert-error {
    background: var(--coral);
    color: var(--coral-ink-dark);
}

.alert-success {
    background: var(--green);
    color: var(--green-ink);
}

/* Password field with a reveal button sitting inside the input's right edge. */
.field-password {
    position: relative;
    display: flex;
}

.field-password .input {
    flex: 1;
    /* Clears the 44px button so a long password never runs under it. */
    padding-right: 48px;
}

.field-password-toggle {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-2);
    background: none;
    border: none;
}

/* Toggle: 52x32, green on / grey off, white 26px knob. */
.toggle {
    width: 52px;
    height: 32px;
    border-radius: var(--radius-pill);
    background: var(--green);
    position: relative;
    flex-shrink: 0;
    transition: background .2s var(--ease);
}

.toggle.is-off {
    background: var(--border);
}

.toggle-knob {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #fff;
    transition: transform .2s var(--ease);
}

.toggle.is-off .toggle-knob {
    transform: none;
}

.toggle:not(.is-off) .toggle-knob {
    transform: translateX(20px);
}

/* Segmented control (e.g. level picker A2/B1/B2). */
.segmented {
    display: flex;
    gap: 8px;
}

.segmented-option {
    flex: 1;
    height: 48px;
    border-radius: var(--radius-pill);
    border: 1.5px solid var(--border);
    background: #fff;
    color: var(--ink);
    font-size: 15px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
}

.segmented-option.is-active {
    background: var(--violet);
    border-color: var(--violet);
}

/* Stacked variant: one option per row, left-aligned — for choices whose labels
   are too long to sit side by side on a phone (e.g. the goal picker). */
.segmented-stack {
    flex-direction: column;
}

.segmented-stack .segmented-option {
    flex: none;
    width: 100%;
    height: auto;
    min-height: 48px;
    justify-content: flex-start;
    text-align: left;
    padding: 12px 18px;
}

.choice-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 44px;
}

.choice-row-label {
    flex: 1;
}

/* ------------------------------------------------------------------- */
/* Tab bar — floating black pill, 4 tabs, hidden in focus mode           */
/* ------------------------------------------------------------------- */

.tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    display: flex;
    justify-content: center;
    gap: 4px;
    width: max-content;
    margin: 0 auto;
    /* "Liquid Glass" material: a translucent dark pane that frosts and
       colour-saturates the content scrolling behind it, a hairline light
       border, and an inner top highlight reading as the glass's lit edge.
       Legibility of the light icons is preserved by the dark tint. */
    background: rgba(23, 23, 28, .6);
    -webkit-backdrop-filter: blur(18px) saturate(180%);
    backdrop-filter: blur(18px) saturate(180%);
    border: 1px solid rgba(255, 255, 255, .1);
    border-radius: var(--radius-pill);
    padding: 7px;
    box-shadow: var(--shadow-tabbar), inset 0 1px 0 rgba(255, 255, 255, .16);
    z-index: 50;
}

.tabbar-tab {
    position: relative;
    /* Clip the tap light-bloom to the pill's rounded shape. */
    overflow: hidden;
    height: 46px;
    border-radius: var(--radius-pill);
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 0 13px;
    color: var(--ink-disabled);
    transition: all .2s var(--ease);
}

/* The selected tab is a brighter glass "lens": near-opaque white with a soft
   specular top edge and a little lift. */
.tabbar-tab.is-active {
    background: rgba(255, 255, 255, .92);
    color: var(--ink);
    padding: 0 16px;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 1px 5px rgba(0, 0, 0, .14);
}

/* Subtle tactile press on tap — a quick squish that springs back via the
   .2s transition above. Neutralised under prefers-reduced-motion. */
.tabbar-tab:active {
    transform: scale(.9);
}

/* Liquid-Glass tap signature: a soft light bloom that swells from the exact
   touch point and dissolves (JS sizes/positions it, see assets/turbo.js). */
.tabbar-glass-ripple {
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
    transform: translate(-50%, -50%) scale(0);
    background: radial-gradient(circle closest-side, rgba(255, 255, 255, .65), rgba(255, 255, 255, 0));
    animation: dsGlassRipple .55s var(--ease) forwards;
}

@keyframes dsGlassRipple {
    to { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

.tabbar-label {
    font-size: 13px;
    font-weight: 700;
    color: var(--ink);
}

/* ------------------------------------------------------------------- */
/* Recorder — signature 6-state widget (speaking, mini-test, pron.)      */
/* ------------------------------------------------------------------- */

.recorder {
    background: var(--surface-2);
    border-radius: var(--radius-card);
    padding: 24px 20px;
}

.recorder-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    text-align: center;
}

/* recorder_controller.js toggles panels via the native `hidden` DOM property.
   An author-origin rule at normal priority (like `.recorder-panel` above)
   always wins over the UA stylesheet's `[hidden] { display: none }`
   regardless of specificity, so without this override every panel would
   render at once instead of only the active one. */
.recorder-panel[hidden] {
    display: none;
}

.recorder-panel-idle {
    padding: 12px 0;
}

.recorder-mic {
    width: 84px;
    height: 84px;
    border-radius: 50%;
    background: var(--ink);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 12px 26px rgba(23, 23, 28, .3);
}

.recorder-mic:active {
    transform: scale(.96);
}

.recorder-hint {
    font-size: 13px;
    color: var(--ink-2);
}

.recorder-waves {
    display: flex;
    align-items: center;
    gap: 3px;
    height: 36px;
}

.recorder-waves span {
    display: inline-block;
    width: 3px;
    height: 100%;
    border-radius: var(--radius-pill);
    background: var(--ink-2);
    animation: dsWave .9s ease-in-out infinite;
}

.recorder-waves span.is-accent {
    background: var(--violet);
}

.recorder-timer {
    font-size: 24px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.recorder-timer small {
    font-size: 14px;
    font-weight: 600;
    color: var(--ink-2);
    margin-left: 4px;
}

.recorder-stop {
    width: 68px;
    height: 68px;
    border-radius: 50%;
    background: var(--violet);
    display: flex;
    align-items: center;
    justify-content: center;
    animation: dsPulse 1.4s ease-in-out infinite;
}

.recorder-stop-square {
    width: 22px;
    height: 22px;
    border-radius: 6px;
    background: var(--ink);
}

.recorder-recorded-actions {
    display: flex;
    gap: 10px;
    width: 100%;
}

.recorder-recorded-actions .btn {
    flex: 1;
}

.recorder-spinner {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 3.5px solid var(--violet-soft);
    border-top-color: var(--violet);
    animation: dsSpin 1s linear infinite;
}

.recorder-uploading-title {
    font-size: 15px;
    font-weight: 700;
}

.recorder-uploading-sub {
    font-size: 13px;
    color: var(--ink-2);
}

.recorder-error-card {
    background: var(--coral);
    color: var(--coral-ink-dark);
    border-radius: var(--radius-tile);
    padding: 14px 16px;
    width: 100%;
}

.recorder-error-message {
    font-size: 14px;
    font-weight: 600;
}

.recorder-offline-card {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--violet-soft);
    color: var(--ink);
    border-radius: var(--radius-tile);
    padding: 14px 16px;
    width: 100%;
    text-align: left;
}

.recorder-tries {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 16px;
}

/* ------------------------------------------------------------------- */
/* Bottom sheet — generic base (Conversation CV2 settings, future reuse)  */
/* ------------------------------------------------------------------- */

/* Set on <html> while any bottom sheet is open (see conversation_config_controller.js
   openSheet/closeSheet): .sheet-overlay is fixed but doesn't lock the page
   behind it on its own — without this, touch-scrolling near the sheet scrolls
   the page instead. */
.is-sheet-open {
    overflow: hidden;
}

.sheet-overlay {
    position: fixed;
    inset: 0;
    background: rgba(23, 23, 28, .35);
    z-index: 60;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    overscroll-behavior: contain;
}

.sheet-overlay[hidden] {
    display: none;
}

.sheet {
    width: 100%;
    max-width: 430px;
    max-height: 85dvh;
    overflow-y: auto;
    background: var(--surface);
    border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
    padding: 14px 20px calc(34px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: 14px;
    animation: dsPop .25s var(--ease) both;
}

.sheet-handle {
    width: 40px;
    height: 5px;
    border-radius: var(--radius-pill);
    background: var(--border);
    margin: 0 auto;
}

.sheet-title {
    font-size: 17px;
    font-weight: 800;
}

/* ------------------------------------------------------------------- */
/* Flash messages                                                       */
/* ------------------------------------------------------------------- */

.flash-messages {
    position: fixed;
    top: calc(12px + env(safe-area-inset-top, 0px));
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    z-index: 100;
    pointer-events: none;
    padding: 0 20px;
}

.flash {
    max-width: 390px;
    width: 100%;
    border-radius: var(--radius-tile);
    padding: 12px 16px;
    font-size: 14px;
    font-weight: 600;
    text-align: center;
    animation: dsPop .3s var(--ease) both;
    pointer-events: auto;
}

.flash-success {
    background: var(--green);
    color: var(--ink);
}

.flash-error {
    background: var(--coral);
    color: var(--coral-ink-dark);
}

/* ------------------------------------------------------------------- */
/* Animations                                                           */
/* ------------------------------------------------------------------- */

@keyframes dsPulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.12); }
}

@keyframes dsWave {
    0%, 100% { transform: scaleY(.35); }
    50% { transform: scaleY(1); }
}

@keyframes dsSpin {
    to { transform: rotate(360deg); }
}

@keyframes dsBlink {
    0%, 100% { opacity: 1; }
    50% { opacity: .45; }
}

@keyframes dsConfetti {
    0% { transform: translateY(-20px) rotate(0deg); opacity: 1; }
    100% { transform: translateY(560px) rotate(540deg); opacity: 0; }
}

@keyframes dsPop {
    0% { transform: scale(.4); opacity: 0; }
    60% { transform: scale(1.12); }
    100% { transform: scale(1); opacity: 1; }
}

@keyframes dsSlideUp {
    from { transform: translateY(24px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
}

/* ------------------------------------------------------------------- */
/* Turbo navigation — progress bar + tab-switch skeleton                */
/* ------------------------------------------------------------------- */

/* Turbo Drive's built-in top progress bar (opt-in nav, see assets/turbo.js). */
.turbo-progress-bar {
    height: 3px;
    background: var(--violet);
}

/* Shimmer placeholder floated over the outgoing screen during a tab visit.
   z-index 20 keeps it under the floating tab bar (z-index 50), so the menu
   stays put while the content area turns into a skeleton. */
.nav-skeleton {
    position: absolute;
    inset: 0;
    z-index: 20;
    background: var(--bg);
    padding: calc(20px + env(safe-area-inset-top, 0px)) 20px calc(var(--tabbar-clearance) + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: 16px;
    overflow: hidden;
}

.nav-skeleton .sk {
    position: relative;
    overflow: hidden;
    background: var(--separator);
    border-radius: var(--radius-tile);
}

.nav-skeleton .sk::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);
    animation: dsShimmer 1.4s ease-in-out infinite;
}

.nav-skeleton .sk-title {
    width: 55%;
    height: 34px;
    border-radius: var(--radius-pill);
}

.nav-skeleton .sk-card {
    height: 128px;
    border-radius: var(--radius-card);
}

.nav-skeleton .sk-row {
    display: flex;
    gap: 10px;
}

.nav-skeleton .sk-tile {
    flex: 1;
    height: 64px;
}

@keyframes dsShimmer {
    to { transform: translateX(100%); }
}
