/* ============================================================
   LearnWords AI -- Components
   Reusable building blocks: layout, buttons, cards, modals,
   lists, badges, animations. Reads only design tokens from
   tokens.css; relies on reset.css for box-sizing and scrollbars.
   ============================================================ */
/* --- App Container --- */
.tg-app {
    width: 100%;
    min-height: 100vh;
    position: relative;
}

/* --- Page Header ---
   Sticky title row. Exposes its own height as --page-header-h so dependant
   sticky elements (e.g. .controls-bar) can chain off it without hardcoding 60px. */
.page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    /* Right padding reserves room for the fixed top-right "+" FAB so right-aligned
       header actions (CSV export, etc.) never sit underneath it. */
    padding: 16px var(--fab-inset-right) 8px 16px;
    position: sticky;
    top: 0;
    z-index: 10;
    background: var(--bg);
    backdrop-filter: blur(20px);
    --page-header-h: 60px;
}

/* Once stuck at top:0 the header sits BEHIND the status bar on hosts drawn edge-to-edge (the
   installed PWA, iOS) — the content's --safe-top padding only moves its resting position, not
   where it sticks. Paint the inset strip above it in the same background so text scrolls under
   an opaque bar instead of under the clock. Zero-height where there is no inset, so nothing
   changes in Telegram / desktop browsers, or on the MAUI Android host (there the activity is
   padded out of the bars natively, so --safe-top is pinned to 0). */
.page-header::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(-1 * var(--safe-top));
    height: var(--safe-top);
    background: inherit;
    pointer-events: none;
}

/* The action pill grows on tablets/desktop (46px segments @ right:18px) — widen the
   reservation to match --fab-inset-right's own desktop step. */
@media (min-width: 768px) {
    .page-header { padding-right: calc(var(--safe-right) + 118px); }
}

.page-header h1 {
    font-size: var(--type-title-l-size);
    font-weight: 700;
    letter-spacing: -0.3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    min-width: 0;
}

/* On a phone the reservation (108px) plus a pair of header actions leaves the title almost
   nothing — "Мой словарь" collapsed to "Мой…". Let the row wrap instead: the title keeps its
   natural width on line one (still clear of the pill), the actions drop to line two.
   Must come AFTER the base h1 rule — same specificity, so source order decides. */
@media (max-width: 480px) {
    .page-header { flex-wrap: wrap; row-gap: 8px; }
    .page-header h1 { flex: 1 0 auto; }
}

.header-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
}

/* Dictionary: place the CSV export right next to the "My Dictionary" title
   instead of pushing it to the far right under the "+" FAB (#122). Scoped to
   .dictionary-page so other pages keep their right-aligned header actions. */
.dictionary-page .page-header {
    justify-content: flex-start;
}
.dictionary-page .page-header h1 {
    flex: 0 1 auto;
}

/* On the narrowest phones the CSV label can crowd the title — keep just the icon. */
@media (max-width: 380px) {
    .btn-export {
        padding: 8px 12px;
    }
    .btn-export .csv-label {
        display: none;
    }
}

/* --- Buttons --- */
.btn-export {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 16px;
    border: none;
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    color: var(--accent);
    font-size: var(--type-body-size);
    font-weight: 600;
    cursor: pointer;
    transition: all var(--transition);
    border: 1px solid var(--card-border);
}

.btn-export:hover {
    background: var(--card-hover);
}

.btn-export:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-export .icon {
    font-size: calc(16px * var(--ui-scale, 1)); /* skip(c): icon glyph */
}

/* --- Tab Bar --- */
.tab-bar {
    display: flex;
    gap: 4px;
    padding: 0 12px;
    margin-bottom: 8px;
}

.tab {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 16px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--type-body-size);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition);
    position: relative;
}

.tab::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 20%;
    right: 20%;
    height: 3px;
    border-radius: 3px;
    background: var(--accent);
    opacity: 0;
    transform: scaleX(0);
    transition: all var(--transition);
}

.tab.active {
    color: var(--accent);
    background: rgba(var(--accent-rgb), 0.08);
}

.tab.active::after {
    opacity: 1;
    transform: scaleX(1);
}

.tab .badge {
    font-size: var(--type-caption-size);
    font-weight: 700;
    padding: 2px 7px;
    border-radius: var(--radius-pill);
    background: rgba(var(--on-surface-rgb), 0.06);
    color: var(--hint);
}

.tab.active .badge {
    background: rgba(var(--accent-rgb), 0.15);
    color: var(--accent);
}

/* --- Phrase type sub-filter --- */
.phrase-type-bar {
    display: flex;
    gap: 6px;
    padding: 0 12px 8px;
}

.phrase-type-btn {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 6px 12px;
    border: 1px solid transparent;
    border-radius: 20px;
    background: rgba(var(--on-surface-rgb), 0.04);
    color: var(--text-secondary);
    font-size: var(--type-body-s-size);
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition);
}

.phrase-type-btn.active {
    background: rgba(var(--accent-rgb), 0.12);
    border-color: rgba(var(--accent-rgb), 0.3);
    color: var(--accent);
}

.badge-small {
    font-size: var(--type-caption-size);
    font-weight: 700;
    padding: 1px 5px;
    border-radius: var(--radius);
    background: rgba(var(--on-surface-rgb), 0.06);
    color: var(--hint);
}

.phrase-type-btn.active .badge-small {
    background: rgba(var(--accent-rgb), 0.15);
    color: var(--accent);
}

/* --- Controls Bar ---
   Sticks just below .page-header. Uses --page-header-h (set by the header itself
   to its own height) instead of a hardcoded 60px — survives a wrapping h1 or any
   future header height change without overlapping. */
.controls-bar {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px 16px;
    background: var(--bg);
    position: sticky;
    top: var(--page-header-h, 60px);
    z-index: 9;
}

/* Wrap onto a second row on narrow screens (≤480px) so bulk-actions + sort + filter
   never get squished into a single overflowing strip. */
.controls-main {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    row-gap: 10px;
}

.selection-group {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-right: 12px;
    border-right: 1px solid var(--card-border);
    flex-shrink: 0;
}

/* Once the row wraps, drop the divider — it'd float in mid-air without a sibling. */
@media (max-width: 540px) {
    .selection-group {
        border-right: none;
        padding-right: 0;
    }
}

.bulk-actions-wrapper {
    display: flex;
    align-items: center;
    min-width: 80px;
    /* Reserve space for buttons */
}

.bulk-actions-mini {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: 4px;
    animation: fadeIn 0.15s ease-out;
}

.mini-action-btn {
    width: 32px;
    height: 32px;
    border: none;
    border-radius: var(--radius);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    /* Buttons don't inherit color from their parent in the UA stylesheet, so without
       this the SVG icon (stroke="currentColor") falls back to the system "ButtonText"
       and becomes invisible on dark themes. */
    color: var(--text);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--type-body-l-size);
    transition: all var(--transition);
}

.mini-action-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.mini-action-btn.mastery-mixed {
    opacity: 0.5;
    background: rgba(var(--success-rgb), 0.05);
}

.mini-action-btn.mastery-all {
    color: var(--success);
    background: rgba(var(--success-rgb), 0.10);
    border-color: rgba(var(--success-rgb), 0.30);
}

.mini-action-btn.mini-action-btn-danger {
    color: var(--destructive);
    border-color: rgba(var(--destructive-rgb), 0.30);
}

.mini-action-btn.mini-action-btn-danger:hover {
    background: rgba(var(--destructive-rgb), 0.12);
}

.sort-wrapper {
    position: relative;
    flex: 1;
    min-width: 160px;
    /* Slightly increased for stability */
    max-width: 220px;
}

.controls-top {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
    justify-content: flex-end;
    /* Align right controls */
}

.filters-overlay {
    position: fixed;
    inset: 0;
    background: transparent;
    z-index: 8;
}

/* The filters drawer is composed of N filter-group cells + a filter-actions cell.
   Wide screens lay them in a single row; ≤540px stacks each group on its own row
   so the labels stay legible and the apply/reset row doesn't collide with selects. */
.filters-panel {
    display: flex;
    align-items: flex-end;
    gap: 12px;
    background: var(--bg-secondary);
    padding: 12px 14px;
    border-radius: var(--radius);
    margin-top: 8px;
    box-shadow: var(--shadow);
    border: 1px solid var(--card-border);
    animation: slideDown 0.2s ease-out;
    position: relative;
    z-index: 15;
    flex-wrap: wrap;
}

.filter-actions {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
    margin-left: auto;
}

@media (max-width: 540px) {
    .filters-panel {
        flex-direction: column;
        align-items: stretch;
    }
    .filter-actions {
        margin-left: 0;
        justify-content: flex-end;
    }
}

.filter-group {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1;
    min-width: 110px;
}

.filter-group label {
    font-size: var(--type-caption-size);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--hint);
}


.btn-apply-filters {
    padding: 8px 20px;
    background: var(--accent);
    color: var(--button-text);
    border: none;
    border-radius: var(--radius-sm);
    font-size: var(--type-body-size);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    height: 38px;
    transition: all var(--transition);
}

.btn-apply-filters:hover {
    opacity: 0.9;
    transform: translateY(-1px);
}

.btn-reset-filters {
    padding: 8px 16px;
    background: transparent;
    color: var(--text-secondary, #707579);
    border: 1px solid var(--card-border, rgba(0, 0, 0, 0.1));
    border-radius: var(--radius-sm);
    font-size: var(--type-body-size);
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
    height: 38px;
    transition: all var(--transition);
}

.btn-reset-filters:hover {
    background: var(--card-hover);
    color: var(--text);
}

.select-all-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 10px 16px;
    background: rgba(var(--accent-rgb), 0.1);
    border-bottom: 1px solid var(--card-border);
    font-size: var(--type-body-size);
    color: var(--text);
    animation: fadeIn 0.2s ease;
}

.btn-link {
    background: none;
    border: none;
    color: var(--accent);
    text-decoration: underline;
    font-weight: 600;
    cursor: pointer;
    font-size: var(--type-body-size);
}

.btn-link:hover {
    color: var(--text);
}

@media (max-width: 480px) {
    .tab {
        padding: 10px 8px;
        gap: 4px;
    }

    .tab-icon {
        font-size: var(--type-body-size);
    }
}

.btn-filter.active {
    color: var(--accent);
    background: rgba(var(--accent-rgb), 0.1);
}

/* A value-bearing filter is applied (#730). Accent fill + a dot badge so the funnel reads as
   "filtered" at a glance even with the panel closed. Coexists with .active (panel open). */
