/* ==========================================================================
   NOVA DRILL CONTROL

   The visual system is a dark, low-chroma shell with one bright "neon"
   accent, taken from tenisdemesa.ar so the app and the site it ships inside
   read as one thing. The four themes are four accent hues over the same dark
   surfaces, which is why a stored theme preference (nova_theme_pref) still
   means something after this change.

   Nothing here is a build artefact. One stylesheet, CSS custom properties per
   theme, no preprocessor. See AGENTS.md.
   ========================================================================== */

/* ==========================================================================
   TYPEFACES
   DM Sans for the interface, JetBrains Mono for anything numeric. Both are
   self-hosted under fonts/ rather than pulled from a CDN: this app's whole
   reason for existing is running with no signal at a table, and a webfont
   that only arrives over the network is a webfont that silently falls back
   exactly when it matters. Both are SIL Open Font License 1.1.
   ========================================================================== */

/* Latin-ext first: the unicode-range on each face is what tells the browser
   which file a character lives in, so order here is cosmetic. */
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 100 1000;
    font-display: swap;
    src: url('../fonts/dm-sans-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                   U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 100 1000;
    font-display: swap;
    src: url('../fonts/dm-sans-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                   U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                   U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                   U+FFFD;
}

@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url('../fonts/jbm-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                   U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url('../fonts/jbm-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                   U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                   U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                   U+FFFD;
}

/* ==========================================================================
   THEME DEFINITIONS

   The neutrals are shared by all four themes on purpose: only the accent
   changes. That is what makes the Settings theme grid readable as a row of
   colour choices rather than four different-looking apps.

   Every text/background pair below clears WCAG AA in every theme, and
   tests/integration.html re-checks the preset and statistics screens on every
   push rather than taking this comment's word for it.

   --danger and --primary are FILL colours: text on them is --on-accent, which
   is a near-black and clears AA on all four accents. White does not (it is
   3.9:1 on the danger red), which is why the deletes are dark-on-red.
   ========================================================================== */

:root {
    --trans-speed: 0.2s;

    --font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                 Roboto, Helvetica, Arial, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo,
                 Consolas, monospace;

    /* Shared neutrals. These are tenisdemesa.ar's own values, rounded to
       8-bit hex: hsl(204 25% 6%) and friends. */
    --bg: #0b1013;            /* the page, and the status bar */
    --surface: #181f23;       /* a card sitting on the page */
    --card-bg: #12181c;       /* a recessed container INSIDE a surface */
    --input-bg: #101619;      /* wells, tracks, anything you type into */
    --text: #f1f3f3;          /* hsl(160 8% 95%) */
    --text-light: #7e918d;    /* hsl(168 8% 53%) - 5.0:1 on --surface */
    --border: #263036;        /* hairline; hsl(204 18% 18%) */
    --header-bg: rgba(11, 16, 19, 0.82);
    --menu-bg: rgba(24, 31, 35, 0.97);
    --shadow: rgba(0, 0, 0, 0.5);

    /* Text that sits ON a --primary / --danger / --spin-* fill. */
    --on-accent: #0b1013;

    /* Semantic fills that are the same in every theme. */
    --danger: #e5484d;
    --danger-ink: #ff8a8f;    /* --danger as TEXT, which needs to be lighter */
    --danger-dim: rgba(229, 72, 77, 0.14);
    --spin-top: #f2a33c;      /* topspin, and only topspin, is amber */
    --spin-back: #3fa9f5;     /* backspin is blue, in every theme */
    --switch-off: #2b383e;    /* a toggle that is off */
    --hover: rgba(255, 255, 255, 0.05);
    --log-bg: #0d1214;

    /* A focus ring and a selected state are boundaries, not decoration, so
       they wear the accent: it is the one colour here with the contrast to
       carry a 3:1 non-text boundary on a near-black surface. */
    --focus: var(--primary);
}

/* Mesa - the tenisdemesa.ar accent, hsl(158 74% 57%). */
[data-theme="standard"] {
    --primary: #40e2a7;
    --primary-ink: #40e2a7;
    --primary-solid-ink: #0b1013;
    --primary-dim: rgba(64, 226, 167, 0.12);
    --accent-fill: #102d23;   /* hsl(158 48% 12%) */
    --accent-line: #1c4535;   /* the site's own accent button border */
    --ring-track: #263036;
    --ring-color: #40e2a7;
}

/* Ocean - the same dark shell on a sky-blue accent. */
[data-theme="ocean"] {
    --primary: #45b8f0;
    --primary-ink: #45b8f0;
    --primary-solid-ink: #07131c;
    --primary-dim: rgba(69, 184, 240, 0.12);
    --accent-fill: #0d2635;
    --accent-line: #16455f;
    --ring-track: #263036;
    --ring-color: #45b8f0;
}

/* Forest - a leaf green, far enough from Mesa's mint to tell apart in the
   theme grid at 26px. */
[data-theme="forest"] {
    --primary: #8fd14f;
    --primary-ink: #8fd14f;
    --primary-solid-ink: #0f1407;
    --primary-dim: rgba(143, 209, 79, 0.12);
    --accent-fill: #1e2a12;
    --accent-line: #3c5220;
    --ring-track: #263036;
    --ring-color: #8fd14f;
}

/* Night - violet. */
[data-theme="night"] {
    --primary: #b98cf5;
    --primary-ink: #b98cf5;
    --primary-solid-ink: #120a1f;
    --primary-dim: rgba(185, 140, 245, 0.13);
    --accent-fill: #241a37;
    --accent-line: #43305e;
    --ring-track: #263036;
    --ring-color: #b98cf5;
}

/* ==========================================================================
   GLOBAL
   ========================================================================== */

body {
    font-family: var(--font-sans);
    background: var(--bg);
    color: var(--text);
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: 100vh;
    margin: 0;
    padding: 0 15px;
    box-sizing: border-box;
    transition: background-color var(--trans-speed), color var(--trans-speed);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    -webkit-tap-highlight-color: transparent;
}

.container {
    width: 100%;
    max-width: 500px;
    position: relative;
    padding-bottom: 80px;
}

/* Every focusable control gets the accent ring. The app is a touch app, so
   this mostly matters for the keyboard cases (Settings, the editor), but a
   visible focus style has to be visible somewhere. */
:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    border-radius: 4px;
}

/* --- HEADER --- */
/* Sticky with a blurred backdrop, the way the rest of the site holds its nav
   in place. It is inside .container, so it sticks to the top of the document
   scroll rather than floating free. */
header {
    display: flex;
    align-items: center;
    justify-content: center;
    /* The title is the ONLY thing in the flow and stays genuinely centred.
       The buttons float over the header's right margin, which is what the
       hamburger always did. Reserving a grid column for them instead - the
       obvious-looking fix - costs the title its width: at 430px a 1fr-auto-1fr
       grid leaves the title 208px for 219px of text, and at 320px it leaves
       98px, so it clips either way. Floating needs no space reserved, because
       the title is short enough to stop short of the buttons on its own. */
    position: sticky;
    top: 0;
    z-index: 90;
    margin: 0 -15px 16px;
    padding: 14px 25px;
    background: var(--header-bg);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border);
}
header h1 {
    min-width: 0;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* The brand is a link to the site the app is published inside, and it looks
   like nothing of the sort: no underline, no link colour. An anchor in the
   middle of the app's own header that painted itself like a link would fight
   the two controls floating beside it. Left `display: inline` on purpose - an
   inline-block would give the h1's `text-overflow: ellipsis` something it
   cannot clip. */
header h1 .brand-link {
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}
/* The narrow form of the name. Below the breakpoint the short one is shown and
   the long one leaves the box entirely - `display: none`, not a smaller font,
   because at 320px the full name and two 40px buttons cannot both fit at any
   readable size.
   The number is arithmetic, not a guess. With the group 86px wide floating
   over a 15px margin, the clear space between a centred name and the group is
   `viewport/2 - 210px`; the full name needs ~16px of that to stop looking
   crowded, so anything under ~453px gets the short form. */
.brand-short { display: none; }
@media (max-width: 460px) {
    .brand-full { display: none; }
    .brand-short { display: inline; }
}

h1 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    color: var(--text);
}

.menu-btn {
    position: absolute;
    /* `right: 8px` with `margin: -8px`, not `right: 0`. The negative margin
       hangs the 8px of padding outside the button so the icon lines up with
       the edge, which is the point of it - but paired with `right: 0` it also
       hung the button itself 8px past the viewport, and the whole page
       scrolled sideways by 8px. This keeps the padding hang and the box
       inside. Found by loading the real index.html, which no check did. */
    right: 8px;
    color: var(--text-light);
    cursor: pointer;
    padding: 8px;
    margin: -8px;
    z-index: 101;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    transition: color var(--trans-speed);
}
.menu-btn:hover { color: var(--primary); }

.menu-btn svg { width: 20px; height: 20px; }

.theme-menu {
    position: absolute;
    /* A child of the STICKY header, so these are measured from the header and
       the dropdown follows it down the page. It was a child of .container
       before, which does not scroll, and the menu opened off the top of the
       screen. `right: 30px` rather than 15px: the header carries a -15px
       margin, so it is 30px wider than the container and the menu has to be
       pushed back to keep its right edge on the same pixel. */
    top: 58px;
    right: 30px;
    width: 210px;
    background: var(--menu-bg);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-radius: 12px;
    box-shadow: 0 16px 40px var(--shadow);
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    z-index: 100;
    border: 1px solid var(--border);
    opacity: 0;
    transform: scale(0.95) translateY(-4px);
    transform-origin: top right;
    pointer-events: none;
    transition: opacity 0.16s, transform 0.16s;
}

.theme-menu.open {
    opacity: 1;
    transform: scale(1) translateY(0);
    pointer-events: auto;
}

.menu-title {
    font-size: 0.65rem;
    color: var(--text-light);
    font-weight: 700;
    padding: 8px 10px 6px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.theme-item {
    padding: 9px 10px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: 10px;
    transition: background var(--trans-speed), color var(--trans-speed);
}

.theme-item:hover {
    background: var(--hover);
    color: var(--primary);
}

.color-preview {
    width: 10px;
    height: 10px;
    border-radius: 3px;
    display: inline-block;
    flex-shrink: 0;
}
.reset-item { color: var(--danger-ink); }
.reset-item:hover { background: var(--danger-dim); color: var(--danger-ink); }

/* --- CONNECTION CARD --- */
.settings-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 14px;
    margin-bottom: 20px;
    transition: background-color var(--trans-speed);
}

.btn-connect {
    width: 100%;
    padding: 12px;
    border: 1px solid transparent;
    border-radius: 9px;
    font-family: inherit;
    font-weight: 700;
    font-size: 0.9rem;
    letter-spacing: 0.01em;
    cursor: pointer;
    background: var(--primary);
    color: var(--primary-solid-ink);
    margin-bottom: 16px;
    transition: background var(--trans-speed), border-color var(--trans-speed),
                color var(--trans-speed), transform 0.1s;
}
.btn-connect:active { transform: scale(0.99); }

/* Connected swaps the solid fill for the accent pill the rest of the site
   uses for "this is the one that is on". */
.btn-connect.connected {
    background: var(--accent-fill);
    border-color: var(--accent-line);
    color: var(--primary);
}

.btn-connect.connecting { opacity: 0.75; cursor: progress; }
.btn-connect:disabled { cursor: not-allowed; }
@keyframes connect-pulse { 0%, 100% { opacity: 1 } 50% { opacity: 0.55 } }
.btn-connect.connecting { animation: connect-pulse 1.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
    .btn-connect.connecting { animation: none; }
}

.control-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 10px;
    transition: opacity 0.3s;
}

.controls-row { display: flex; gap: 8px; margin-bottom: 4px; }

.control-pill {
    background: var(--input-bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 9px 10px;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 0;
}

/* The site's micro-label: ~10px, uppercase, wide tracking, muted. Everything
   labelled the same way in this file copies this.

   0.65rem is 10.4px and it is a floor, not a preference: this app gets read at
   a table, and tests/integration.html fails the build on any preset-UI text
   under 10px. Do not introduce a smaller step without a reason that survives
   that check. */
.control-pill label,
.setting-label {
    font-size: 0.65rem;
    color: var(--text-light);
    margin-bottom: 3px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    line-height: 1.4;
}

input[type="number"] {
    border: none;
    font-family: var(--font-mono);
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--primary);
    text-align: center;
    width: 100%;
    background: transparent;
    -moz-appearance: textfield;
    appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"]:focus { outline: none; }
input[type="number"]:disabled { opacity: 0.5; cursor: not-allowed; color: var(--text-light); }

/* --- SEGMENTED SWITCH (mode, spin, language) --- */
.mode-switch {
    display: flex;
    background: var(--input-bg);
    border: 1px solid var(--border);
    border-radius: 9px;
    padding: 3px;
    width: 100%;
    margin-bottom: 10px;
}

.mode-option {
    flex: 1;
    text-align: center;
    padding: 8px;
    font-size: 0.72rem;
    font-weight: 600;
    border-radius: 6px;
    cursor: pointer;
    color: var(--text-light);
    /* box-sizing is not global in this file, and 8px of padding either side of
       a 13px line box lands at 29px - under the 32px floor the rest of the app
       holds to. */
    box-sizing: border-box;
    min-height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--trans-speed);
}
.mode-option.active {
    background: var(--accent-fill);
    color: var(--primary);
}

/* The language picker reuses .mode-switch inside a settings row, which is a
   flex ROW with the description beside it. The standalone switch is width:100%
   with a bottom margin for the control card, and both of those squeeze the
   description into a 70px column on a phone. Here it is a compact control that
   keeps its own width and gives the row its space back. */
.settings-row .mode-switch {
    width: auto;
    margin-bottom: 0;
    flex: 0 0 auto;
}
.settings-row .mode-option {
    flex: 0 0 auto;
    padding: 8px 14px;
    white-space: nowrap;
    /* 30px, inherited from the standalone switch, is under the 40px tap target
       the rest of Settings is held to. A language picker is a setting people
       tap once and set, but it is still a button, so it gets the same floor.
       The app is not globally border-box, so without this the 8px padding
       lands outside the min-height and the option comes out 56px tall. */
    box-sizing: border-box;
    min-height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* --- DIFFICULTY --- */
.level-select { display: flex; justify-content: center; gap: 10px; margin-bottom: 10px; }

.lvl-dot {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--input-bg);
    border: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-light);
    cursor: pointer;
    transition: all var(--trans-speed);
}

.lvl-dot.active {
    background: var(--accent-fill);
    border-color: var(--accent-line);
    color: var(--primary);
}

.section-label {
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--text-light);
    margin-bottom: 8px;
    padding-left: 2px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

/* --- TABS --- */
.tabs-container {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 8px;
    margin-bottom: 16px;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 6px;
}

.tab-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5px;
    padding: 11px 5px;
    border-radius: 9px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--text-light);
    font-family: inherit;
    font-weight: 600;
    font-size: 0.7rem;
    cursor: pointer;
    transition: all var(--trans-speed);
    text-align: center;
}

.tab-btn.active {
    background: var(--accent-fill);
    border-color: var(--accent-line);
    color: var(--primary);
}

.icon-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px;
    width: 9px;
    height: 9px;
}
.icon-dot { background-color: currentColor; border-radius: 2px; opacity: 0.45; }
.tab-btn.active .icon-dot { opacity: 1; }

/* --- DRILL LIST --- */
.drill-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 10px;
}
.hidden { display: none !important; }

.btn-drill {
    width: 100%;
    min-height: 56px;
    padding: 0 14px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 14px;

    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    color: var(--text);
    position: relative;
    transition: transform 0.1s, background-color var(--trans-speed),
                border-color var(--trans-speed), color var(--trans-speed);
    user-select: none;
    -webkit-user-select: none;
    cursor: pointer;
}

.btn-drill:active { transform: scale(0.99); }
.btn-drill.running {
    border-color: var(--accent-line);
    background: var(--accent-fill);
    color: var(--primary);
}
.btn-drill:disabled { opacity: 0.5; cursor: not-allowed; background: var(--card-bg); }

.btn-drill .drill-icon {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3px;
    width: 13px;
    height: 13px;
    flex-shrink: 0;
    opacity: 0.55;
}
.btn-drill .drill-icon .d-dot {
    background-color: currentColor;
    border-radius: 2px;
    transition: background-color var(--trans-speed);
}
.btn-drill.running .drill-icon .d-dot { background-color: var(--primary); }