.btn-filter.applied {
    color: var(--accent);
    background: rgba(var(--accent-rgb), 0.1);
    border-color: rgba(var(--accent-rgb), 0.45);
    position: relative;
}
.btn-filter.applied::after {
    content: "";
    position: absolute;
    top: 3px;
    right: 3px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
    border: 1.5px solid var(--card-bg);
}

.btn-filter {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border-radius: 12px;
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    color: var(--text-secondary);
    font-size: var(--type-body-size);
    font-weight: 500;
    cursor: pointer;
}

.select-all-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--type-body-size);
    color: var(--text-secondary);
    white-space: nowrap;
    cursor: pointer;
}

/* --- Custom Checkbox --- */
.custom-checkbox {
    appearance: none;
    -webkit-appearance: none;
    width: 20px;
    height: 20px;
    /* High contrast border that works on both dark and light bg */
    border: 2px solid #888;
    border-radius: 6px;
    background: var(--card-bg);
    cursor: pointer;
    position: relative;
    transition: all var(--transition);
    flex-shrink: 0;
}

@media (prefers-color-scheme: light) {
    .custom-checkbox {
        border-color: #999;
    }
}

:root.dark .custom-checkbox {
    border-color: #666;
}

@media (prefers-color-scheme: light) {
    :root:not(.dark) .custom-checkbox {
        border-color: #abb0b5;
    }
}

.custom-checkbox:checked {
    background: var(--accent);
    border-color: var(--accent);
}

.custom-checkbox:checked::after {
    content: '';
    position: absolute;
    left: 6px;
    top: 2px;
    width: 5px;
    height: 10px;
    border: solid white;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.mastery-check {
    color: var(--success);
    font-size: calc(18px * var(--ui-scale, 1)); /* skip(c): check glyph in fixed button */
    font-weight: bold;
}

.mastery-check.inactive {
    opacity: 0.3;
    filter: grayscale(1);
}

/* --- Word List --- */
.word-list,
.phrase-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 0 8px;
    /* Ensure the list doesn't have its own scroll fixed height */
}

/* Sentinel for infinite scroll */
#infinite-scroll-sentinel,
#infinite-scroll-sentinel-phrases {
    height: 40px;
    margin-top: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
}

/* --- Word Item --- */
.word-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    padding: 13px 14px;
    border-radius: var(--radius);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    box-shadow: var(--shadow);
    transition: border-color var(--transition), box-shadow var(--transition);
    animation: fadeIn 0.3s ease;
    gap: 8px;
}

/* Whole-row background hover was removed (caused blinking); a border tint is stable. */
.word-item:hover { border-color: rgba(var(--on-surface-rgb), 0.18); }

.word-item.selected {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.07);
    box-shadow: 0 6px 18px -10px rgba(var(--accent-rgb), 0.6);
}

.word-item-left {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
    overflow: hidden;
}

.word-checkbox {
    width: 18px;
    height: 18px;
    accent-color: var(--accent);
    flex-shrink: 0;
}

.word-info {
    flex: 1;
    min-width: 0;
    cursor: pointer;
    padding: 2px 4px;
    border-radius: var(--radius);
    transition: background var(--transition);
}

.word-info:hover {
    background: var(--card-hover);
}

/* Word row main text — body size (14px) with row-level emphasis via weight, NOT size.
   The lw-type-title-s class (15px) used to be applied in markup and the rule below
   was bumping it to 17px — the row read as a card header rather than a list item.
   See VOCAB_PROGRESS_GUIDE.md §5. */
.word-text {
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    font-weight: 600;
    letter-spacing: -0.005em;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.word-translation {
    font-size: var(--type-body-s-size);
    line-height: var(--type-body-s-line);
    color: var(--text-secondary);
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.word-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 4px;
    flex-wrap: wrap;
}

.freq-badge,
.stage-badge,
.difficulty-badge,
.source-word {
    font-size: var(--type-body-s-size);
    font-weight: 600;
    padding: 3px 10px;
    border-radius: 999px;
    white-space: nowrap;
    line-height: 1.5;
}

.freq-badge {
    color: var(--text-secondary);
    background: rgba(var(--on-surface-rgb), 0.04);
}

.freq-badge.dim {
    opacity: 0.5;
}

.stage-badge.srs {
    color: var(--success);
    background: rgba(76, 175, 80, 0.12);
}

.stage-badge.learning {
    color: var(--accent);
    background: rgba(var(--accent-rgb), 0.12);
}

.difficulty-badge {
    color: var(--warning);
    background: rgba(255, 152, 0, 0.12);
}

.source-word {
    color: var(--hint);
    background: rgba(var(--on-surface-rgb), 0.04);
}

.word-item-actions,
.phrase-item-actions {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.action-btn {
    width: 36px;
    height: 36px;
    border: none;
    border-radius: 50%;
    background: transparent;
    /* Buttons don't inherit color from their parent in the UA stylesheet, so without
       this the SVG icon (stroke="currentColor") falls back to the system "ButtonText"
       and becomes invisible on dark themes. */
    color: var(--text-secondary);
    font-size: calc(18px * var(--ui-scale, 1)); /* skip(c): icon glyph in fixed button */
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--transition);
}

.action-btn:hover {
    background: rgba(var(--on-surface-rgb), 0.08);
    color: var(--text);
}

.action-btn.audio {
    color: var(--accent);
    opacity: 0.8;
}

.action-btn.audio:hover {
    opacity: 1;
    background: rgba(var(--accent-rgb), 0.1);
}

.action-btn.delete {
    color: var(--destructive);
}

.action-btn.delete:hover {
    background: rgba(var(--destructive-rgb), 0.12);
    color: var(--destructive);
}

.action-btn.learned:hover {
    background: rgba(var(--success-rgb), 0.10);
}

/* --- Phrase Item --- */
.phrase-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    padding: 13px 14px;
    border-radius: var(--radius);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    box-shadow: var(--shadow);
    transition: border-color var(--transition), box-shadow var(--transition);
    animation: fadeIn 0.3s ease;
    gap: 8px;
}

.phrase-item:hover { border-color: rgba(var(--on-surface-rgb), 0.18); }

.phrase-item.selected {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.07);
    box-shadow: 0 6px 18px -10px rgba(var(--accent-rgb), 0.6);
}

.phrase-item-left {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
    overflow: hidden;
}

.phrase-info {
    flex: 1;
    min-width: 0;
    /* Tapping the row opens the phrase card, so it needs the same click affordance as .word-info —
       a pointer cursor + hover tint (#577). */
    cursor: pointer;
    padding: 2px 4px;
    border-radius: var(--radius);
    transition: background var(--transition);
}

.phrase-info:hover {
    background: var(--card-hover);
}

/* Phrase row main text — same body size as .word-text. Phrases can wrap to 2 lines
   without becoming heading-sized. See VOCAB_PROGRESS_GUIDE.md §5. */
.phrase-text {
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    font-weight: 600;
    letter-spacing: -0.005em;
    color: var(--text);
    /* Long phrases get 2-line clamp so a single long row doesn't blow up the list. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
}

.phrase-translation {
    font-size: var(--type-body-s-size);
    line-height: var(--type-body-s-line);
    color: var(--text-secondary);
    margin-top: 3px;
    /* Wrap the full translation onto as many lines as needed instead of clamping it — a
       clamp (even at 2 lines) still hid the tail of longer translations and the user
       couldn't read them (#123). The phrase-text above stays 2-line-clamped; the
       translation is the payload, so it gets to breathe. */
    white-space: normal;
    overflow-wrap: anywhere;
}


.phrase-meta {
    display: flex;
    gap: 6px;
    margin-top: 4px;
    flex-wrap: wrap;
}

/* --- Stage Indicator (legacy — kept until all callers migrate to .lw-prog) --- */
.stage-indicator {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
}

/* ============================================================
   Word progress indicator (.lw-prog)
   ------------------------------------------------------------
   Four mutually-exclusive visual states for a vocab item:
     • .lw-prog--learning  → funnel stages 1..6  (segmented arc + center number)
     • .lw-prog--srs       → in SRS, not mastered (continuous arc + % + ticks)
     • .lw-prog--manual    → user marked as known  (filled disc + check + pin)
     • .lw-prog--auto      → algorithm auto-mastered (soft disc + check + sparkle)
   Driven entirely by --srs / --accent / --success theme tokens.
   ============================================================ */
.lw-prog {
    --lw-prog-size: 34px;
    width:  var(--lw-prog-size);
    height: var(--lw-prog-size);
    border-radius: 999px;
    position: relative;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font);
    line-height: 1;
}

/* The ring background SVG fills the pill behind the centered label.
   Scoped to learning + srs only — the badge svgs (check, sparkle) are
   centered by parent flex and must NOT be stretched to 100% by an
   over-broad rule. */
.lw-prog--learning > svg,
.lw-prog--srs > svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
}