.btn-drill span {
    flex: 1;
    text-align: left;
    font-size: 0.92rem;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mark-random {
    position: static;
    width: 22px;
    height: 22px;
    border: 1.5px solid var(--danger);
    color: var(--danger-ink);
    border-radius: 50%;
    font-family: var(--font-mono);
    font-size: 0.65rem;
    font-weight: 800;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    line-height: 1;
    flex-shrink: 0;
}

.drill-grab-handle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 100%;
    cursor: grab;
    color: var(--text-light);
    opacity: 0.35;
    font-size: 1.3rem;
    margin-left: 2px;
    border-left: 1px solid var(--border);
    padding-left: 8px;
    flex-shrink: 0;
    transition: opacity 0.2s;
}
.drill-grab-handle:hover { opacity: 1; color: var(--primary); }

.btn-drill.dragging {
    opacity: 0.5;
    background: var(--card-bg);
    border: 1px dashed var(--text-light);
}

/* --- STATUS, FOOTER, LOG --- */
#status-text {
    text-align: center;
    font-size: 0.65rem;
    color: var(--primary);
    font-weight: 700;
    height: 18px;
    margin-bottom: 6px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.log-box {
    margin-top: 10px;
    background: var(--log-bg);
    color: var(--primary);
    padding: 12px;
    border-radius: 10px;
    font-family: var(--font-mono);
    font-size: 0.65rem;
    height: 120px;
    overflow-y: auto;
    display: none;
    border: 1px solid var(--border);
    line-height: 1.5;
}
.log-box div {
    margin-bottom: 2px;
    border-bottom: 1px solid var(--border);
    padding-bottom: 2px;
}

.footer-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 18px;
    font-size: 0.65rem;
    color: var(--text-light);
    font-weight: 600;
    letter-spacing: 0.04em;
    padding: 0 2px;
}

/* Visually hidden, still announced. Used to name a control's destination
   without lengthening the line it sits on. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* The running totals, as a way into the statistics screen.
   The negative margin gives it the 40px tap target the rest of the app holds
   to without adding 40px of height to a one-line footer. */
.stats-link {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: 40px;
    margin: -8px 0 -8px -6px;
    padding: 0 10px 0 6px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 9px;
    font-family: var(--font-mono);
    font-size: 0.65rem;
    font-weight: 600;
    color: var(--text-light);
    cursor: pointer;
    transition: color 0.2s, background 0.2s, border-color 0.2s;
}
.stats-link:hover {
    color: var(--primary);
    background: var(--hover);
    border-color: var(--border);
}
.stats-link-icon { width: 14px; height: 14px; flex-shrink: 0; opacity: 0.8; }

/* --- SWAP BUTTON (between balls) --- */
.swap-zone {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 2px 0;
    margin: -8px 0 8px 0;   /* pulls the two halves together */
    position: relative;
    z-index: 5;
}
.swap-zone::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--border);
    transform: translateX(-50%);
    z-index: -1;
}

.btn-swap {
    background: var(--input-bg);
    border: 1px solid var(--border);
    color: var(--text-light);
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    transition: all 0.2s ease;
}
.btn-swap:hover {
    background: var(--accent-fill);
    border-color: var(--accent-line);
    color: var(--primary);
    transform: scale(1.08);
}

/* The "+ new drill" button at the foot of a custom tab. Same look as the
   swap button it reuses, just a size up. ui.js applies this class rather than
   writing an inline cssText, which is what kept the border pinned to one
   theme's colour. */
.btn-drill-add {
    width: 40px;
    height: 40px;
    font-size: 1.2rem;
    color: var(--primary);
    border-color: var(--accent-line);
}

.checkbox-wrapper { display: flex; align-items: center; gap: 6px; }
#stats-display { font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

/* --- TOAST (z-index 300, over every modal) --- */
#toast {
    visibility: hidden;
    min-width: 250px;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    text-align: center;
    border-radius: 10px;
    padding: 14px 18px;
    position: fixed;
    z-index: 300;
    left: 50%;
    bottom: 30px;
    transform: translateX(-50%);
    font-size: 0.85rem;
    opacity: 0;
    box-shadow: 0 10px 30px var(--shadow);
    transition: opacity 0.3s, bottom 0.3s;
}
#toast.show { visibility: visible; opacity: 1; bottom: 50px; }

/* ==========================================================================
   MODALS
   Every modal is a .modal-overlay at z-index 200. A nested one has to come
   LATER in index.html to stack on top.
   ========================================================================== */

.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s;
}
.modal-overlay.open { opacity: 1; pointer-events: auto; }

.modal {
    background: var(--surface);
    width: 95%;
    max-width: 400px;
    max-height: 90vh;
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 18px;
    box-shadow: 0 24px 60px var(--shadow);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.modal-header {
    display: flex;
    align-items: center;
    gap: 15px;
    margin-bottom: 14px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border);
    width: 100%;
}

.modal-title {
    font-weight: 500;
    color: var(--text);
    font-size: 1.02rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    min-width: 0;
    text-align: left;
    margin-right: 10px;
}

.modal-close {
    font-size: 1.4rem;
    color: var(--text-light);
    cursor: pointer;
    line-height: 1;
    padding: 0 4px;
    transition: color var(--trans-speed);
}
.modal-close:hover { color: var(--text); }

.modal-body { overflow-y: auto; flex: 1; padding-right: 5px; }

/* --- EDITOR MODAL HEADER (two rows) --- */
#editor-modal .modal-header {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
}

#editor-modal .header-top {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    min-width: 0;
}

#editor-modal .title-container {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
    cursor: pointer;
    margin-right: 10px;
}

#editor-drill-name {
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

#editor-drill-edit-icon {
    font-size: 0.95rem;
    opacity: 0.6;
    flex-shrink: 0;   /* the edit affordance must never disappear */
    color: var(--primary);
}

#editor-modal .header-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}

.btn-header-share { margin-right: 0; }
.random-toggle-container { margin-right: 0; }

/* ==========================================================================
   DRILL EDITOR
   ========================================================================== */

.ball-group {
    border: 1px solid var(--border);
    border-radius: 10px;
    margin-bottom: 12px;
    overflow: hidden;
    background: var(--bg);
    transition: opacity 0.3s;
}
.ball-group.inactive { opacity: 0.5; border-style: dashed; }

.group-title {
    background: var(--card-bg);
    padding: 9px 12px;
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    border-bottom: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* --- toggles --- */
.ball-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}

.toggle-switch {
    position: relative;
    width: 32px;
    height: 18px;
    background: var(--switch-off);
    border-radius: 15px;
    transition: background 0.2s;
}
.toggle-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    background: var(--text-light);
    border-radius: 50%;
    transition: transform 0.2s, background 0.2s;
}

.ball-toggle.active .toggle-switch { background: var(--primary); }
.ball-toggle.active .toggle-switch::after { transform: translateX(14px); background: var(--on-accent); }

.random-toggle-container {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}
.random-toggle-container .toggle-label {
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}
.random-toggle-container input { display: none; }
.random-toggle-container input:checked + .toggle-switch { background: var(--primary); }
.random-toggle-container input:checked + .toggle-switch::after { transform: translateX(14px); background: var(--on-accent); }

.btn-add-opt {
    background: transparent;
    border: 1px solid var(--accent-line);
    color: var(--primary);
    width: 24px;
    height: 24px;
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 1.1rem;
    font-weight: 400;
    line-height: 1;
    padding: 0 0 2px 0;
    transition: all 0.2s;
}
.btn-add-opt:hover { background: var(--accent-fill); transform: scale(1.08); }

.option-card { padding: 10px; border-bottom: 1px solid var(--border); position: relative; }
.option-card:last-child { border-bottom: none; }

.option-label {
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--text-light);
    margin-bottom: 5px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    display: block;
}

/* --- the speed / spin / depth / placement grid --- */
.editor-grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 6px;
    margin-bottom: 10px;
}

.editor-field {
    display: flex;
    flex-direction: column;
    background: var(--input-bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 5px;
    min-width: 0;
}

.field-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1px;
    padding: 0 4px;
    gap: 4px;
}

.field-header label {
    font-size: 0.65rem;
    color: var(--text-light);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    white-space: nowrap;
}

.field-header .range-hint {
    font-family: var(--font-mono);
    font-size: 0.65rem;
    color: var(--text-light);
    opacity: 0.8;
    white-space: nowrap;
}

.editor-field input {
    border: none;
    background: transparent;
    font-family: var(--font-mono);
    font-size: 0.9rem;
    color: var(--text);
    font-weight: 700;
    text-align: center;
    width: 100%;
    padding: 4px 0;
    -moz-appearance: textfield;
}
.editor-field input:focus { outline: none; color: var(--primary); }

/* --- topspin / backspin --- */
.spin-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
    padding: 0 4px;
}

.spin-label {
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.spin-capsule {
    display: flex;
    background: var(--input-bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 2px;
}

.sc-opt {
    padding: 5px 12px;
    font-size: 0.65rem;
    font-weight: 700;
    border-radius: 6px;
    cursor: pointer;
    color: var(--text-light);
    transition: all 0.2s;
}

/* The neutral selected state first, then the two semantic fills. editor.js
   and presetUi.js also set `background` inline on the selected .sc-opt; both
   write var(--spin-top) / var(--spin-back), so the token and the markup agree. */
.sc-opt.active {
    background: var(--card-bg);
    color: var(--text);
}
.sc-opt:first-child.active { background: var(--spin-top); color: var(--on-accent); }
.sc-opt:last-child.active { background: var(--spin-back); color: var(--on-accent); }

/* --- per-ball actions --- */
.card-actions { display: flex; gap: 6px; margin-top: 6px; }

.btn-action {
    flex: 1;
    padding: 8px;
    border-radius: 8px;
    border: 1px solid transparent;
    font-family: inherit;
    font-size: 0.68rem;
    font-weight: 700;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s, border-color 0.2s, color 0.2s;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text);
}

.btn-act-test { background: var(--input-bg); border-color: var(--border); }
.btn-act-test:hover { border-color: var(--accent-line); color: var(--primary); }
.btn-act-test:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-act-clone { background: var(--spin-back); color: var(--on-accent); }
.btn-act-clone:hover { opacity: 0.88; }

.btn-act-del { background: var(--danger); color: var(--on-accent); }
.btn-act-del:hover { opacity: 0.88; }
.btn-act-del:disabled { opacity: 0.3; cursor: not-allowed; }

.btn-header-share {
    background: transparent;
    border: none;
    color: var(--primary);
    cursor: pointer;
    /* 7px of padding around a 20px glyph is a 34px target. At 5px it came out
       at 30px, under the 32px floor everything else here is held to. */
    padding: 7px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.2s, color 0.2s;
    flex-shrink: 0;
}
.btn-header-share:hover { transform: scale(1.1); color: var(--text); }
.btn-header-share svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* ==========================================================================
   MODAL FOOTER
   ========================================================================== */

.modal-footer {
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
    display: flex;
    gap: 8px;
    align-items: stretch;
}

.btn-modal {
    padding: 11px 8px;
    border-radius: 8px;
    border: 1px solid transparent;
    background: transparent;
    font-family: inherit;
    font-weight: 700;
    cursor: pointer;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    display: flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap;
    transition: background var(--trans-speed), border-color var(--trans-speed),
                color var(--trans-speed), transform 0.1s;
}
.btn-modal:active { transform: scale(0.97); }

.btn-delete-drill {
    background: var(--danger);
    color: var(--on-accent);
    width: 44px;
    flex: 0 0 44px;
    padding: 0;
}
.btn-delete-drill svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.btn-delete-drill:disabled { opacity: 0.3; cursor: not-allowed; }

.btn-test-combo {
    background: transparent;
    border-color: var(--accent-line);
    color: var(--primary);
    flex: 1.2;
}
.btn-test-combo:hover { background: var(--accent-fill); }

.btn-save-as {
    background: transparent;
    border-color: var(--border);
    color: var(--text);
    flex: 1.2;
}
.btn-save-as:hover { background: var(--hover); }

.btn-save {
    background: var(--primary);
    color: var(--primary-solid-ink);
    flex: 1.5;
}
.btn-save:hover { opacity: 0.9; }

/* ==========================================================================
   RUN OVERLAY
   ========================================================================== */

#run-ui {
    display: none;
    flex-direction: column;
    align-items: center;
    width: 100%;
    justify-content: center;
    text-align: center;
}
.modal-overlay.open #run-ui { display: flex; }

.countdown-circle { position: relative; width: 190px; height: 190px; margin-bottom: 28px; }
.countdown-circle svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.track { fill: none; stroke: var(--ring-track); stroke-width: 6; }
.progress {
    fill: none;
    stroke: var(--ring-color);
    stroke-width: 6;
    stroke-dasharray: 565;
    stroke-dashoffset: 0;
    stroke-linecap: round;
}

.center-text {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.count-val {
    font-family: var(--font-mono);
    font-size: 4.2rem;
    font-weight: 700;
    color: var(--primary);
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
.status-label {
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    margin-top: 8px;
}

.run-controls { display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 10px; }
.btn-run-ctrl {
    width: 100%;
    padding: 14px;
    border-radius: 9px;
    border: 1px solid var(--border);
    font-family: inherit;
    font-weight: 700;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: transform 0.1s;
}
.btn-run-ctrl:active { transform: scale(0.97); }
.btn-pause { background: var(--input-bg); color: var(--text); }
.btn-stop-run {
    background: var(--danger);
    color: var(--on-accent);
    border-color: transparent;
    padding: 26px;     /* doubled - this is the one you hit in a hurry */
    font-size: 1.3rem;
    margin-top: 6px;
}
.pulse-anim { animation: pulse 1s infinite; }
@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } }

/* --- last played --- */
.btn-drill.last-played { border-color: var(--accent-line); }
.btn-drill.last-played::after {
    content: '';
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--primary);
}

/* ==========================================================================
   BALL PRESETS
   A library of named ball recipes that can be dropped into a drill with their
   placement/depth variations expanded. See js/presets.js.
   ========================================================================== */

/* "Add from preset", used at the top of the drill editor body and again under
   the "+" at the end. One class for both: they are the same action, so they
   look the same. */
.preset-entry {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 11px;
    margin: 0 0 14px 0;
    border: 1px dashed var(--accent-line);
    border-radius: 10px;
    background: var(--accent-fill);
    color: var(--primary);
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    cursor: pointer;
    transition: background 0.2s, transform 0.1s;
}
.preset-entry:hover { background: var(--primary-dim); }
.preset-entry:active { transform: scale(0.99); }
.preset-entry svg { flex-shrink: 0; width: 16px; height: 16px; fill: none; stroke: currentColor; }

/* --- the picker --- */
.preset-card {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg);
    padding: 11px;
    margin-bottom: 8px;
}
.preset-card-top { display: flex; align-items: flex-start; gap: 4px; }
.preset-name { font-weight: 700; font-size: 0.95rem; color: var(--text); flex: 1; word-break: break-word; }
.preset-sub { font-size: 0.7rem; color: var(--text-light); font-weight: 600; margin-top: 3px; }
.preset-edit-btn {
    background: transparent;
    border: none;
    color: var(--text-light);
    cursor: pointer;
    width: 34px;
    height: 34px;            /* >=32px touch target */
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: color 0.2s;
}
.preset-edit-btn:hover { color: var(--primary); }
.preset-edit-btn svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.preset-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 9px; }
.preset-chip {
    font-size: 0.68rem;
    font-weight: 600;
    padding: 3px 8px;
    border-radius: 20px;
    background: var(--input-bg);
    border: 1px solid var(--border);
    color: var(--text);
    white-space: nowrap;
}
.preset-chip b { color: var(--primary); font-weight: 700; }
.preset-chip.placement b { color: var(--text); }
.preset-chip-axis { font-size: 0.65rem; color: var(--text-light); }