/* Centered label/check sits above the background ring. */
.lw-prog__label {
    position: relative;
    z-index: 2;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.lw-prog--sm { --lw-prog-size: 26px; }
.lw-prog--md { --lw-prog-size: 34px; }
.lw-prog--lg { --lw-prog-size: 56px; }

/* --- Learning — segmented arc + stage number --- */
.lw-prog--learning           { background: rgba(var(--on-surface-rgb), 0.045); }
.lw-prog--learning.is-new    { background: var(--accent-soft); }
.lw-prog--learning.is-done   { background: rgba(var(--accent-rgb), 0.14); }

.lw-prog--learning .lw-prog__label {
    font-size: 11px; /* skip(c): fixed ring geometry */
    color: var(--text);
}
.lw-prog--learning.is-new .lw-prog__label {
    color: var(--accent-strong, var(--accent));
}
.lw-prog--learning.lw-prog--sm .lw-prog__label { font-size: 10px; /* skip(c): fixed 26px ring geometry */ }
.lw-prog--learning.lw-prog--lg .lw-prog__label { font-size: 16px; /* skip(c): fixed 56px ring geometry */ }

/* --- SRS — continuous arc + ticks + percent --- */
.lw-prog--srs         { background: rgba(var(--srs-rgb), 0.09); }
.lw-prog--srs.is-done { background: rgba(var(--srs-rgb), 0.18); }

/* Stroke colors live here (CSS) and NOT on the SVG element's stroke= attribute,
   because some browsers don't resolve var() inside SVG presentation attributes
   on <circle>/<line>. The track is bumped from the original 24% to 35% so it
   stays visibly distinct from the pill's 9% bg tint on every theme. */
.lw-prog--srs__track    { stroke: rgba(var(--srs-rgb), 0.35); }
.lw-prog--srs__progress { stroke: var(--srs); }
.lw-prog--srs__tick     { stroke: rgba(var(--srs-rgb), 0.45); }

.lw-prog--srs .lw-prog__label {
    font-size: 9.5px; /* skip(c): fixed 34px ring geometry, mono percent */
    color: var(--srs);
    font-family: var(--font-mono, var(--font));
    letter-spacing: -0.04em;
}
.lw-prog--srs.lw-prog--sm .lw-prog__label { font-size: 8.5px; /* skip(c): fixed 26px ring geometry */ }
.lw-prog--srs.lw-prog--lg .lw-prog__label { font-size: 13px; /* skip(c): fixed 56px ring geometry */ }

/* --- Manual learned — filled disc + check + pin corner --- */
.lw-prog--manual {
    background: var(--success);
    color: #fff;
    box-shadow:
        0 1px 3px rgba(var(--success-rgb), 0.35),
        inset 0 -1px 0 rgba(0, 0, 0, 0.06);
}

/* Check rides on the parent's flex centering — no absolute positioning,
   so it stays in the middle regardless of size. */
.lw-prog--manual .lw-prog__check {
    position: relative;
    z-index: 2;
    width: 56%;
    height: 56%;
    stroke: currentColor;
    stroke-width: 3;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.lw-prog--manual::after {
    content: "";
    position: absolute;
    top: -1px; right: -1px;
    width: 32%; height: 32%;
    min-width: 10px; min-height: 10px;
    border-radius: 999px;
    background: var(--bg);
    box-shadow: 0 0 0 1px rgba(var(--on-surface-rgb), 0.04);
    z-index: 3;
}
.lw-prog--manual::before {
    content: "";
    position: absolute;
    top: 7%; right: 7%;
    width: 18%; height: 18%;
    min-width: 5px; min-height: 5px;
    border-radius: 999px;
    background: var(--success);
    z-index: 4;
}

/* --- Auto learned — soft disc + dashed ring + sparkle corner --- */
.lw-prog--auto {
    background: rgba(var(--success-rgb), 0.12);
    color: var(--success);
}

/* Dashed ring fills the pill (layer 1). */
.lw-prog--auto .lw-prog__ring {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
}
.lw-prog--auto .lw-prog__ring circle {
    fill: none;
    stroke: var(--success);
    stroke-width: 1.3;
    stroke-dasharray: 2.4 2.8;
}

/* Check rides on parent flex centering (layer 2). */
.lw-prog--auto .lw-prog__check {
    position: relative;
    z-index: 2;
    width: 46%;
    height: 46%;
    stroke: currentColor;
    stroke-width: 2.6;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.lw-prog--auto .lw-prog__sparkle {
    position: absolute;
    top: -2px; right: -2px;
    width: 36%; height: 36%;
    min-width: 11px; min-height: 11px;
    border-radius: 999px;
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--success);
    box-shadow: 0 0 0 1px rgba(var(--on-surface-rgb), 0.04);
    z-index: 3;
}
.lw-prog--auto .lw-prog__sparkle svg {
    width: 72%;
    height: 72%;
    stroke: currentColor;
    stroke-width: 2;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* --- Queued (Added) — saved but waiting for a lesson: muted disc + clock --- */
.lw-prog--queued {
    background: rgba(var(--on-surface-rgb), 0.06);
    color: rgba(var(--on-surface-rgb), 0.5);
}
.lw-prog--queued .lw-prog__clock {
    position: relative;
    z-index: 2;
    width: 62%;
    height: 62%;
    stroke: currentColor;
    stroke-width: 2;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* --- Pagination --- */
.pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 20px;
    padding: 16px;
}

.page-btn {
    padding: 10px 20px;
    border: 1px solid var(--card-border);
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    color: var(--text);
    font-size: var(--type-body-l-size);
    cursor: pointer;
    transition: all var(--transition);
}

.page-btn:hover:not(:disabled) {
    background: var(--card-hover);
}

.page-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.page-info {
    font-size: var(--type-body-size);
    color: var(--text-secondary);
    font-weight: 500;
}

/* --- Loading --- */
.loading-container {
    display: flex;
    justify-content: center;
    padding: 60px 0;
}

.spinner {
    width: 36px;
    height: 36px;
    border: 3px solid var(--card-border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* The in-button wait, sized to sit beside a label. GLOBAL because that is what the markup using it
   assumed: the class was only ever defined inside two components' SCOPED stylesheets, so every
   other button that wrote <span class="spinner-small"> rendered an unstyled empty span — the
   writing block and the writing page both showed "Checking…" with nothing turning next to it.
   Drawn in the button's own text colour so one rule works on an accent fill and on an outline
   button, in both themes; the two scoped copies keep their own colours and still win. */
.spinner-small {
    width: 16px;
    height: 16px;
    flex: none;
    border: 2px solid color-mix(in srgb, currentColor 35%, transparent);
    border-top-color: currentColor;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

/* Scoped tab-loading overlay for the dictionary content area. A plain centred
   spinner instead of the full-brand "orbital" LoadingOverlay, which was too heavy
   for an in-page tab switch (#118). Mirrors .lw-loader.is-scoped positioning. */
.dictionary-tab-loading {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    z-index: 5;
}

/* --- Modal overlay --- */
.modal-overlay {
    position: fixed;
    inset: 0;
    /* Opaque background */
    background: var(--bg);
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Keeps centred children (FeedbackModal and friends) clear of the system bars — they
       are in this flex flow, unlike .modal-card which is fixed and pads itself. The
       backdrop still bleeds edge-to-edge; only the content box is inset. */
    padding: var(--safe-top) 0 var(--safe-bottom);
    animation: fadeInModal 0.25s ease-out;
}

@keyframes fadeInModal {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

/* --- Word Card Modal --- */
.modal-card {
    background: var(--bg);
    width: 100%;
    max-width: 500px;
    height: 100dvh;
    max-height: 100dvh;
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1001;
    overflow-y: auto;
    /* Full-height sheet pinned to top:0 — so its own padding is the only thing keeping the
       header out from under the status-bar clock and the footer off the gesture pill. The
       shell chrome (topbar/tabbar) that normally absorbs the insets is not in play here. */
    padding: calc(24px + var(--safe-top)) 20px calc(24px + var(--safe-bottom));
    /* Faster and more direct animation */
    animation: zoomIn 0.2s ease-out;
}

@keyframes zoomIn {
    from {
        opacity: 0;
        transform: translate(-50%, 10px) scale(0.98);
    }

    to {
        opacity: 1;
        transform: translate(-50%, 0) scale(1);
    }
}

@keyframes slideUp {
    from {
        transform: translateY(100%);
    }

    to {
        transform: translateY(0);
    }
}

.modal-close {
    position: absolute;
    top: 16px;
    right: 16px;
    width: 32px;
    height: 32px;
    border: none;
    border-radius: 50%;
    background: rgba(var(--on-surface-rgb), 0.08);
    color: var(--text-secondary);
    font-size: 18px; /* skip(c): fixed 32×32 circle, single × glyph */
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--transition);
}

.modal-close:hover {
    background: rgba(var(--on-surface-rgb), 0.15);
}

.card-header {
    margin-bottom: 24px;
    display: flex;
    flex-direction: column;
    /* Stack vertically then align start */
    align-items: flex-start;
    gap: 4px;
    position: relative;
    /* For close button */
}

.card-header-actions {
    display: flex;
    gap: 12px;
    margin-top: 16px;
}

.modal-close-inline {
    position: absolute;
    top: -4px;
    right: -4px;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: 50%;
    background: rgba(var(--on-surface-rgb), 0.05);
    color: var(--text-secondary);
    font-size: 20px; /* skip(c): fixed 36×36 circle, single × glyph */
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--transition);
}

.modal-close-inline:hover {
    background: rgba(var(--on-surface-rgb), 0.1);
    color: var(--text);
}

.card-header-top {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 4px;
    /* Reserve room for the absolute .modal-close-inline (top-right) so a long phrase title plus the
       audio button never slide under the close X (#577). */
    padding-right: 44px;
}

.card-word {
    font-size: var(--type-display-size);
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.5px;
}

.action-btn.audio-main {
    font-size: calc(24px * var(--ui-scale, 1)); /* skip(c): audio icon glyph */
    width: 44px;
    height: 44px;
    /* Never let a long phrase title compress the round button into an oval (#661). */
    flex-shrink: 0;
    min-width: 44px;
    min-height: 44px;
    color: var(--accent);
    background: rgba(var(--accent-rgb), 0.08);
}

.action-btn.audio-main:hover {
    background: rgba(var(--accent-rgb), 0.15);
}

.card-phonetics {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 6px;
    flex-wrap: wrap;
}

.transcription {
    font-family: var(--font-mono);
    font-size: var(--type-body-size);
    color: var(--text-secondary);
}

.pos {
    font-size: var(--type-body-s-size);
    padding: 3px 10px;
    border-radius: 6px;
    background: rgba(var(--accent-rgb), 0.1);
    color: var(--accent);
    font-weight: 600;
}

.level-badge {
    color: var(--button-text);
    background: var(--accent);
    padding: 2px 8px;
    border-radius: 6px;
    font-weight: bold;
    font-size: 0.9em;
}

.difficulty-text {
    display: flex;
    align-items: center;
    gap: 8px;
}

.freq {
    /* A pill matching .pos / .level-badge height so the phonetics row reads evenly, with a
       brighter ink than --hint so the rank icon + number are legible on dark themes (#600). */
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--type-body-s-size);
    font-weight: 600;
    line-height: 1.5;
    color: var(--text-secondary);
    background: rgba(var(--on-surface-rgb), 0.06);
    padding: 3px 9px;
    border-radius: 999px;
    white-space: nowrap;
}

.card-translation {
    padding: 14px 16px;
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    font-size: var(--type-body-l-size);
    margin-bottom: 16px;
    /* Glosses (L1 translations) use the UI sans face, not the editorial serif: the serif
       mis-positions Cyrillic combining stress marks (U+0301) and "slices" words (#600). */
    font-family: var(--font);
    line-height: 1.5;
}

.card-translation .label {
    margin-right: 6px;
}

.card-progress {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    margin-bottom: 16px;
    gap: 12px;
}

.card-progress-left {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1;
}

.card-progress-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.progress-text {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    font-size: var(--type-body-size);
    color: var(--text);
}

/* --- Dictionary card polish (#600) --- */
/* Each control in the action row gets a ring so it reads as a button; the report flag
   matches the 36px action buttons and sits in the warning hue (was small / grey / top-aligned). */
.card-progress-actions .action-btn {
    border: 1px solid rgba(var(--on-surface-rgb), 0.18);
}
.card-progress-actions .action-btn.mastery-active {
    border-color: rgba(var(--success-rgb), 0.45);
}
.card-progress-actions .action-btn.delete {
    border-color: rgba(var(--destructive-rgb), 0.30);
}
.card-progress-actions .feedback-button {
    width: 36px;
    height: 36px;
    color: var(--warning);
    background: rgba(var(--warning-rgb), 0.10);
    border-color: rgba(var(--warning-rgb), 0.40);
    opacity: 1;
}
.card-progress-actions .feedback-button:hover {
    color: var(--warning);
    background: rgba(var(--warning-rgb), 0.18);
    border-color: rgba(var(--warning-rgb), 0.60);
}
.card-progress-actions .feedback-button svg {
    width: 16px;
    height: 16px;
}

.difficulty-text {
    font-size: var(--type-body-s-size);
    color: var(--warning);
}

.card-section {
    margin-bottom: 16px;
}

.card-section h3 {
    font-size: var(--type-title-s-size);
    font-weight: 600;
    margin-bottom: 10px;
    color: var(--text-secondary);
}

/* Modern section divider: small uppercase caption with a thin accent rule on the
   left. Replaces the older emoji-prefixed h3 (📝 / 🔗) — looks cleaner across
   all themes (cool / sepia / dark) and matches Home page section labels. */
.card-section-label {
    color: var(--text-secondary);
    margin: 0 0 var(--space-3) 2px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.card-section-label::before {
    content: "";
    display: inline-block;
    width: 14px;
    height: 2px;
    border-radius: 1px;
    background: var(--accent);
    opacity: 0.6;
}

/* Numbered list of English sense definitions (monolingual learn-by-definition aid). */
.card-definitions {
    margin: 0;
    /* Boxed like the sibling Sentences/Collocations cards (#943 parity): same surface
       tokens + padding so the whole list reads as one outlined block. Left padding is
       larger to seat the outside <ol> markers inside the border. */
    padding: 10px 16px 10px 34px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    color: var(--text-primary);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: var(--radius-sm);
}
.card-definitions li {
    padding-left: 2px;
    line-height: 1.4;
}
.card-definitions li::marker {
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* Sentences share the collocation list's column layout so the two sections' speaker icons
   line up across both (#730). The wrapper also carries the alignment grid added below (#934). */
.examples-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.example-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
}

/* Text (sentence + translation) on the left; the +learn button / "изучается" status docks right
   and stays vertically centred so the action column aligns across rows (#711). */
.example-body { flex: 1 1 auto; min-width: 0; }

.example-original {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    font-size: var(--type-body-l-size);
    line-height: 1.5;
    color: var(--text);
}

.action-btn.audio-small {
    width: 28px;
    height: 28px;
    min-width: 28px;
    padding: 0;
    color: var(--accent);
    opacity: 0.75;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* Always dock to the right end of its flex parent (sentence row / collocation row),
       so position is identical between Sentences and Collocations sections. */
    margin-left: auto;
    transition: opacity var(--transition), background var(--transition);
}

.action-btn.audio-small:hover {
    opacity: 1;
    background: rgba(var(--accent-rgb), 0.10);
}

.example-original b,
.col-phrase b,
.col-phrase strong {
    color: var(--accent);
    font-weight: 700;
}

.example-translation {
    font-size: var(--type-body-size);
    color: var(--text);
    /* Brighter for better contrast */
    opacity: 0.9;
    /* Slightly offset from main text */
    margin-top: 4px;
    font-style: italic;
}

/* Collapsed "more" list (#1133): reference-only examples that merely CONTAIN the word (generated
   for a neighbour) — one tier below the primary 3 + 3, opened on demand via a native
   <details>/<summary> disclosure with a custom ▸/▾ marker. Token-driven; no hardcoded colours. */
.examples-more { margin-top: 6px; }

.examples-more-toggle {
    cursor: pointer;
    color: var(--accent);
    opacity: 0.85;
    padding: 4px 2px;
    list-style: none;
    user-select: none;
    transition: opacity var(--transition);
}

.examples-more-toggle:hover { opacity: 1; }

.examples-more-toggle::-webkit-details-marker { display: none; }

.examples-more-toggle::before {
    content: "\25B8"; /* ▸ */
    display: inline-block;
    margin-right: 6px;
    transition: transform var(--transition);
}

.examples-more[open] > .examples-more-toggle::before { transform: rotate(90deg); }

.examples-more > .examples-list,
.examples-more > .collocations-list { margin-top: 6px; }

/* #858 — "из [Book] · Author" caption under a read-only reading example on the word card. */
.reading-example-source {
    margin-top: 6px;
    color: var(--text-secondary);
    opacity: 0.85;
}

.collocations-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.collocation-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    font-size: var(--type-body-l-size);
}

/* Phrase + audio on the first line, translation on its own line below — mirrors the Sentences
   section so the speaker icons line up vertically across both sections and the translation can
   breathe (italic, like the sentence translation) (#730). Action docks right of the body (#711). */
.collocation-body {
    flex: 1 1 auto;
    min-width: 0;
}

.collocation-original {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    color: var(--text);
}

.col-phrase {
    color: var(--text);
    font-weight: 500;
}

.col-translation {
    color: var(--text);
    /* Brighter for better contrast */
    opacity: 0.9;
    font-size: var(--type-body-size);
    /* Own line under the phrase, italic — matches .example-translation (#730). */
    margin-top: 4px;
    font-style: italic;
}

/* "+ learn" opt-in on a reference-only example/collocation on the word card (#682); right-docked
   in the row's action column (#711). */
.phrase-add-btn {
    flex-shrink: 0;
    font-size: var(--type-body-s-size);
    font-weight: 600;
    padding: 3px 10px;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-strong);
    border: none;
    cursor: pointer;
    white-space: nowrap;
}
.phrase-add-btn:disabled { opacity: 0.5; cursor: default; }
.phrase-added-label { flex-shrink: 0; color: var(--text-secondary); font-weight: 600; white-space: nowrap; }

/* Audio-icon column alignment (#934). The speaker button docks to the right edge of the text
   body (margin-left:auto), so its x tracks the body width — which shrinks when the row's action
   is wider ("✓ Learning" vs "+ Learn"), knocking the icons out of a straight column. Where
   subgrid is available (every current WebView), promote each list to a shared 2-column grid: the
   action column auto-sizes to the widest action across ALL rows, so the body column is identical
   per row and every speaker icon lands at the same x — and the action pills share a column too.
   The flex rules above stay as the fallback for engines without subgrid (no worse than today). */
@supports (grid-template-columns: subgrid) {
    .examples-list,
    .collocations-list {
        display: grid;
        grid-template-columns: 1fr auto;
        gap: 6px 12px;
    }
    .examples-list > .example-item,
    .collocations-list > .collocation-item {
        display: grid;
        grid-template-columns: subgrid;
        grid-column: 1 / -1;
        align-items: center;
    }
}

/* --- Confirm Dialog --- */
.confirm-dialog {
    background: var(--bg-secondary);
    border-radius: var(--radius);
    padding: 28px 24px;
    max-width: 320px;
    width: 90%;
    text-align: center;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    box-shadow: var(--shadow);
    animation: fadeIn 0.2s ease;
}

.confirm-message {
    font-size: var(--type-body-l-size);
    font-weight: 500;
    margin-bottom: 20px;
    color: var(--text);
}

.confirm-actions {
    display: flex;
    gap: 10px;
}

.btn-cancel,
.btn-confirm {
    flex: 1;
    padding: 12px 16px;
    border: none;
    border-radius: var(--radius-sm);
    font-size: var(--type-title-s-size);
    font-weight: 600;
    cursor: pointer;
    transition: all var(--transition);
    font-family: var(--font);
}

.btn-cancel {
    background: var(--card-bg);
    color: var(--text-secondary);
    border: 1px solid var(--card-border);
}

.btn-cancel:hover {
    background: var(--card-hover);
}

.btn-confirm {
    background: var(--accent);
    color: #fff;
}

.btn-confirm-destructive {
    background: var(--destructive);
}

.btn-confirm:hover {
    opacity: 0.85;
}

/* --- Home page --- */
.home-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 60vh;
    padding: 24px;
    text-align: center;
}

.home-page h1 {
    font-size: var(--type-display-size);
    margin-bottom: 12px;
}

.home-page p {
    color: var(--text-secondary);
    font-size: var(--type-body-size);
}

.auth-info {
    margin-top: 24px;
    padding: 16px;
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    font-size: var(--type-body-size);
    color: var(--hint);
}

/* --- Mastery Buttons --- */
.action-btn.mastery-active {
    color: var(--success);
    background: rgba(var(--success-rgb), 0.10);
}

.action-btn.mastery-inactive {
    color: var(--hint);
}

.action-btn.mastery-inactive:hover {
    color: var(--accent);
}

/* --- Load More --- */
.load-more-container {
    padding: 24px 16px;
    display: flex;
    justify-content: center;
}

.btn-load-more {
    background: var(--card-bg);
    color: var(--accent);
    border: 1px solid var(--accent);
    padding: 12px 32px;
    border-radius: 20px;
    font-size: var(--type-title-s-size);
    font-weight: 600;
    cursor: pointer;
    transition: all var(--transition);
}

.btn-load-more:hover:not(:disabled) {
    background: rgba(var(--accent-rgb), 0.08);
}

.btn-load-more:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.loading-more {
    display: flex;
    justify-content: center;
    padding: 12px;
}

.level-badge.small {
    padding: 1px 6px;
    font-size: var(--type-caption-size);
}

/* base_vocab + CEFR badge shown next to addable words (#661, BaseVocabBadge.razor). */
.word-vocab-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
}
.word-vocab-badge__icon { color: var(--accent); }


/* Settings Page */
/* Language Grid (still used by Onboarding's language step) */
.language-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    gap: 12px;
}

.language-card {
    background: var(--bg-secondary);
    border-radius: 12px;
    padding: 12px;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    border: 2px solid transparent;
}

.language-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}

.language-card.active {
    background: var(--button-bg);
    color: var(--button-text);
    border-color: var(--button-bg);
}

.lang-flag {
    font-size: 1.8rem;
    margin-bottom: 8px;
}

.lang-name {
    font-size: 0.9rem;
    font-weight: 500;
}

.active-check {
    position: absolute;
    top: 6px;
    right: 8px;
    font-size: 0.8rem;
    background: rgba(var(--on-surface-rgb), 0.2);
    border-radius: 50%;
    width: 18px;
    height: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* .mode-btn — base used by the Reader / Story mode toggles (they each add their
   own overrides inline). */
.mode-btn {
    padding: 12px;
    border-radius: var(--radius-lg);
    border: 1px solid rgba(0,0,0,0.05);
    background: var(--bg-secondary);
    text-align: left;
    transition: all 0.2s;
    font-weight: 500;
}

.mode-btn.active {
    background: var(--button-bg);
    color: var(--button-text);
}

.btn-back {
    width: 100%;
    padding: 14px;
    border-radius: 12px;
    border: none;
    background: var(--bg-secondary);
    color: var(--text);
    font-weight: 600;
    transition: background 0.2s;
}

/* Animations — [animation="fadeIn"] is used by MyDictionary; keyframes fadeIn
   is also referenced by the settings pages. */
[animation="fadeIn"] {
    animation: fadeIn 0.3s ease-out;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.icon-svg {
    width: 16px;
    height: 16px;
    display: block;
    flex-shrink: 0;
}

/* --- Theme switcher (segmented cards) --- */
.theme-switcher {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
}

.theme-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 12px 6px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--card-border);
    background: var(--card-bg);
    color: var(--text-secondary);
    cursor: pointer;
    font-family: var(--font);
    transition: all var(--transition);
}