.preset-actions { display: flex; gap: 6px; margin-top: 10px; }
.btn-preset-add {
    flex: 1;
    padding: 9px 4px;
    border-radius: 8px;
    border: 1px solid var(--accent-line);
    background: transparent;
    color: var(--primary);
    font-family: inherit;
    font-size: 0.66rem;
    font-weight: 700;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
    transition: background 0.2s, transform 0.1s;
}
.btn-preset-add:hover { background: var(--accent-fill); }
.btn-preset-add:disabled { color: var(--text-light); border-color: var(--border); background: transparent; }
.btn-preset-add:active { transform: scale(0.97); }
.btn-preset-add.solid { background: var(--primary); border-color: var(--primary); color: var(--primary-solid-ink); }
.btn-preset-add:disabled { opacity: 0.55; cursor: not-allowed; }

.preset-empty { text-align: center; color: var(--text-light); font-size: 0.82rem; padding: 24px 10px; line-height: 1.6; }

.btn-preset-new { background: transparent; border-color: var(--accent-line); color: var(--primary); flex: 1; }
.btn-preset-new:hover { background: var(--accent-fill); }

.btn-delete-preset { background: var(--danger); color: var(--on-accent); width: 44px; flex: 0 0 44px; padding: 0; }
.btn-delete-preset svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.btn-delete-preset[hidden] { display: none; }

/* --- the preset editor --- */
.preset-name-input {
    box-sizing: border-box;   /* width:100% + padding + border would overflow right */
    width: 100%;
    padding: 10px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-weight: 700;
    font-size: 1rem;
    text-align: center;
    margin-bottom: 12px;
}
.preset-name-input:focus { outline: none; border-color: var(--accent-line); color: var(--primary); }

/* One variation axis: a list of label + value rows. */
.axis-block {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg);
    padding: 9px;
    margin-bottom: 10px;
}
.axis-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.axis-title { font-size: 0.65rem; font-weight: 700; color: var(--text); text-transform: uppercase; letter-spacing: 0.1em; }
.axis-hint { font-size: 0.66rem; color: var(--text-light); font-weight: 500; margin-top: -2px; margin-bottom: 8px; line-height: 1.45; }
.axis-row { display: flex; align-items: center; gap: 4px; margin-bottom: 5px; }
.axis-row:last-child { margin-bottom: 0; }
.axis-row input {
    box-sizing: border-box;   /* the app is not globally border-box */
    border: 1px solid var(--border);
    background: var(--input-bg);
    color: var(--text);
    border-radius: 7px;
    padding: 7px 4px;
    font-family: inherit;
    font-weight: 600;
    text-align: center;
}
.axis-row input:focus { outline: none; border-color: var(--accent-line); color: var(--primary); }
.axis-row .axis-label-input { flex: 1 1 0; min-width: 0; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; }
.axis-row .axis-value-input { width: 64px; min-width: 0; flex: 0 0 64px; font-family: var(--font-mono); font-size: 0.85rem; }
.axis-del-btn {
    background: transparent;
    border: none;
    color: var(--text-light);
    cursor: pointer;
    width: 34px;
    height: 34px;                                /* >=32px touch target */
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 34px;                              /* no shrink, no overflow */
    transition: color 0.2s;
}
.axis-del-btn:hover { color: var(--danger-ink); }
.axis-del-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.btn-axis-add {
    width: 100%;
    margin-top: 6px;
    padding: 10px;
    border-radius: 7px;
    border: 1px dashed var(--border);
    background: transparent;
    color: var(--text-light);
    font-family: inherit;
    font-size: 0.66rem;
    font-weight: 700;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.btn-axis-add:hover { border-color: var(--accent-line); color: var(--primary); }
.axis-standard-btn {
    width: 100%;
    margin-top: 6px;
    padding: 10px;
    border-radius: 7px;
    border: 1px solid var(--border);
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.66rem;
    font-weight: 700;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    transition: border-color 0.2s, color 0.2s;
}
.axis-standard-btn:hover { border-color: var(--accent-line); color: var(--primary); }

/* "Save this ball as a preset" on an option card */
.btn-act-preset { flex: 0 0 42px; background: var(--input-bg); color: var(--text); border-color: var(--border); padding: 8px 0; }
.btn-act-preset:hover { color: var(--primary); }
.btn-act-preset svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* ==========================================================================
   SETTINGS SCREEN
   A full screen reached from the hamburger menu. The menu keeps only the
   drill actions; everything else lives here.
   ========================================================================== */

.container.screen-hidden { display: none; }

.settings-view {
    position: fixed;
    inset: 0;
    z-index: 150;                 /* below modals (200) so overlays still win */
    background: var(--bg);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.settings-view[hidden] { display: none; }
.settings-view.active { display: block; }

.settings-shell {
    width: 100%;
    max-width: 500px;
    margin: 0 auto;
    padding: 0 16px 40px 16px;
}

.settings-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 0 12px 0;
    position: sticky;
    top: 0;
    background: var(--header-bg);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    z-index: 1;
    border-bottom: 1px solid var(--border);
}
.settings-header h1 { margin: 0; font-size: 1.05rem; font-weight: 500; color: var(--text); }

.settings-back {
    display: flex;
    align-items: center;
    gap: 5px;
    min-height: 40px;
    padding: 8px 14px 8px 10px;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
    transition: transform 0.1s, border-color 0.2s, color 0.2s;
}
.settings-back:active { transform: scale(0.97); }
.settings-back:hover { border-color: var(--accent-line); color: var(--primary); }
.settings-back svg { flex-shrink: 0; }

.settings-section { margin-top: 20px; }
.settings-section-title {
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-light);
    margin-bottom: 8px;
    padding-left: 2px;
}

/* --- one row per setting --- */
.settings-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    margin-bottom: 8px;
}
.settings-row-main { flex: 1; min-width: 0; }
.settings-row-title { font-size: 0.88rem; font-weight: 600; color: var(--text); }
.settings-row-desc { font-size: 0.72rem; color: var(--text-light); margin-top: 3px; line-height: 1.4; }
.danger-text { color: var(--danger-ink); font-weight: 600; }

.settings-btn {
    flex-shrink: 0;
    min-height: 40px;
    min-width: 74px;
    padding: 10px 16px;
    border-radius: 8px;
    border: 1px solid var(--accent-line);
    background: transparent;
    color: var(--primary);
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    transition: background 0.2s, transform 0.1s;
}
.settings-btn:hover { background: var(--accent-fill); }
.settings-btn:active { transform: scale(0.97); }
.settings-btn.danger { border-color: var(--danger); color: var(--danger-ink); }
.settings-btn.danger:hover { background: var(--danger-dim); }

/* --- theme picker --- */
.theme-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.theme-card {
    display: flex;
    align-items: center;
    gap: 9px;
    min-height: 54px;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.84rem;
    font-weight: 600;
    cursor: pointer;
    position: relative;
    transition: border-color 0.2s, transform 0.1s, background 0.2s;
}
.theme-card:active { transform: scale(0.98); }
.theme-card:hover { border-color: var(--accent-line); }
.theme-card.active { border-color: var(--accent-line); background: var(--accent-fill); color: var(--primary); }
.theme-swatch {
    width: 24px;
    height: 24px;
    border-radius: 6px;
    flex-shrink: 0;
    border: 1px solid var(--border);
}
.theme-name { flex: 1; text-align: left; }
.theme-check {
    display: none;
    color: var(--primary);
    font-weight: 800;
    font-size: 0.9rem;
    line-height: 1;
}
.theme-card.active .theme-check { display: block; }

.settings-foot {
    margin-top: 26px;
    text-align: center;
    font-size: 0.68rem;
    color: var(--text-light);
}
/* The domain in that line is the one place outside the About box that names the
   site, so it goes there. `inline-block` plus padding is what makes an 11px-tall
   strip of micro-label text into a real tap target; the negative margin hangs
   the padding outside so it does not lengthen the line or shift the text
   either side of it. */
.settings-foot .foot-link {
    display: inline-block;
    padding: 7px 3px;
    margin: 0 -3px;
    border-radius: 7px;
    color: var(--primary);
    font-family: var(--font-mono);
    text-decoration: none;
}
.settings-foot .foot-link:hover,
.settings-foot .foot-link:focus-visible { text-decoration: underline; }

/* --- About --- */
.about-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    padding: 4px 0 2px;
}
.about-title { margin: 0; font-size: 1.1rem; font-weight: 700; color: var(--text); }
.about-version {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 0.68rem;
    color: var(--text-light);
}

.about-link, .about-original {
    display: inline-block;
    text-decoration: none;
    font-weight: 700;
    border-radius: 9px;
    transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.about-link {
    margin-top: 6px;
    padding: 11px 22px;
    background: var(--primary);
    color: var(--primary-solid-ink);
    font-size: 0.78rem;
    letter-spacing: 0.04em;
}
.about-link:hover { opacity: 0.9; }

/* The site the app is published inside. A link rather than another button:
   it is a reference, not the main action, and three equal-weight buttons in a
   row would flatten the hierarchy. */
.about-site {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    width: 100%;
    margin-top: 4px;
    padding: 11px 14px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--input-bg);
    text-decoration: none;
    transition: background 0.2s, border-color 0.2s;
}
.about-site:hover { background: var(--hover); border-color: var(--accent-line); }
.about-site-name {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--primary);
    letter-spacing: 0.01em;
}
.about-site-blurb {
    font-size: 0.72rem;
    line-height: 1.45;
    color: var(--text-light);
}

.about-credits {
    margin-top: 10px;
    padding-top: 14px;
    width: 100%;
    border-top: 1px solid var(--border);
}
.about-thanks {
    margin: 0 0 10px;
    font-size: 0.74rem;
    line-height: 1.55;
    color: var(--text-light);
}
.about-original {
    padding: 8px 14px;
    border: 1px solid var(--accent-line);
    color: var(--primary);
    font-size: 0.7rem;
    letter-spacing: 0.04em;
}
.about-original:hover { background: var(--accent-fill); }

/* ==========================================================================
   STATISTICS SCREEN
   A full screen above Settings. Reached from a Settings row; the back button
   returns to Settings rather than straight to the drill list, because that is
   where the row you tapped lives.

   The chart is hand-rolled divs. No dependencies is the point of this project:
   a 14-bar chart does not need a charting library, and pulling one in would
   cost more than the whole rest of the UI.
   ========================================================================== */

.stats-view {
    position: fixed;
    inset: 0;
    z-index: 160;                 /* above Settings (150), below modals (200) */
    background: var(--bg);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.stats-view[hidden] { display: none; }
.stats-view.active { display: block; }

.stats-live {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    margin-bottom: 14px;
    border: 1px solid var(--accent-line);
    border-radius: 10px;
    background: var(--accent-fill);
    color: var(--primary);
    font-size: 0.75rem;
    font-weight: 600;
}

/* --- four numbers across the top --- */
.stat-tiles {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-bottom: 4px;
}
.stat-tile {
    padding: 12px 10px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    text-align: center;
    min-width: 0;                 /* stop a long duration forcing the grid wide */
}
.stat-value {
    font-family: var(--font-mono);
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--primary);
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
    word-break: break-word;
}
.stat-label {
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-light);
    margin-top: 4px;
}

.stats-section { margin-top: 20px; }

/* --- 14-day bar chart --- */
.chart {
    display: flex;
    align-items: flex-end;
    gap: 4px;
    height: 110px;
    padding: 10px 8px 0 8px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
}
.chart-col {
    flex: 1 1 0;
    min-width: 0;                 /* 14 columns must fit a narrow phone */
    height: 100%;
    display: flex;
    align-items: flex-end;
}
.chart-bar {
    width: 100%;
    background: var(--primary);
    border-radius: 3px 3px 0 0;
    min-height: 2px;
}
.chart-bar.empty { background: var(--card-bg); }
.chart-labels { display: flex; gap: 4px; padding: 4px 8px 0 8px; }
.chart-label {
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 0.65rem;
    color: var(--text-light);
    font-weight: 600;
}

/* --- most played drills --- */
.rank-list {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    padding: 4px 10px;
}
.rank-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 0;
    border-bottom: 1px solid var(--border);
}
.rank-row:last-child { border-bottom: none; }
.rank-pos {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--text-light);
    min-width: 14px;
}
.rank-name {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text);
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.rank-bar {
    flex: 0 0 34%;
    max-width: 110px;
    height: 6px;
    border-radius: 3px;
    background: var(--input-bg);
    overflow: hidden;
}
.rank-bar > span {
    display: block;
    height: 100%;
    border-radius: 3px;
    background: var(--primary);
}
.rank-count {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--primary);
    min-width: 20px;
    text-align: right;
}