.theme-option:hover {
    background: var(--card-hover);
}

.theme-option.active {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.10);
    color: var(--accent);
}

.theme-option-icon {
    font-size: 1.4rem;
    line-height: 1;
}

.theme-option-label {
    font-size: var(--type-body-s-size);
    font-weight: 600;
    white-space: nowrap;
}

@media (max-width: 360px) {
    /* Three nowrap labels across a 360px screen — drop a tier rather than let them collide. */
    .theme-option-label {
        font-size: var(--type-caption-size);
    }

    .theme-option {
        padding: 10px 4px;
    }
}

/* ============================================================
   App shell — adaptive navigation frame (AppShell.razor)
   Mobile-first: top bar + fixed bottom tab bar.
   ≥768px: vertical side rail + top bar (tab bar hidden).
   ============================================================ */
.lw-icon {
    display: block;
    flex-shrink: 0;
}

.app-shell {
    min-height: 100dvh;
    background: var(--bg);
    color: var(--text);
}

/* ── Top bar ──────────────────────────────────────────────── */
.app-shell__topbar {
    position: sticky;
    top: 0;
    z-index: var(--shell-z-chrome);
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 8px;
    min-height: calc(var(--shell-topbar-h) + var(--safe-top));
    padding: calc(var(--safe-top) + 6px) max(14px, var(--safe-right)) 6px max(14px, var(--safe-left));
    background: var(--bg);
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--card-border);
}

.app-shell__brand {
    justify-self: start;
    min-width: 0;
    display: flex;
    align-items: center;
}

.app-shell__brand-text {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--type-title-m-size);
    letter-spacing: -0.01em;
    color: var(--text);
    white-space: nowrap;
}

/* Branded wordmark used as AppShell BrandContent (<BrandMark>): orbit glyph + "Lang" in the
   ink colour and "Orbit" in the terracotta accent. Mirrors the boot-loader's lw-orb-core
   .l1/.l2 colour split so the brand reads the same from boot splash to top bar. */
.lw-brand {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
}

.lw-brand__orb {
    flex: none;
    width: 22px;
    height: 22px;
}

.lw-brand__word {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--type-title-m-size);
    letter-spacing: -0.01em;
    white-space: nowrap;
}

.lw-brand__lang { color: var(--text); }
.lw-brand__orbit { color: var(--accent); }

.app-shell__title {
    justify-self: center;
    min-width: 0;
    text-align: center;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--type-body-l-size);
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-shell__actions {
    justify-self: end;
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ── Content ──────────────────────────────────────────────── */
.app-shell__content {
    min-width: 0;
    /* Clear the fixed mobile tab bar (overridden to 0 on desktop). */
    padding-bottom: calc(var(--shell-tabbar-h) + var(--safe-bottom));
}

/* Without the shell top bar the content starts at the very top of the window, so it has to
   clear the status bar itself — the top bar used to be the thing being overlapped. Zero on
   hosts that owe nothing: Telegram Mini App and desktop browsers have no inset at all, and the
   MAUI Android host has already been inset by the activity, so it pins --safe-* to 0. Real on
   the installed PWA and on iOS, where the page does own the strip. Immersive is excluded — the
   Reader lays out its own full-bleed chrome. */
.app-shell--no-topbar:not(.app-shell--immersive) > .app-shell__content {
    padding-top: var(--safe-top);
}

.app-shell--immersive .app-shell__content {
    padding-bottom: 0;
}

/* Telegram Mini App (#790): the chat is the navigation, so the shell drops its own tab
   bar — reclaim its reserved space. Keeps the non-immersive internal-scroll model (form /
   list pages keep their sticky headers and the content pane stays the scroll container),
   unlike immersive which removes it via the Reader's full-page scroll. */
.app-shell--no-nav .app-shell__content {
    padding-bottom: 0;
}

/* ── Full-height page helper ──────────────────────────────────
   For a page that owns an INTERNAL scroll container (its own header and footer pinned, a
   thread scrolling between them — the Speaking console). The shell does NOT hand a page a
   bounded height: on mobile the DOCUMENT scrolls and .app-shell__content is height:auto, so
   `height:100%` on the page root resolves to auto and the inner `overflow:auto` child inflates
   to its content instead of scrolling. Pin it to the viewport MINUS the chrome the shell has
   already reserved room for (the fixed tab bar and the insets), or the page overshoots by
   exactly the tab bar and the document scrolls after all.

   Desktop (>=768px) is the other model — there the content pane is itself the scroll container
   with a real height, so a plain 100% is right and dvh would double-count the rail-less layout. */
.lw-fullheight {
    height: calc(100dvh - var(--safe-top) - var(--shell-tabbar-h) - var(--safe-bottom));
    min-height: 0;
}

/* Telegram Mini App: the chat is the navigation, the shell draws no tab bar and reserves no
   room for one — so neither does the page. */
.app-shell--no-nav .lw-fullheight {
    height: calc(100dvh - var(--safe-top));
}

@media (min-width: 768px) {
    .lw-fullheight,
    .app-shell--no-nav .lw-fullheight {
        height: 100%;
    }
}

/* ── Bottom tab bar (mobile) ──────────────────────────────── */
.app-shell__tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--shell-z-chrome);
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    align-items: center;
    height: calc(var(--shell-tabbar-h) + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    background: var(--bg);
    background: color-mix(in srgb, var(--bg) 86%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-top: 1px solid var(--card-border);
}

.app-shell__tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    text-decoration: none; /* #942 — nav items are <a>; kill the inherited browser underline */
    padding: 6px 4px;
    /* Let the label's ellipsis kick in inside the 1fr grid column instead of
       overflowing into the neighbouring tab. */
    min-width: 0;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font);
    transition: color var(--transition), transform var(--transition);
}

.app-shell__tab-label {
    font-size: var(--type-caption-size);
    font-weight: 600;
    line-height: 1;
    /* Keep every tab caption on a single line — long localized labels
       ("My dictionary", "Il mio dizionario") otherwise wrap to two rows and
       break the bar's rhythm (#80). Clip with an ellipsis if a translation is
       still too wide for its column. */
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.app-shell__tab.is-active {
    color: var(--accent);
}

.app-shell__tab:active {
    transform: scale(0.92);
}

/* ── "More" overflow sheet (mobile) ───────────────────────── */
/* Backdrop covers the page content but stops at the tab bar — the bar stays
   visible so the active "More" highlight + the dismiss-tap target are obvious. */
.app-shell__more-backdrop {
    position: fixed;
    inset: 0 0 calc(var(--shell-tabbar-h) + var(--safe-bottom)) 0;
    z-index: calc(var(--shell-z-chrome) - 1);
    background: rgba(0, 0, 0, 0.35);
    animation: app-shell-fade-in 0.16s ease-out;
}

.app-shell__more-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--shell-tabbar-h) + var(--safe-bottom));
    z-index: var(--shell-z-chrome);
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px max(8px, var(--safe-right)) 8px max(8px, var(--safe-left));
    background: var(--bg);
    border-top: 1px solid var(--card-border);
    box-shadow: 0 -8px 24px -8px rgba(0, 0, 0, 0.25);
    animation: app-shell-slide-up 0.18s ease-out;
}

.app-shell__more-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--text);
    font-family: var(--font);
    font-size: var(--type-title-s-size);
    font-weight: 500;
    text-align: left;
    text-decoration: none;
    transition: background var(--transition), color var(--transition);
}

.app-shell__more-item:hover {
    background: var(--card-hover);
}

.app-shell__more-item:active {
    background: rgba(var(--accent-rgb), 0.12);
}

.app-shell__more-item.is-active {
    color: var(--accent);
    background: rgba(var(--accent-rgb), 0.10);
}

@keyframes app-shell-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes app-shell-slide-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

/* ── Side rail (desktop) ──────────────────────────────────── */
.app-shell__rail {
    display: none;
}

@media (min-width: 768px) {
    .app-shell {
        display: grid;
        grid-template-columns: var(--shell-rail-w) 1fr;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "rail topbar"
            "rail content";
    }

    .app-shell--immersive {
        grid-template-columns: 1fr;
        grid-template-areas: "content";
    }

    /* Telegram Mini App (#790): no rail either — the chat is the nav. Single "content"
       column like immersive, but the non-immersive scroll rules below still apply
       (internal content scroll, not the Reader's full-page scroll). Excludes immersive so
       a page that is BOTH (the reader inside Telegram) keeps immersive's grid untouched —
       no-nav must not override its rows at equal specificity. */
    .app-shell--no-nav:not(.app-shell--immersive) {
        grid-template-columns: 1fr;
        grid-template-rows: 1fr;
        grid-template-areas: "content";
    }

    /* No top bar (Mini App): rail + content only, content spans the full height.
       Excluded when also immersive (the Reader) OR no-nav (the Telegram Mini App):
       both must stay single-column "content". Without :not(), this rule's "rail content"
       areas — defined later with equal specificity — overrode immersive's "content",
       reinstating a 1fr rail column with no rail element and shoving the content right
       (#464; same trap for no-nav). */
    .app-shell--no-topbar:not(.app-shell--immersive):not(.app-shell--no-nav) {
        grid-template-rows: 1fr;
        grid-template-areas: "rail content";
    }

    .app-shell__topbar {
        grid-area: topbar;
    }

    .app-shell__content {
        grid-area: content;
        padding-bottom: 0;
    }

    /* Non-immersive desktop: the shell is exactly the viewport and the CONTENT PANE is the
       scroll container — not the window. This pins the side rail in place. Previously the
       window scrolled while the rail was sticky over a grid area taller than the viewport,
       so on a long page (the Library shelves) the sticky released near the bottom and the
       rail — with the top nav items — scrolled out of view even though it had empty space.
       The immersive Reader is excluded: it keeps full-page scroll for its own chrome and
       sticky translate slider. */
    .app-shell:not(.app-shell--immersive) {
        height: 100dvh;
        overflow: hidden;
    }

    .app-shell:not(.app-shell--immersive) .app-shell__content {
        min-height: 0;
        overflow-y: auto;
    }

    .app-shell__tabbar,
    .app-shell__more-backdrop,
    .app-shell__more-sheet {
        /* Desktop uses the rail (no overflow needed — it scrolls). If the user resizes
           from mobile while the More sheet is open, hide it here so it doesn't linger. */
        display: none;
    }

    .app-shell__rail {
        grid-area: rail;
        position: sticky;
        top: 0;
        align-self: start;
        height: 100dvh;
        display: flex;
        flex-direction: column;
        gap: 4px;
        padding: calc(var(--safe-top) + 14px) 8px 14px;
        background: var(--bg-secondary);
        border-right: 1px solid var(--card-border);
        overflow-y: auto;
    }

    .app-shell__rail-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 4px;
        padding: 10px 4px;
        min-width: 0;
        text-decoration: none; /* #942 — kill the inherited <a> underline on rail items too */
        background: none;
        border: none;
        border-radius: var(--radius-sm);
        cursor: pointer;
        color: var(--text-secondary);
        font-family: var(--font);
        transition: background var(--transition), color var(--transition);
    }

    .app-shell__rail-item:hover {
        background: var(--card-hover);
        color: var(--text);
    }

    .app-shell__rail-label {
        font-size: var(--type-caption-size);
        font-weight: 600;
        line-height: 1.15;
        text-align: center;
        max-width: 100%;
        /* Wrap long localized labels to a 2nd line instead of clipping them with an
           ellipsis — the vertical rail has the room, unlike the bottom tabbar (#704 item 1). */
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
        overflow-wrap: anywhere;
    }

    .app-shell__rail-item.is-active {
        color: var(--accent);
        background: rgba(var(--accent-rgb), 0.10);
    }
}

/* ============================================================
   Shared settings primitives (SettingsSection / LanguagePicker)
   Prefixed lw- so they don't collide with a host's bespoke
   .settings-* styles; hosts can migrate onto these over time.
   ============================================================ */
.lw-settings-section {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    padding: 12px 14px;
    margin-bottom: 10px;
    box-shadow: var(--shadow);
}

.lw-settings-section__title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    font-size: var(--type-body-s-size);
    font-weight: 700;
    color: var(--text);
}

.lw-settings-section__icon {
    color: var(--accent);
}

/* ── Language picker ──────────────────────────────────────── */
.lw-lang-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 6px;
}

/* ── UI-language switcher (sign-in + onboarding) ──────────────
   A chip that opens the same .lw-lang-grid in a popover. Pre-Settings surfaces
   only — once the app shell exists, Settings owns this choice. */
.lw-uilang {
    position: relative;
    display: inline-block;
}

.lw-uilang-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 10px 5px 6px;
    border-radius: 999px;
    border: 1px solid var(--card-border);
    background: var(--card-bg);
    color: var(--text-secondary);
    font-family: var(--font);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    transition: all var(--transition);
}

.lw-uilang-chip:hover {
    background: var(--card-hover);
    color: var(--text);
}

.lw-uilang-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    padding: 3px 5px;
    border-radius: 999px;
    background: rgba(var(--accent-rgb), 0.14);
    color: var(--accent);
    font-size: 11px;
    font-weight: 700;
}

.lw-uilang-caret {
    font-size: 10px;
    opacity: 0.7;
}

/* Full-viewport catcher: a tap outside the panel closes it. Transparent — the
   popover is small and the page behind it stays readable, which matters on the
   sign-in screen where the form is the thing you came for. */
.lw-uilang-backdrop {
    position: fixed;
    inset: 0;
    z-index: 60;
}

.lw-uilang-panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 61;
    width: min(320px, calc(100vw - 32px));
    max-height: min(60vh, 420px);
    overflow-y: auto;
    padding: 10px;
    border-radius: var(--radius-md, 16px);
    border: 1px solid var(--card-border);
    background: var(--surface, var(--card-bg));
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
}

/* One column inside the popover: the grid's 150px minimum would otherwise wrap to
   two cramped columns at 320px and truncate the longer endonyms. */
.lw-uilang-panel .lw-lang-grid {
    grid-template-columns: 1fr;
}

/* Flat horizontal card: badge ▸ name ▸ ✓. About half the height of the old
   two-row vertical layout, and a single line read for the eye. */
.lw-lang-card {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 6px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--card-border);
    background: var(--card-bg);
    color: var(--text-secondary);
    cursor: pointer;
    font-family: var(--font);
    transition: all var(--transition);
}

.lw-lang-card:hover {
    background: var(--card-hover);
    color: var(--text);
}

.lw-lang-card.is-active {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.10);
    color: var(--accent);
}

/* 2-letter "flag badge" — replaces the unreliable cross-platform flag emoji.
   Windows ships Segoe UI Emoji without flag glyphs, so "🇬🇧" rendered as "GB"
   letter pair anyway — make it intentional and pretty. */
.lw-lang-flag {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    height: 24px;
    padding: 0 6px;
    border-radius: 999px;
    background: linear-gradient(135deg,
        rgba(var(--accent-rgb), 0.22),
        rgba(var(--accent-rgb), 0.08));
    color: var(--accent);
    border: 1px solid rgba(var(--accent-rgb), 0.28);
    font-size: var(--type-caption-size);
    font-weight: 800;
    letter-spacing: 0.6px;
    line-height: 1;
}

.lw-lang-card.is-active .lw-lang-flag {
    background: rgba(var(--accent-rgb), 0.18);
    color: var(--accent);
}

.lw-lang-name {
    flex: 1 1 auto;
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lw-lang-check {
    flex: 0 0 auto;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--accent);
}

/* ── Segmented control (level / mode / gender) ────────────── */
.lw-segmented {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.lw-segmented--vertical {
    flex-direction: column;
}

.lw-segmented__item {
    flex: 1 1 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-width: 0;
    padding: 7px 12px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--card-border);
    background: var(--card-bg);
    color: var(--text-secondary);
    font-family: var(--font);
    font-size: var(--type-body-size);
    font-weight: 600;
    cursor: pointer;
    transition: all var(--transition);
}

.lw-segmented__item:hover {
    background: var(--card-hover);
    color: var(--text);
}

.lw-segmented__item.is-active {
    border-color: transparent;
    background: var(--accent);
    color: var(--button-text);
    box-shadow: 0 6px 16px -8px rgba(var(--accent-rgb), 0.8);
}

.lw-segmented--vertical .lw-segmented__item {
    justify-content: flex-start;
}

.lw-segmented__icon {
    font-size: 1.1rem;
    line-height: 1;
}

/* ── Toggle switch ────────────────────────────────────────── */
.lw-toggle {
    position: relative;
    flex-shrink: 0;
    width: 46px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: rgba(var(--on-surface-rgb), 0.18);
    cursor: pointer;
    transition: background var(--transition);
}

.lw-toggle.is-on {
    background: var(--accent);
}

.lw-toggle:disabled {
    opacity: 0.5;
    cursor: default;
}

.lw-toggle__knob {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    transition: transform var(--transition);
}

.lw-toggle.is-on .lw-toggle__knob {
    transform: translateX(18px);
}

/* ── Labelled field row (label left, control right) ───────── */
.lw-field-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 32px;
}

.lw-field-row + .lw-field-row {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--card-border);
}

.lw-field-row__label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--type-body-size);
    font-weight: 600;
    color: var(--text);
}

/* "Level not set for this pair" note above the level picker (#1419). */
.level-not-set {
    margin: 0 2px 8px;
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    color: var(--hint, var(--text-secondary));
}

/* A segmented control sitting beside a label must be allowed to SHRINK: its
   buttons carry translated labels whose max-content width is unbounded (ru
   "Последовательный" is ~2x the en "Sequential"), so a rigid `flex: 0 0 auto`
   made the control overflow the card to the right. It keeps grow:0 (no
   stretching when it fits) but shrinks past its max-content width, at which
   point .lw-segmented's own flex-wrap drops the buttons onto a second line,
   still right-aligned under the label row. */
.lw-field-row > .lw-segmented {
    flex: 0 1 auto;
    min-width: min-content;  /* never narrower than its widest button — the label text must stay inside it */
    margin-left: auto;       /* stays right-aligned even when .lw-field-row wraps it onto its own line */
    justify-content: flex-end;
}

/* ── Number stepper ───────────────────────────────────────── */
.lw-stepper {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--card-border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--card-bg);
}

.lw-stepper__btn {
    width: 32px;
    height: 32px;
    border: none;
    background: transparent;
    color: var(--text);
    font-size: calc(16px * var(--ui-scale, 1)); /* skip(c): stepper +/- glyph */
    font-weight: 600;
    cursor: pointer;
    transition: background var(--transition);
}

.lw-stepper__btn:hover:not(:disabled) {
    background: var(--card-hover);
}

.lw-stepper__btn:disabled {
    opacity: 0.4;
    cursor: default;
}