/* --- the session log --- */
.session-list {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    padding: 2px 10px;
    margin-bottom: 10px;
}
.session-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
}
.session-row:last-child { border-bottom: none; }
.session-main { flex: 1 1 0; min-width: 0; }
.session-when {
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.session-meta {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    color: var(--text-light);
    font-weight: 500;
    margin-top: 3px;
}
.session-meta .dot { margin: 0 2px; }
.session-del {
    flex: 0 0 40px;
    width: 40px;
    height: 40px;                 /* >=40px touch target */
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 8px;
    color: var(--text-light);
    cursor: pointer;
    transition: color 0.2s, background 0.2s;
}
.session-del:hover { color: var(--danger-ink); background: var(--danger-dim); }
.session-del svg { fill: none; stroke: currentColor; }

.stats-clear {
    width: 100%;
    min-height: 44px;
    padding: 11px;
    border: 1px solid var(--danger);
    border-radius: 9px;
    background: transparent;
    color: var(--danger-ink);
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    transition: background 0.2s, transform 0.1s;
}
.stats-clear:hover { background: var(--danger-dim); }
.stats-clear:active { transform: scale(0.98); }

.stats-foot-note {
    text-align: center;
    font-size: 0.66rem;
    color: var(--text-light);
    margin-top: 10px;
    line-height: 1.5;
}

.stats-empty {
    text-align: center;
    color: var(--text-light);
    font-size: 0.82rem;
    padding: 40px 14px;
    line-height: 1.6;
}

/* ==========================================================================
   INSTALLED APP
   The app installs to the home screen and runs in standalone display mode
   with no browser chrome. The layout problem that creates is the cutout:
   Android and iOS report their status bar and notch as "safe area insets",
   and viewport-fit=cover in index.html lets the page draw straight through
   them, which is what makes it look like a real app. This is the other half
   of that trade - putting the content back inside the area actually visible.

   Every inset is 0 on a device with no cutout, so all of this collapses to
   padding. It is deliberately NOT wrapped in a
   @media (display-mode: standalone) block: the same phone in a browser tab
   has the same cutout, and the padding is right in both.
   ========================================================================== */

body {
    padding-top: max(0px, env(safe-area-inset-top));
    padding-right: max(15px, env(safe-area-inset-right));
    padding-bottom: max(20px, env(safe-area-inset-bottom));
    padding-left: max(15px, env(safe-area-inset-left));
}

/* The header is sticky, so it carries the top inset itself rather than
   letting body's padding scroll away underneath it. */
header { padding-top: calc(14px + env(safe-area-inset-top)); }

/* Settings and Statistics are position:fixed inset:0, so they live outside
   body's padding box and need the top inset of their own - their sticky
   header is what would otherwise sit under the status bar. */
.settings-shell { padding-top: env(safe-area-inset-top); }

/* Modals are centred by flexbox, and centring treats the top of the viewport
   as usable. border-box is required and is not global in this file: without
   it, padding on a width:100%/height:100% element would grow the overlay
   past the screen and the editor would gain a horizontal scrollbar. */
.modal-overlay {
    box-sizing: border-box;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
}

/* The toast is pinned near the bottom, which on a gesture-navigation phone is
   exactly where the home indicator is drawn. */
#toast.show { bottom: calc(50px + env(safe-area-inset-bottom)); }

/* ==========================================================================
   The AI assistant panel
   ==========================================================================

   A full screen in the free z-index band between Statistics (160) and the
   modals (200), so the preset sheet and the drill editor still open over it.
   It reuses .settings-shell / .settings-header / .settings-back verbatim, the
   way #stats-view reuses them, rather than growing a parallel set of chrome.

   The `hidden` / `.active` pair is load-bearing and is the same trap
   #stats-view and #settings-view both fell into once: .ai-view declares no
   `display` of its own, so openAiView() has to remove `hidden` and
   closeAiView() has to put it back, or the panel keeps covering the drill
   list underneath it. See open/close in js/aiUi.js. */

.ai-view {
    position: fixed;
    inset: 0;
    z-index: 170;                 /* above Statistics (160), below modals (200) */
    background: var(--bg);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.ai-view[hidden] { display: none; }
.ai-view.active { display: block; }

/* --- the transcript ------------------------------------------------------ */

/* Interim speech and the model's in-flight prose are dimmed but present: the
   user has to be able to see it is working, not broken. */
.ai-transcript {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 14px;
}
.ai-empty {
    padding: 18px 14px;
    border: 1px dashed var(--border);
    border-radius: 12px;
    color: var(--text-light);
    font-size: 0.8rem;
    line-height: 1.5;
    text-align: center;
}
.ai-msg {
    padding: 10px 13px;
    border-radius: 12px;
    font-size: 0.86rem;
    line-height: 1.45;
    max-width: 88%;
    /* Model prose is arbitrary text: it can be a 500-character run with no
       spaces. overflow-wrap is what stops one long token taking the panel
       sideways, which the integration suite checks for. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.ai-msg.user {
    align-self: flex-end;
    background: var(--accent-fill);
    color: var(--primary);
    border: 1px solid var(--accent-line);
}
.ai-msg.model {
    align-self: flex-start;
    background: var(--card-bg);
    border: 1px solid var(--border);
    color: var(--text);
}
.ai-msg.interim { opacity: 0.6; font-style: italic; }
.ai-msg.error {
    background: transparent;
    border-color: var(--danger);
    color: var(--danger-ink);
}

/* Which tier answered, shown on the message that did it. */
.ai-tier {
    display: inline-block;
    margin-top: 5px;
    font-size: 0.65rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-light);
}

/* --- the tool trace ------------------------------------------------------ */

.ai-trace {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 14px;
}
.ai-trace-line {
    display: flex;
    align-items: baseline;
    gap: 7px;
    font-size: 0.72rem;
    color: var(--text-light);
    line-height: 1.4;
}
.ai-trace-line::before {
    content: '\203A';           /* › */
    color: var(--primary);
    flex-shrink: 0;
}

/* --- the draft preview --------------------------------------------------- */

.ai-draft {
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--card-bg);
    padding: 12px;
    margin-bottom: 14px;
}
.ai-draft-name {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 3px;
    /* A model-generated name is arbitrary text and can be very long. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.ai-step {
    padding: 10px 0;
    border-top: 1px solid var(--border);
}
.ai-step:first-of-type { border-top: none; padding-top: 6px; }
.ai-step-label {
    font-size: 0.82rem;
    color: var(--text);
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.ai-step-meta {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.7rem;
    color: var(--text-light);
    margin-top: 3px;
}
/* "from the preset X" or "generated" - the honest label either way. */
.ai-chip {
    display: inline-block;
    margin-top: 6px;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.65rem;
    font-weight: 600;
    border: 1px solid var(--accent-line);
    background: var(--accent-fill);
    color: var(--primary);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-chip.generated {
    background: transparent;
    color: var(--text-light);
    border-color: var(--border);
}

/* --- the action bar ------------------------------------------------------ */

.ai-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
}
.ai-btn {
    flex: 1 1 auto;
    min-height: 44px;
    padding: 11px 14px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
    /* Colours come from here, never from an inline style in JS, so they keep
       following the theme - see AGENTS.md, Design. */
}
.ai-btn.primary {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-accent);
}
.ai-btn.danger {
    background: transparent;
    border-color: var(--danger);
    /* Destructive LABELS use --danger-ink, which clears AA as text. White on
       --danger is 3.9:1. */
    color: var(--danger-ink);
}
.ai-btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* --- the composer -------------------------------------------------------- */

.ai-composer {
    display: flex;
    gap: 8px;
    align-items: stretch;
    margin-top: 6px;
}
.ai-input {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 44px;
    padding: 11px 13px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.86rem;
}
.ai-input:focus {
    outline: none;
    border-color: var(--accent-line);
}
.ai-icon-btn {
    flex: 0 0 auto;
    width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--input-bg);
    color: var(--text);
    cursor: pointer;
}
.ai-icon-btn svg { flex-shrink: 0; }
.ai-icon-btn.listening {
    border-color: var(--danger);
    color: var(--danger-ink);
}
.ai-icon-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* The header button that opens the panel, next to the hamburger. The wrapper
   is what keeps the two controls a pair; the header centres its children, so
   without it the mic would sit against the title and the menu would drift. */
.ai-open-wrap {
    /* Floated over the header's right margin, like the hamburger always was,
       with `right: 15px` because the header carries a -15px margin and is
       therefore 15px wider than the viewport on that side. */
    position: absolute;
    right: 15px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    z-index: 101;
}
/* Inside the wrapper the hamburger is a normal flex item again. Its
   `position: absolute; right: 0` was for when it was the header's only
   floating control, and it would pull it out of the group and back to the
   header's right edge - on top of the mic. */
.ai-open-wrap .menu-btn {
    position: static;
    margin: 0;
}
.ai-open-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    margin-right: 2px;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: var(--input-bg);
    color: var(--text);
    cursor: pointer;
}
.ai-open-btn:hover { border-color: var(--accent-line); color: var(--primary); }

/* --- the AI settings form -----------------------------------------------
   The first place in the app with a real form in it, so it brings its own
   row type rather than being bent into `.settings-row`. Everything is a
   custom property, so it follows all four themes for free. */

.ai-field {
    padding: 12px 0;
    border-top: 1px solid var(--border);
}
.ai-field:first-of-type { border-top: none; padding-top: 4px; }
.ai-field-label {
    display: block;
    font-size: 0.65rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-light);
    font-weight: 700;
    margin-bottom: 6px;
}
.ai-input-row {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ai-input, .ai-select {
    width: 100%;
    box-sizing: border-box;
    min-height: 44px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.85rem;
}
.ai-input:focus, .ai-select:focus { outline: none; border-color: var(--accent-line); }
.ai-input.mono { font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; }
/* A key is a secret: the field is a password type, and this is the only place
   its length is ever shown. */
.ai-key-state {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    font-size: 0.72rem;
    color: var(--text-light);
}
.ai-btn-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.ai-note {
    font-size: 0.72rem;
    color: var(--text-light);
    line-height: 1.45;
    margin-top: 8px;
}
.ai-note.warn {
    color: var(--danger-ink);
    border-left: 2px solid var(--danger);
    padding-left: 9px;
}
/* A test result line: green when it worked, danger-ink when it did not. Both
   are text colours, not fills. */
.ai-test-result { font-size: 0.75rem; margin-top: 8px; line-height: 1.4; }
.ai-test-result.ok { color: var(--primary); }
.ai-test-result.bad { color: var(--danger-ink); }
.ai-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 0;
    border-top: 1px solid var(--border);
}

/* A question the model needs answered before it can go on. It is part of the
   conversation, not a modal: interrupting a tool call with a dialog is worse
   than a row of buttons under the message that asked. */