.lw-stepper__value {
    min-width: 46px;
    text-align: center;
    font-size: var(--type-title-s-size);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

/* Two compact sections side by side on wider screens. */
.lw-settings-row {
    display: grid;
    grid-template-columns: 1fr;
    gap: 14px;
}

@media (min-width: 480px) {
    .lw-settings-row {
        grid-template-columns: 1fr 1fr;
    }
    .lw-settings-row .lw-settings-section {
        margin-bottom: 0;
    }
}

/* ============================================================
   SRS "due" hint surfaced on each word/phrase row.
   Two stacked lines (caption "ПОВТОР" + value "сегодня / 3 дня") so the
   eye lands on the value while the caption supplies context. The value
   flips to --accent when due ≤ today — gives the user a one-glance read
   of "what needs my attention" inside long lists.
   ============================================================ */
.srs-due-label {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;
    min-width: 64px;
    margin-right: 4px;
    line-height: 1.1;
    color: var(--text-secondary);
}

.srs-due-caption {
    color: var(--hint);
    opacity: 0.85;
}

.srs-due-value {
    color: var(--text-secondary);
    margin-top: 1px;
    font-weight: 500;
}

.srs-due-value.is-due {
    color: var(--accent);
    font-weight: 600;
}

/* ============================================================
   Hide row-level mastery / delete buttons until the row is selected.
   The audio button stays visible because it's the one action users
   take without picking a word first. Mastery & delete unhide when
   the row gains .selected; this de-clutters long lists.
   ============================================================ */
.word-item .action-btn.mastery-inactive,
.word-item .action-btn.mastery-active,
.word-item .action-btn.delete,
.phrase-item .action-btn.mastery-inactive,
.phrase-item .action-btn.mastery-active,
.phrase-item .action-btn.delete {
    display: none;
}

.word-item.selected .action-btn.mastery-inactive,
.word-item.selected .action-btn.mastery-active,
.word-item.selected .action-btn.delete,
.phrase-item.selected .action-btn.mastery-inactive,
.phrase-item.selected .action-btn.mastery-active,
.phrase-item.selected .action-btn.delete {
    display: flex;
}

/* ============================================================
   Dictionary search box (MyDictionaryView). Same shape as the Packs one, but that copy lives in
   PacksView's SCOPED stylesheet and can't be reached from here — so these rules are deliberately
   nested under .dict-search rather than owning the bare .search-box name globally, which would
   start competing with every scoped copy on the site.
   ============================================================ */
.dict-search {
    padding: 0 12px;
    margin-bottom: 8px;
}

.dict-search .search-box {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    padding: 4px 4px 4px var(--space-3);
    transition: border-color var(--transition);
}
.dict-search .search-box:focus-within { border-color: var(--accent); }

.dict-search .search-icon { color: var(--text-secondary); display: inline-flex; }

.dict-search .search-box input {
    flex: 1;
    background: transparent;
    border: none;
    padding: 10px 4px;
    color: var(--text);
    font: 500 var(--type-body-size) / var(--type-body-line) var(--font);
    outline: none;
    min-width: 0;
}
.dict-search .search-box input::placeholder { color: var(--hint); }
/* Safari/Chrome add their own clear affordance to type=search; we render our own. */
.dict-search .search-box input::-webkit-search-cancel-button { display: none; }

.dict-search .btn-clear-search {
    background: transparent;
    color: var(--text-secondary);
    border: none;
    border-radius: var(--radius-sm);
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    transition: background var(--transition), color var(--transition), transform var(--transition-fast);
}
.dict-search .btn-clear-search:hover { background: var(--card-hover); color: var(--text); }
.dict-search .btn-clear-search:active { transform: scale(0.94); }

.dict-search-empty {
    color: var(--text-secondary);
    text-align: center;
    padding: var(--space-6) var(--space-4);
    margin: 0;
}

/* ============================================================
   Bucket strip — three-up summary of NEW / IN PROGRESS / MASTERED.
   Doubles as a one-tap stage filter (sets _filterStage in MyDictionary).
   Active bucket = filled tint, non-active = neutral surface; the count
   number is colored per-bucket to match the existing dashboard palette.
   ============================================================ */
.bucket-strip {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 6px;
    padding: 10px 4px;
}

.dict-hint-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 2px;
    margin: 0 4px 6px;
}

.bucket-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 10px 8px;
    border-radius: var(--radius);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition), transform var(--transition-fast);
    user-select: none;
    /* Grid items default to min-width:auto (= min-content), so a nowrap label would widen
       its column past 1fr and push the strip off-screen instead of being clipped. */
    min-width: 0;
}

.bucket-cell:hover {
    border-color: rgba(var(--on-surface-rgb), 0.18);
}

.bucket-cell:active {
    transform: scale(0.98);
}

.bucket-cell.active {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.08);
}

.bucket-label {
    color: var(--text-secondary);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    font-weight: var(--type-caption-weight);
    letter-spacing: var(--type-caption-track);
    text-transform: uppercase;
    /* Never wrap: a two-line label stretches its grid row and leaves the other three
       cells vertically centered against it, which reads as broken. If a label still
       overflows (user-raised --ui-scale, an unusually long locale), clip it rather
       than let it push the layout around. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

/* Phone widths: four 1fr cells on a 360px screen leave ~67px of content box each, while
   the caption preset (11px + 0.14em tracking + uppercase) needs ~68px for ru "В РАБОТЕ"
   and ~112px for de "IN BEARBEITUNG". Tracking and uppercase are pure width cost with no
   information in them, so they go first; tightening the gutters buys back the rest. */
@media (max-width: 420px) {
    .bucket-strip { gap: 4px; }

    .bucket-cell { padding: 10px 4px; }

    .bucket-label {
        letter-spacing: 0;
        text-transform: none;
        font-size: calc(10.5px * var(--ui-scale, 1));
    }
}

.bucket-count {
    font-size: var(--type-num-m-size); font-variant-numeric: tabular-nums;
    font-weight: 700;
    line-height: 1.1;
}

.bucket-cell[data-bucket="queued"]   .bucket-count { color: rgba(var(--on-surface-rgb), 0.55); }
.bucket-cell[data-bucket="learning"] .bucket-count { color: var(--accent); }
.bucket-cell[data-bucket="srs"]      .bucket-count { color: var(--warning); }
.bucket-cell[data-bucket="mastered"] .bucket-count { color: var(--mastered-stable); }

/* --- LwSelect: themed dropdown with custom popup (replacement for native <select>) --- */
.lw-select {
    position: relative;
    width: 100%;
}

.lw-select-trigger {
    /* Visually matches .btn-filter (same row in MyDictionary controls-top) —
       same bg/border/padding/font-size keeps the sort dropdown and the filter
       button reading as one chip-row instead of two unrelated controls. */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    width: 100%;
    background: var(--card-bg);
    color: var(--text-secondary);
    border: 1px solid var(--card-border);
    padding: 8px 12px;
    border-radius: 12px;
    font-size: var(--type-body-size);
    font-weight: 500;
    cursor: pointer;
    outline: none;
    transition: all var(--transition);
}

.lw-select-trigger:hover {
    color: var(--text);
}

.lw-select-trigger:focus-visible {
    border-color: var(--accent);
    color: var(--text);
}

.lw-select.open .lw-select-trigger {
    color: var(--text);
    border-color: var(--accent);
}

.lw-select-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: left;
    flex: 1;
}

.lw-select-chevron {
    display: inline-flex;
    opacity: 0.6;
    transition: transform var(--transition);
}

.lw-select.open .lw-select-chevron {
    transform: rotate(180deg);
}

.lw-select-overlay {
    position: fixed;
    inset: 0;
    background: transparent;
    z-index: 100;
}

.lw-select-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 101;
    list-style: none;
    margin: 0;
    padding: 4px;
    background: var(--bg-secondary);
    border: 1px solid var(--card-border);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
    max-height: 320px;
    overflow-y: auto;
    animation: lw-select-pop 0.12s ease;
}

@keyframes lw-select-pop {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.lw-select-option {
    padding: 10px 12px;
    border-radius: var(--radius);
    cursor: pointer;
    color: var(--text);
    font-size: var(--type-body-size);
    line-height: 1.3;
    user-select: none;
}

.lw-select-option:hover {
    background: var(--card-hover);
}

.lw-select-option.selected {
    background: var(--accent);
    color: #fff;
}

/* ============================================================
   Feedback Button + Modal
   Small flag icon in the corner of content cards, opens a modal
   with category chips and a free-text body. Used by both the
   in-context report (ContentReport) and the global feedback
   form (GeneralFeedback) — same component, different defaults.
   ============================================================ */

.feedback-button-host {
    /* Inline so the host can position the button via flex/absolute as needed. */
    display: inline-block;
    position: relative;
}

.feedback-button {
    width: 28px;
    height: 28px;
    padding: 0;
    /* Visible ring + fill so the flag reads as a tappable button, not a faint glyph (#502). */
    border: 1px solid rgba(var(--on-surface-rgb), 0.22);
    background: rgba(var(--on-surface-rgb), 0.06);
    color: var(--text-secondary);
    border-radius: 50%;
    cursor: pointer;
    font-size: 13px; /* skip(c): fixed 28×28 circle, single icon glyph */
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Was 0.55 — looked disabled. Keep it quiet but legibly present. */
    opacity: 0.9;
    transition: all var(--transition);
}

.feedback-button:hover {
    background: rgba(var(--accent-rgb), 0.12);
    border-color: rgba(var(--accent-rgb), 0.5);
    color: var(--accent);
    opacity: 1;
}

.feedback-button:active {
    transform: scale(0.94);
}

/* Inline placement: the flag trails the translation / grammar explanation text
   (#502) instead of floating in the card header or a separate footer row. Nudge it
   off the last word and centre it on the text line. */
.popup-report-flag {
    margin-left: 6px;
    vertical-align: middle;
}

/* The feedback modal is centered by its flex parent (.modal-overlay), NOT by
   left:50% like .modal-card — so it must NOT reuse the shared `zoomIn` keyframe,
   whose `translateX(-50%)` is the horizontal-centering hack for absolutely-placed
   cards. Borrowing it shifted the modal half its width to the left for the whole
   animation, then snapped it back to center when the (fill-mode:none) animation
   ended. This vertical-only keyframe keeps it centered the entire time. */
@keyframes feedbackZoomIn {
    from {
        opacity: 0;
        transform: translateY(10px) scale(0.98);
    }

    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

/* Modal card itself — slightly tighter than the word-card modal because the
   content is a short form, not a full card. Reuses .modal-overlay from above. */
.feedback-modal {
    background: var(--bg);
    width: 100%;
    max-width: 480px;
    max-height: 90dvh;
    overflow-y: auto;
    border-radius: var(--radius);
    padding: 24px 20px 20px;
    position: relative;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
    animation: feedbackZoomIn 0.18s ease-out;
}

.feedback-modal__title {
    font-size: var(--type-title-m-size);
    font-weight: 700;
    color: var(--text);
    margin: 0 0 4px;
    padding-right: 32px;
}

.feedback-modal__subtitle {
    font-size: var(--type-body-size);
    color: var(--text-secondary);
    margin: 0 0 20px;
    line-height: 1.4;
}

.feedback-modal__section {
    margin-bottom: 16px;
}

.feedback-modal__label {
    display: block;
    font-size: var(--type-body-s-size);
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 8px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.feedback-modal__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.feedback-modal__chip {
    padding: 8px 14px;
    border: 1px solid var(--card-border);
    border-radius: 999px;
    background: var(--card-bg);
    color: var(--text);
    font-size: var(--type-body-size);
    cursor: pointer;
    transition: all var(--transition);
}

.feedback-modal__chip:hover {
    background: var(--card-hover);
}

.feedback-modal__chip--selected {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}

.feedback-modal__textarea {
    width: 100%;
    min-height: 96px;
    padding: 12px;
    border: 1px solid var(--card-border);
    border-radius: var(--radius-sm);
    background: var(--card-bg);
    color: var(--text);
    font-size: var(--type-body-l-size);
    font-family: inherit;
    line-height: 1.5;
    resize: vertical;
    transition: border-color var(--transition);
}

.feedback-modal__textarea:focus {
    outline: none;
    border-color: var(--accent);
}

.feedback-modal__error {
    color: #d04545;
    font-size: var(--type-body-size);
    margin-bottom: 12px;
}

.feedback-modal__success {
    padding: 24px 16px;
    text-align: center;
    color: var(--text);
    font-size: var(--type-body-size);
}

.feedback-modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 8px;
}

/* Legal footer — three small links to the static legal pages, rendered below the
   AppShell. Compact + low-contrast so it doesn't compete with the navigation. */
.legal-footer {
    padding: 18px 16px 28px;
    text-align: center;
    font-size: var(--type-body-s-size);
    color: rgba(var(--on-surface-rgb), 0.5);
}
.legal-footer--hidden { display: none; }
.legal-footer__links { display: inline-flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: center; }
.legal-footer__links a { color: inherit; text-decoration: none; }
.legal-footer__links a:hover { color: rgba(var(--on-surface-rgb), 0.85); text-decoration: underline; }
.legal-footer__dot { color: rgba(var(--on-surface-rgb), 0.3); }

/* ============================================================
   Editorial atoms (Ink & Paper) — reusable, theme-driven building
   blocks for the redesigned screens. Namespaced `ed-` so they never
   collide with a host's bespoke .chip/.stat/.section-label. Compose
   with the .lw-type-* utilities + tokens; no hardcoded colors.
   ============================================================ */

/* mono uppercase eyebrow / kicker */
.ed-eyebrow {
    font-family: var(--font-mono);
    font-size: var(--type-caption-size);
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--hint);
}
.ed-eyebrow--accent { color: var(--accent); }

/* section label — mono kicker with a trailing hairline */
.ed-section-label {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 30px 0 14px;
    font-family: var(--font-mono);
    font-size: var(--type-caption-size);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--hint);
}
.ed-section-label::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(var(--on-surface-rgb), 0.08);
}