.ai-ask {
    border: 1px solid var(--accent-line);
    background: var(--accent-fill);
    border-radius: 12px;
    padding: 12px;
    margin-bottom: 12px;
}
.ai-ask-question {
    font-size: 0.86rem;
    color: var(--text);
    line-height: 1.45;
    margin-bottom: 10px;
    /* Model-authored text: arbitrary, and potentially a 500-character run. */
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* --- the IA tab ----------------------------------------------------------
   A drill the assistant built is not a saved drill: it is in nova_ai_drills,
   not in custom_drills, and it is not in the shared CSV until it is moved. So
   the row says so with a chip, and carries its own one-tap move buttons
   rather than borrowing the drag-and-drop the custom sets use - these are not
   in `drillOrder` and there is nothing to reorder against. */

.ia-row { margin-bottom: 8px; }

.mark-ai {
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    padding: 2px 5px;
    border-radius: 5px;
    background: var(--accent-fill);
    color: var(--primary);
    border: 1px solid var(--accent-line);
    flex-shrink: 0;
}
.mark-source { font-size: 0.8rem; flex-shrink: 0; opacity: 0.8; }

.ia-row-actions {
    display: flex;
    gap: 6px;
    padding: 0 2px 2px 2px;
}
.ia-move {
    flex: 1 1 0;
    min-width: 0;
    min-height: 32px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--input-bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.72rem;
    font-weight: 700;
    cursor: pointer;
}
.ia-move:hover { border-color: var(--accent-line); color: var(--primary); }
/* A destructive LABEL, so --danger-ink and not white on --danger. */
.ia-move.danger { color: var(--danger-ink); border-color: var(--border); }
.ia-move.danger:hover { border-color: var(--danger); }

.ia-clear { margin: 15px auto 10px; }
.ia-empty {
    padding: 18px 14px;
    border: 1px dashed var(--border);
    border-radius: 12px;
    color: var(--text-light);
    font-size: 0.8rem;
    line-height: 1.5;
    text-align: center;
}

/* --- armed microphone ----------------------------------------------------
   Arming the mic is a visible, deliberate state, not a blinking icon. The
   filled style is the "the microphone is OPEN right now" case, which is a
   different thing from "we are listening to this one utterance". */
.ai-icon-btn.armed {
    border-color: var(--danger);
    background: var(--accent-fill);
    color: var(--danger-ink);
}
.ai-icon-btn.armed::after {
    /* A slow pulse, so it reads as live from across a table. Decorative, so
       it is aria-hidden by virtue of being a pseudo-element. */
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 12px;
    border: 1px solid var(--danger);
    opacity: 0.5;
    animation: ai-pulse 1.8s ease-in-out infinite;
    pointer-events: none;
}
.ai-icon-btn { position: relative; }

@keyframes ai-pulse {
    0%, 100% { opacity: 0.5; transform: scale(1); }
    50%      { opacity: 0;   transform: scale(1.12); }
}
/* Respect the OS setting rather than animating at somebody who asked us not to. */
@media (prefers-reduced-motion: reduce) {
    .ai-icon-btn.armed::after { animation: none; opacity: 0.4; }
}

.ai-armed-note {
    margin-top: 8px;
    padding: 8px 11px;
    border: 1px solid var(--danger);
    border-radius: 10px;
    background: var(--card-bg);
    /* A destructive LABEL, so --danger-ink and not white on --danger. */
    color: var(--danger-ink);
    font-size: 0.75rem;
    line-height: 1.4;
    text-align: center;
}

/* The "model not set up" row.
   Neutral, not a danger border: nothing is wrong, and the on-device tier
   really does still work. The armed note above is a privacy warning about
   something live RIGHT NOW, which is why it earns a red one - reusing that
   treatment here would make a missing key look like an error state. */
.ai-setup-note {
    margin-top: 8px;
    padding: 10px 11px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--card-bg);
    color: var(--text-light);
    font-size: 0.75rem;
    line-height: 1.45;
}

.ai-setup-text {
    /* Both halves of the message matter, and the second one is what the row is
       for, so the button gets its own block and is not left as a trailing word
       on the last line. */
    margin-bottom: 9px;
}

.ai-setup-cta {
    display: block;
    width: 100%;
    /* 44px, the floor the suite enforces on every control in this panel. It is
       not the 32px of the preset UI: this is the only way out of an
       unconfigured assistant, and it is on screen while the mic may be armed. */
    min-height: 44px;
    padding: 0 12px;
    border: 1px solid var(--primary);
    border-radius: 10px;
    background: transparent;
    /* --primary is a FILL colour, never text - see Design in AGENTS.md. */
    color: var(--primary);
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
}

.ai-setup-cta:active {
    background: var(--card-bg);
}

/* ==========================================================================
   Settings, as a list of groups you open
   ==========================================================================

   Settings had grown to 2.8 phone-screens of 29 rows, and the AI section alone
   was SIXTEEN flat siblings with nothing to say which four were the text model
   and which three were behaviour toggles. A column that long is not a list, it
   is a wall.

   So each section is a native <details>: a 52px summary you can actually hit
   with a thumb, and the contents only when you want them. Native rather than a
   div + class, because it brings keyboard support, the open state in the a11y
   tree, and no JavaScript to keep working. The chevron is CSS; the open state
   is persisted in JS ONLY so that a re-render - which happens on every
   presets-updated / stats-updated / locale-changed - does not snap the screen
   shut under the user who just opened something.

   `.settings-section-title` is deliberately NOT reused: statsUi.js uses it too,
   and restyling it there would be a change nobody asked for. */

.settings-group {
    margin-top: 10px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--card-bg);
    overflow: hidden;
}
.settings-group:first-of-type { margin-top: 0; }

.settings-group-head {
    display: flex;
    align-items: center;
    gap: 10px;
    /* min-height is the CONTENT box here - box-sizing is not global in this
       file - so 44 + 20 of padding is a 64px row. Comfortably above every
       tap-target minimum in the app, and eight of them fit one phone screen. */
    min-height: 44px;
    padding: 10px 14px;
    cursor: pointer;
    list-style: none;
    -webkit-tap-highlight-color: transparent;
}
.settings-group-head::-webkit-details-marker { display: none; }
.settings-group-head::marker { content: ''; }
.settings-group-head:active { background: var(--accent-fill); }

.settings-group-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text);
    /* A title long enough to wrap must not push the note off the row. */
    overflow-wrap: anywhere;
}
/* The live status, so a CLOSED group still says what is in it. That is the
   difference between a list you can scan and a list you have to open eight
   times to find out. */
.settings-group-note {
    flex: 0 1 auto;
    max-width: 45%;
    font-size: 0.72rem;
    color: var(--text-light);
    text-align: right;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.settings-group-chev {
    flex-shrink: 0;
    color: var(--text-light);
    transition: transform var(--trans-speed);
}
.settings-group[open] .settings-group-chev { transform: rotate(90deg); }
.settings-group[open] .settings-group-note { opacity: 0.55; }

.settings-group-body { padding: 0 14px 14px; }
/* The old section spacing is the group's job now. */
.settings-group-body > .settings-section:first-child,
.settings-group-body > .settings-section:first-of-type { margin-top: 0; }
.settings-group-body .ai-note:first-child { margin-top: 0; }

/* --- sub-groups inside the AI section --------------------------------------
   The sixteen siblings become three things: what the model is, what the voice
   is, and when it is allowed to listen. */
.ai-subgroup { margin: 18px 0 2px; }
.ai-subgroup:first-child { margin-top: 6px; }
.ai-subgroup-title {
    display: block;
    font-size: 0.65rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-light);
    font-weight: 700;
    margin-bottom: 2px;
}
/* The divider between sub-groups, so the eye can chunk sixteen rows into
   three. The first one does not get a rule above it. */
.ai-subgroup + .ai-subgroup { border-top: 1px solid var(--border); }

@media (prefers-reduced-motion: reduce) {
    .settings-group-chev { transition: none; }
}

/* ==========================================================================
   Boxes that are wider than their parent
   ==========================================================================

   `box-sizing` is content-box in this file by design (see `.modal-overlay`:
   it is applied there explicitly for the same reason as here), so any element
   with a width AND horizontal padding renders `padding` wider than it asked
   for. That is invisible until the parent is exactly the viewport width, and
   then the right-hand side is simply off-screen and unreachable - the page
   does not even scroll, because the overflowing box is inside a
   `position: fixed` overlay, which is why `documentElement.scrollWidth` stays
   honest while the content is clipped.

   Measured on a 430px phone, all of these were over:
     .settings-shell   +32px   Settings, Statistics and the assistant panel
     .about-site       +30px   the About box, clipped by .modal's overflow
     .mode-switch       +8px   the reps/time switch, on the drill list
     input[type=number] +4px   the count and pause fields

   Fixed by applying `border-box` to the offenders themselves rather than
   globally: a global reset at this point would re-lay-out every element in
   the app, and the one place it has already been applied is a single line with
   a comment explaining why. `tools/check-app.mjs` now audits every element on
   the real page for "border box wider than its parent's content box", so the
   next one of these is caught by a check instead of by somebody's phone. */

.settings-shell,
.about-site,
.mode-switch,
input[type="number"],
.control-pill,
.drill-list > * {
    box-sizing: border-box;
}

/* The share-code and drill-name fields had no width rule at all, so they took
   the UA default of ~20 characters - which at their inline 1.6rem and 1.2rem
   font sizes is 350px and 262px, inside a modal that has 255px of content on
   a 320px phone. `.modal` is `overflow: hidden`, so the right-hand end of both
   fields was cut off with no way to scroll to it. `width: 100%` plus
   border-box is what every other full-width input in this app already does. */
#dl-code,
#save-name {
    width: 100%;
    box-sizing: border-box;
}