/* hairline rules */
.ed-rule { height: 1px; background: var(--card-border); border: 0; margin: 0; }
.ed-rule-soft { height: 1px; background: rgba(var(--on-surface-rgb), 0.07); border: 0; margin: 0; }

/* serif heading convenience */
.ed-h-serif { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.015em; color: var(--text); }

/* editorial stat row — numbers + ruled cells, no shadowed cards */
.ed-statrow {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    margin: 22px 0 4px;
    border-top: 1px solid var(--card-border);
    border-bottom: 1px solid var(--card-border);
}
.ed-stat { padding: 14px 4px; text-align: left; position: relative; }
.ed-stat + .ed-stat::before {
    content: "";
    position: absolute;
    left: 0;
    top: 14px;
    bottom: 14px;
    width: 1px;
    background: rgba(var(--on-surface-rgb), 0.07);
}
.ed-stat__label {
    font-family: var(--font-mono);
    font-size: var(--type-caption-size);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--hint);
    margin-bottom: 6px;
}
.ed-stat__val {
    font-family: var(--font-display);
    font-size: var(--type-num-l-size); font-variant-numeric: tabular-nums;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--text);
}
.ed-stat__val sup { font-size: var(--type-body-s-size); font-weight: 500; color: var(--text-secondary); vertical-align: super; margin-left: 2px; }
.ed-stat__sub { font-size: var(--type-body-s-size); color: var(--text-secondary); margin-top: 4px; }
.ed-stat--accent .ed-stat__val { color: var(--accent); }

/* filter chips — text + underline, not pills */
.ed-chips { display: flex; gap: 16px; padding: 2px 0 4px; overflow-x: auto; scrollbar-width: none; }
.ed-chips::-webkit-scrollbar { display: none; }
.ed-chip {
    font-size: var(--type-body-s-size);
    color: var(--text-secondary);
    cursor: pointer;
    padding: 0 0 5px;
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    white-space: nowrap;
    font-family: var(--font);
}
.ed-chip.is-active { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }

/* editorial buttons — flat, hairline ghost / solid terracotta */
.ed-btn {
    font-size: var(--type-body-size);
    font-weight: 600;
    border-radius: var(--radius-sm);
    cursor: pointer;
    border: 1px solid transparent;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 0 18px;
    height: 44px;
    font-family: var(--font);
    transition: background var(--transition), border-color var(--transition), opacity var(--transition);
}
.ed-btn--solid { background: var(--accent); color: var(--button-text); }
.ed-btn--solid:hover { opacity: 0.92; }
.ed-btn--ghost { background: transparent; color: var(--text); border-color: var(--card-border); }
.ed-btn--ghost:hover { border-color: var(--accent); color: var(--accent); }

/* ruled-row list — replaces stacks of soft cards */
.ed-ruled { display: flex; flex-direction: column; }
.ed-ruled__row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 15px 2px;
    cursor: pointer;
    border-bottom: 1px solid rgba(var(--on-surface-rgb), 0.07);
}
.ed-ruled__row:first-child { border-top: 1px solid rgba(var(--on-surface-rgb), 0.07); }

/* accent-tinted callout (memory-orbit / auto-learned notes) */
.ed-note {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    border: 1px solid var(--card-border);
    border-radius: var(--radius-sm);
    background: rgba(var(--accent-rgb), 0.06);
}

/* ============================================================
   Button size grid (typography migration §6, 2026-07).
   Opt-in utilities: new buttons compose one of these instead of
   ad-hoc height/font/radius. NOT pill — the tokens.css doctrine
   reserves 999px for true circles (avatars, toggles). Existing
   buttons migrate gradually as they're touched.
   ============================================================ */
.lw-btn-lg {
    min-height: 54px;
    font-size: var(--type-body-l-size);
    font-weight: 700;
    border-radius: var(--radius);
}
.lw-btn-md {
    min-height: 44px;
    font-size: var(--type-title-s-size);
    font-weight: 600;
    border-radius: var(--radius);
}
.lw-btn-sm {
    min-height: 34px;
    font-size: calc(13px * var(--ui-scale, 1));
    font-weight: 600;
    border-radius: var(--radius-sm);
}
.lw-btn-icon {
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* ============================================================
   Button base + variants (home redesign, 2026-07).
   Composition: .lw-btn + a size from the grid above + a variant.
   Colour encodes WEIGHT, not meaning: one --accent fill per card
   (its main action), everything else neutral outline. --success is
   deliberately NOT a button colour here — it is spoken for by
   "correct / learned" in the exercises, and a second meaning would
   blunt that signal.
   ============================================================ */
.lw-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    width: 100%;              /* full width of its container by default */
    min-width: 0;             /* REQUIRED: without it a long label in .lw-btn-row stretches
                                 its own column and the pair stops being equal (shows up at
                                 115–130 % interface scale and on long localisations) */
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-family: inherit;
    line-height: 1.2;
    letter-spacing: -0.01em;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition);
}
.lw-btn:disabled { opacity: 0.5; cursor: default; }
.lw-btn > svg, .lw-btn > .lw-icon { flex: none; }
/* The icon button is a fixed square — keep the base's width: 100% off it. */
.lw-btn.lw-btn-icon { width: 36px; }

/* The card's main action — exactly one. */
.lw-btn--primary { background: var(--accent); color: var(--button-text); }
.lw-btn--primary:not(:disabled):hover { background: var(--accent-strong, var(--accent)); }

/* Everything else. */
.lw-btn--secondary {
    background: rgba(var(--on-surface-rgb), 0.05);
    border-color: var(--card-border);
    color: var(--text);
}
.lw-btn--secondary:not(:disabled):hover { background: rgba(var(--on-surface-rgb), 0.09); }

/* Destructive — only where it already exists (sign out, delete). */
.lw-btn--danger {
    background: transparent;
    border-color: rgba(var(--destructive-rgb), 0.5);
    color: var(--destructive);
}

/* Muted tail inside a label: "· ~8 min" */
.lw-btn-est { opacity: 0.78; font-weight: 600; }

/* Layouts. Always full width of the parent. */
.lw-btn-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.lw-btn-col { display: grid; gap: 8px; }

@media (max-width: 640px) {
    /* Bigger touch targets on the phone. */
    .lw-btn-lg { min-height: 56px; }
    .lw-btn-md { min-height: 48px; }
    .lw-btn-sm { min-height: 40px; }
}


/* ============================================================
   Lesson block frame (#1620).
   A sitting is assembled from blocks that each live in their OWN component —
   SessionPreviewBlock, SessionWriteBlock, the grammar drill — mounted into the
   session screen. Blazor scopes CSS per component, so the session screen's own
   card rules could never reach them: SessionPreviewBlock carried `.session-card`
   markup against rules defined in SessionView.razor.css and therefore rendered
   with no gutter, no title type and no centring at all, which is what made every
   new lesson screen look like a different app (text on the screen edge, a
   different heading size per block).
   These live in the GLOBAL sheet precisely so the frame is the same wherever a
   block is mounted, and so the next block added cannot repeat the mistake.
   ============================================================ */

/* The plain gutter, for a block that lays itself out and only needs to stop
   running into the screen edges (the writing block, the review panel). Same
   16px every page header, .ex-runner and the practice hub use. */
.lw-lesson-block {
    padding-inline: var(--space-4, 16px);
}

/* The centred one-decision card: a line of type, maybe a list, and the button
   that moves the sitting on. */
.lw-lesson-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3, 12px);
    padding: var(--space-6, 28px) var(--space-4, 16px);
    text-align: center;
}

/* Display type, matching the writing block's own h1 — the two are consecutive
   screens of one sitting and used to be set 22px serif against 1.1rem sans. */
.lw-lesson-card-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--type-title-l-size);
    line-height: var(--type-title-l-line);
    font-weight: var(--type-title-l-weight);
    letter-spacing: var(--type-title-l-track);
    color: var(--text);
}

.lw-lesson-card-sub {
    margin: 0;
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    color: var(--text-secondary);
}

/* A block's own content column. Wider than the readable measure would be for
   prose because these hold rows and buttons, not paragraphs. */
.lw-lesson-card > .lw-lesson-body,
.lw-lesson-card > .lw-btn {
    width: 100%;
    max-width: 420px;
}
