/* Phase 124: extracted from pages.css. Source order preserved exactly; every rule
   here matches only admin-panel DOM, and this sheet loads immediately after pages.css at
   every load site, so the cascade is unchanged. Do not add a rule that can match
   another surface — put that in pages.css. */
/* Phase 170 removed two rules that could no longer paint: `.lframe-layout .tab-header.no-border::after`
   suppressed a pseudo-element pages.css stopped creating when the head panel replaced the bordered
   header, and `.admin-override-card` (the red wash on Progress Override) was (0,1,0) against the tab
   panel's own (0,2,0) reset. The CLASS stays on #tab-danger as a hook; the tab's danger signal is now
   its head tile and its title, which .tab-header.danger-header and .admin-override-title paint. */
.admin-override-title{color:var(--danger);margin-top:0;font-size:1.5rem;font-weight:800;display:inline-flex;align-items:center;gap:10px;}
.admin-override-desc{margin-bottom:0;color:var(--text-muted);font-size:0.95rem;line-height:1.5;}
/* Phase 179f (owner: use the width): three across where the pane allows, not a fixed two with half of each card empty. */
.admin-btn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:20px;margin-top:20px;}
/* ── Phase 179: the admin in You's shape (the owner's approved canvas, "Admin in You · 19 Sept") ─────────────
   The learner's header with You current, a wide scene in the admin's light, and the menu beside the open
   section (views/admin.php). The page brings its own gutter and keeps the admin's wider width, exactly as
   .academy-page does; the header's words re-derive the same left edge. */
/* Phase 181 (the owner, 2026-09-20): You's width, header and content — the admin's wider cap put its
   content 217px wider than You's beside the same menu. */
.admin-page{width:100%;max-width:var(--learner-content-max);margin:0 auto;padding:var(--app-content-top) var(--app-content-gutter) 96px;}
.admin-lhero .lhero-inner{max-width:var(--learner-content-max);gap:12px;}
.admin-workspace{display:grid;grid-template-columns:280px minmax(0,1fr);gap:24px;align-items:start;}
.admin-page .tab-content-area{min-width:0;max-width:none;margin:0;padding:0;}
/* ── The menu (Phase 181, revised after the owner's look 2026-09-20) ───────────────────────────────────────
   IT IS YOU'S INDEX, NOT A COPY OF IT: views/admin.php gives the panel `.you-index` and every row
   `.you-nav-row`, so the anatomy comes from profile.css and the two cannot drift again. What drifted while
   this file held a copy: the rows were `.btn` buttons, which do not inherit the page font, so the menu
   rendered in Arial beside You's Nunito. Only the admin's own additions live here.
   ⚠️ (0,2,0), NOT (0,1,0): profile.css loads after this file and hides `.you-index` below 1025px for You's
   phone list. A bare `.admin-index` rule loses that tie and the menu disappears on a phone. */
.admin-page .admin-index{display:flex;flex-direction:column;gap:16px;padding:12px 10px;position:sticky;top:calc(var(--app-header-height) + 16px);}
/* ⚠️ NO INNER SCROLL BOX, and that is the owner's bug report: `overflow-y:auto` with
   `overscroll-behavior:contain` (from the fold-out days) swallowed the wheel, so the page would not scroll
   while the pointer sat over the menu — measured 0px against 240px over You's. Ten rows cannot outgrow the
   window, and You's index has neither property. */
.admin-page .admin-index .you-nav-row{width:100%;}
/* A row that DRILLS says so: a chevron on its right (the owner, 2026-09-20). A row that opens a page has
   none — the mark means "there is another level here", not "this is a link". */
.admin-page .admin-index .you-nav-more{flex:0 0 auto;margin-left:auto;color:var(--text-muted);font-size:0.72rem;opacity:0.75;}
.admin-page .admin-index .admin-nav-badge:not(.hidden)+.you-nav-more{margin-left:8px;}
/* A count is attention, not an action: the coral tint, never the purple pill (the canvas's badges). */
.admin-page .admin-index .admin-nav-badge{margin-left:auto;background:rgba(244,130,92,0.14);color:#A3421F;}
/* The drill: one level at a time inside a viewport that keeps the panel's width and animates its height.
   The level leaving goes out of flow so the height follows the one arriving (admin.js). */
.admin-menu-viewport{position:relative;}
.admin-menu-viewport.is-sliding{overflow:hidden;transition:height 0.26s ease;}
/* ⚠️ NO GAP: the kicker sits INSIDE the group, as You's does, and carries its own 5px. A gap here stacked
   on that margin and left a hole between the title and the first row (the owner, 2026-09-20). */
.admin-menu-level{display:flex;flex-direction:column;}
.admin-menu-viewport.is-sliding .admin-menu-level{transition:transform 0.26s ease,opacity 0.26s ease;}
.admin-menu-level.is-leaving{position:absolute;top:0;left:0;right:0;}
.admin-menu-level.from-right,.admin-menu-level.to-right{transform:translateX(110%);opacity:0;}
.admin-menu-level.from-left,.admin-menu-level.to-left{transform:translateX(-110%);opacity:0;}
/* The way back to the ten sections, above the section's name (the owner's shape). Since Phase 183 it
   is a FULL-WIDTH ROW — admin.js builds it as a `.you-nav-row`, so its anatomy, its hover and its
   focus ring are profile.css's and are not restated here (§ Phase 181: a second copy of that anatomy
   in this file is how the menu drifted into Arial). What is left is only its quieter ink and the gap
   before the section's name. */
.admin-menu-back{margin-bottom:2px;color:var(--text-muted);}
.admin-menu-back .you-nav-glyph{color:var(--text-muted);}
.admin-index-sub,.admin-back{display:none;}
/* Phase 179f: an (i) beside a label holds the note that used to sit under it (ui-contracts.md, the owner's rule on
   descriptive text). Plain case and weight even inside an uppercase kicker; one focus or hover shows the words. */
.admin-info{margin-left:5px;color:var(--text-secondary);font-size:0.82em;font-weight:400;letter-spacing:0;text-transform:none;opacity:0.72;cursor:help;}
.admin-info:hover,.admin-info:focus-visible{opacity:1;color:var(--primary-hover);}
.admin-info:focus-visible{outline:2px solid var(--primary-glow);outline-offset:2px;border-radius:50%;}
/* Text that introduces a table stands clear of its header row, and a list field is wide enough to read. */
.admin-tool-block>.tab-desc+.act-scroll{margin-top:10px;}
#adm-support-settings-root .set-arow-wide .sa-ctl>input.theme-input{width:100%;font-size:0.9rem;}
/* A status or preview row that is still empty takes no room (Languages → Grammar structure drew an empty band), and the
   row above it drops its separator. ⚠️ An empty LIVE region is clipped, never display:none: a region that was not
   rendered when its words arrived is not announced (the Phase 179 detached review; #grammar-gen-preview). */
.set-arow.set-arow-wide:has(>.sa-sub:only-child:empty:not([aria-live])){display:none;}
.set-arow.set-arow-wide:has(>.sa-sub[aria-live]:only-child:empty){position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);border:0;}
.set-arow:has(+.set-arow.set-arow-wide>.sa-sub:only-child:empty){border-bottom:0;}
/* A card-level note under its head reads like every row's sub-line; it rendered at body size, larger than the card's
   own title. A kept-visible safety warning keeps its size on purpose (ui-contracts.md, the one exemption). */
.set-group>.sa-sub:not([data-keep-visible]){font-size:0.78rem;line-height:1.42;color:var(--text-secondary);margin:0 0 8px;}
/* Three one-line tools side by side where they fit (Progress), each one a row on a narrow pane; one row, one height. */
.admin-tool-trio{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:16px;}
.admin-tool-trio>.admin-tool-block{margin:0;}
/* Each tab opens on You's section heading: its title and its one line on the ground, with no raised panel
   and no icon tile (the menu already names the section). ⚠️ Scoped past the material it overrides:
   components.css paints .surface-raised at (0,3,1), so a bare .admin-head would lose. */
body.app-shell-active .admin-page .tab-header.admin-head.surface-raised{padding:0 4px;border:0;border-radius:0;background:none;box-shadow:none;backdrop-filter:none;-webkit-backdrop-filter:none;}
.admin-page .admin-head-ic{display:none;}
.admin-page .admin-head-text h1{font-family:var(--font-display);font-size:1.875rem;line-height:1.15;}
/* The header's words: the eyebrow's two state chips, "Admin" with who is signed in, and the figures. */
.admin-lhero-name{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 14px;}
.admin-state-chip>i{font-size:0.5rem;}
.admin-state-chip>i.fa-clock{font-size:0.78rem;}
.admin-state-chip--production{background:rgba(16,185,129,0.14);color:#0B6B4C;}
.admin-state-chip--staging{background:rgba(244,162,60,0.18);color:#8C5A14;}
.admin-state-chip--local,.admin-state-chip--plain{background:rgba(255,255,255,0.72);color:var(--text-secondary);}
.admin-figures{display:flex;flex-wrap:wrap;gap:10px;}
.admin-figure{display:flex;flex-direction:column;align-items:flex-start;gap:3px;padding:10px 16px;border:1px solid rgba(79,62,143,0.12);border-radius:14px;background:rgba(255,255,255,0.74);color:var(--text-color);font:inherit;text-align:left;cursor:pointer;transition:background 0.16s ease,border-color 0.16s ease;}
.admin-figure[hidden]{display:none;}
.admin-figure:hover{border-color:rgba(79,62,143,0.26);background:rgba(255,255,255,0.94);}
.admin-figure:focus-visible{outline:3px solid var(--primary-glow);outline-offset:2px;}
.admin-figure-value{font-family:var(--font-display);font-size:1.75rem;font-weight:800;line-height:1;}
.admin-figure-label{display:flex;align-items:center;gap:6px;color:var(--text-secondary);font-size:0.78rem;font-weight:700;white-space:nowrap;}
.admin-figure.is-attention .admin-figure-value{color:#A3421F;}
.admin-figure.is-attention .admin-figure-label::before{content:'';flex:0 0 7px;width:7px;height:7px;border-radius:999px;background:#F4825C;}
.override-grid-btn {
    display:flex;
    align-items:center;
    gap:15px;
    padding:16px 20px;
    background:rgba(255,255,255,0.75);
    border:1px solid var(--card-border);
    border-radius:12px;
    cursor:pointer;
    transition:all 0.25s cubic-bezier(0.4,0,0.2,1);
    text-align:left;
    color:var(--text);
    width:100%;
}
body.hyper-theme .override-grid-btn{background:rgba(15,23,42,0.65);}
.override-grid-btn:hover{transform:translateY(-2px);background:rgba(255,255,255,0.95);}
body.hyper-theme .override-grid-btn:hover{background:rgba(15,23,42,0.85);}
.override-btn-icon {
    display:flex;
    align-items:center;
    justify-content:center;
    width:45px;
    height:45px;
    border-radius:10px;
    font-size:1.3rem;
    flex-shrink:0;
    transition:all 0.25s ease;
}
.override-btn-text{display:flex;flex-direction:column;gap:2px;}
.override-btn-title{font-size:1.05rem;font-weight:800;}
.override-btn-desc{font-size:0.8rem;color:var(--text-muted);font-weight:500;}
.override-grid-btn.learn-btn .override-btn-icon{background:rgba(244,162,60,0.1);color:#F4A23C;}
.override-grid-btn.learn-btn:hover{border-color:#F4A23C;box-shadow:0 6px 20px rgba(244,162,60,0.15)!important;}
.override-grid-btn.learn-btn:hover .override-btn-icon{background:#F4A23C;color:#fff;box-shadow:0 0 10px rgba(244,162,60,0.4);}
.override-grid-btn.warp-btn .override-btn-icon{background:rgba(111,95,228,0.1);color:var(--primary);}
.override-grid-btn.warp-btn:hover{border-color:var(--primary);box-shadow:0 6px 20px var(--primary-glow)!important;}
.override-grid-btn.warp-btn:hover .override-btn-icon{background:var(--primary);color:#fff;box-shadow:0 0 10px var(--primary-glow);}
.override-grid-btn.wipe-vocab-btn .override-btn-icon{background:rgba(239,68,68,0.1);color:#ef4444;}
.override-grid-btn.wipe-vocab-btn:hover{border-color:#ef4444;box-shadow:0 6px 20px rgba(239,68,68,0.15)!important;}
.override-grid-btn.wipe-vocab-btn:hover .override-btn-icon{background:#ef4444;color:#fff;box-shadow:0 0 10px rgba(239,68,68,0.4);}
.override-grid-btn.wipe-stats-btn .override-btn-icon{background:rgba(244,63,94,0.1);color:#f43f5e;}
.override-grid-btn.wipe-stats-btn:hover{border-color:#f43f5e;box-shadow:0 6px 20px rgba(244,63,94,0.15)!important;}
.override-grid-btn.wipe-stats-btn:hover .override-btn-icon{background:#f43f5e;color:#fff;box-shadow:0 0 10px rgba(244,63,94,0.4);}

/* Testing toolbelt (Progress Override) */
.admin-snapshot{margin-top:20px;padding:16px 18px;background:rgba(255,255,255,0.6);border:1px solid var(--card-border);border-radius:14px;}
body.hyper-theme .admin-snapshot{background:rgba(15,23,42,0.55);}
.admin-snapshot-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px;}
.admin-snapshot-refresh{width:34px;height:34px;border-radius:50%;border:1px solid var(--card-border);background:var(--card-bg);color:var(--primary);cursor:pointer;font-size:0.9rem;flex-shrink:0;transition:all 0.2s;}
.admin-snapshot-refresh:hover{background:var(--primary);color:#fff;}
.admin-snapshot-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:10px;}
.snap-chip{display:flex;flex-direction:column;align-items:center;gap:3px;padding:10px 6px;background:var(--card-bg);border:1px solid var(--card-border);border-radius:10px;}
.snap-val{font-size:1.35rem;font-weight:800;color:var(--text-color);}
.snap-label{font-size:0.68rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--text-muted);font-weight:700;}
.snap-chip-warn{border-color:rgba(192,86,58,0.5);}
.snap-chip-warn .snap-val{color:#C0563A;}
/* 183: was rgba(255,255,255,0.55) — a 45% see-through card, so the constellations read through every
   tool block in the panel. The card material, like every other surface here. */
.admin-tool-block{margin-top:18px;padding:16px 18px;background:var(--surface-base-bg);border:1px solid var(--card-border);border-radius:14px;}
body.hyper-theme .admin-tool-block{background:rgba(15,23,42,0.5);}
.admin-tool-title{display:flex;align-items:center;gap:8px;font-size:1rem;font-weight:800;margin:0 0 12px;color:var(--text-color);}
.admin-tool-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.admin-lessons-seed-grid{
    display:grid;
    grid-template-columns:minmax(180px,1.45fr) minmax(120px,0.7fr) minmax(130px,0.7fr) auto;
    align-items:end;
    gap:10px;
}
.admin-seed-field{display:flex;flex-direction:column;gap:6px;min-width:0;}
.admin-seed-field>span{color:var(--text-muted);font-size:0.72rem;font-weight:800;letter-spacing:0.04em;text-transform:uppercase;}
.admin-seed-field .dd,.admin-seed-field .admin-tool-input{width:100%;}
.admin-seed-field .admin-tool-input{box-sizing:border-box;}
.admin-seed-pro{display:flex;align-items:flex-start;gap:9px;margin-top:12px;color:var(--text-color);cursor:pointer;}
.admin-seed-pro input{margin-top:3px;accent-color:var(--primary);}
.admin-seed-pro span{display:flex;flex-direction:column;gap:2px;}
.admin-seed-pro strong{font-size:0.86rem;}
.admin-seed-pro small{color:var(--text-muted);font-size:0.76rem;line-height:1.35;}
.admin-tool-label{font-weight:700;color:var(--text-muted);font-size:0.9rem;}
.admin-tool-input{width:90px;padding:10px 12px;background:var(--background);border:2px solid var(--card-border);color:var(--text);border-radius:8px;outline:none;font-family:inherit;font-size:1rem;}
.admin-tool-input:focus{border-color:var(--primary);}
.admin-tool-select{flex:1;min-width:180px;padding:10px 12px;background:var(--background);border:2px solid var(--card-border);color:var(--text);border-radius:8px;outline:none;font-family:inherit;font-size:1rem;cursor:pointer;}
.admin-tool-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 18px;background:var(--primary);color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;transition:background 0.2s;}
.admin-tool-btn:hover{background:var(--primary-hover);}
.admin-tool-btn:disabled{opacity:0.6;cursor:default;}
.admin-tool-hint{margin:10px 0 0;font-size:0.8rem;color:var(--text-muted);}

.terminal-log {
    height:300px;
    overflow-y:auto;
    background:#1e293b;
    color:#cbd5e1;
    font-family:monospace;
    padding:15px;
    border-radius:8px;
    font-size:0.9rem;
    margin-bottom:15px;
    word-break:break-all;
    line-height:1.5;
}

.terminal-log-info{color:#cbd5e1;}
.terminal-log-success{color:#10b981;}
.terminal-log-warning{color:#F4A23C;}
.terminal-log-error{color:#ef4444;}
.tab-header.no-border{border-bottom:none;padding-bottom:0;margin-bottom:16px;}
/* Phase 170: `.tab-header.danger-header{border-bottom-color:…}` went with the rule it coloured — the head panel
   is a raised card now and draws no bottom border at all. The danger signal is the red icon tile and the red
   title (pages.css), both of which this phase kept. */
/* A tab's long-form doctrine, folded behind a quiet disclosure so the controls it describes stay above
   the fold. The chevron is the same idiom the rest of the admin uses for an expander. */
.tab-desc-toggle{margin-top:10px;}
.tab-desc-toggle i{transition:transform 0.18s ease;}
.tab-desc-toggle[aria-expanded="true"] i{transform:rotate(90deg);}
@media(prefers-reduced-motion:reduce){.tab-desc-toggle i{transition:none;}}
.tab-desc-more{display:flex;flex-direction:column;gap:9px;margin-top:10px;}

.sync-panel{background:var(--bg-color);border:1px solid var(--card-border);padding:20px;border-radius:12px;margin-bottom:20px;}
.sync-meta{display:flex;justify-content:space-between;margin-bottom:10px;font-weight:bold;}
.sync-progress-container {
    width:100%;
    height:15px;
    background:#e2e8f0;
    border-radius:10px;
    overflow:hidden;
    margin-bottom:15px;
}
.sync-progress-bar{height:100%;background:var(--primary);transition:width 0.3s;border-radius:10px;}
/* `flex-wrap:wrap` (Phase 171): every other bar in the admin already wraps; this one did not, so at
   390 the last two buttons of Image sync ran to x=679 inside a 366px row. Unpainted that only looked
   like a crowded row; as a quiet bar they spill straight through the panel's edge. */
.admin-btn-group{display:flex;gap:15px;flex-wrap:wrap;}
.btn-gray{background:#94a3b8;border-color:#94a3b8;color:#fff;}
.btn-gray:hover{background:#78889b;border-color:#78889b;}
.manual-results-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:15px;}
.manual-feedback-box {
    margin-top:15px;
    padding:15px;
    border-radius:8px;
    display:none;
    text-align:center;
    font-weight:bold;
}
.manual-img-card{border:2px solid transparent;border-radius:8px;overflow:hidden;cursor:pointer;transition:all 0.2s;}
.manual-img-card-img{width:100%;height:120px;object-fit:cover;display:block;}
.manual-feedback-box.error-feedback{color:#ef4444;background-color:rgba(239,68,68,0.1);border:1px solid #ef4444;}
.manual-feedback-box.success-feedback{color:#10b981;background-color:rgba(16,185,129,0.1);border:1px solid #10b981;}
.manual-img-card.saving{opacity:0.5;pointer-events:none;}
.manual-img-card.saved{opacity:1;border-color:#10b981;}
.manual-img-card:hover:not(.saving):not(.saved){border-color:#38bdf8;}

/* Manual Image Editor — carousel */
.mie-toolbar {
    display:flex;
    justify-content:space-between;
    align-items:center;
    gap:15px;
    margin-bottom:20px;
    flex-wrap:wrap;
}
.mie-filter{display:flex;gap:8px;}
.mie-filter-wrap{display:flex;gap:14px;flex-wrap:wrap;align-items:center;}
.mie-filter-btn,.mie-type-btn{padding:8px 16px;border:1px solid var(--card-border);background:var(--bg-color);border-radius:20px;cursor:pointer;font-weight:700;color:var(--text-muted);}
.mie-filter-btn.active,.mie-type-btn.active{background:var(--primary);border-color:var(--primary);color:#fff;}
.mie-jump{flex:1;min-width:220px;max-width:360px;}
.mie-stage{display:flex;align-items:center;gap:15px;}
.mie-nav-btn {
    flex-shrink:0;
    width:52px;
    height:52px;
    border-radius:50%;
    border:1px solid var(--card-border);
    background:var(--bg-color);
    cursor:pointer;
    font-size:1.2rem;
    color:var(--text-color);
    transition:all 0.15s;
}
.mie-nav-btn:hover{background:var(--primary);color:#fff;border-color:var(--primary);}
.mie-stage-main{flex:1;min-width:0;}
.mie-card {
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:16px;
    padding:24px;
    border:1px solid var(--card-border);
    border-radius:16px;
    background:var(--bg-color);
}
.mie-image-wrap{position:relative;width:100%;max-width:440px;}
.mie-image{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:12px;display:block;background:#f1f5f9;}
.mie-missing-badge{position:absolute;top:10px;left:10px;background:var(--danger);color:#fff;padding:4px 10px;border-radius:8px;font-size:0.8rem;font-weight:700;}
.mie-info{text-align:center;}
.mie-pos{margin-bottom:8px;}
.mie-gloss{margin:0 0 4px;font-size:1.4rem;}
.mie-desc{margin:0;color:var(--text-muted);font-style:italic;}
.mie-counter{display:inline-block;margin-top:8px;font-weight:700;color:var(--text-muted);font-size:0.9rem;}
.mie-replace-btn{min-width:200px;}
.mie-card-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;}
.mie-image.is-space{display:flex;align-items:center;justify-content:center;font-size:2.4rem;}
.mie-ai-review {
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:10px;
    margin-top:14px;
    padding:14px 16px;
    border-radius:12px;
    background:rgba(111,95,228,0.06);
    border:1px dashed rgba(111,95,228,0.35);
}
.mie-ai-review-label{font-weight:600;color:var(--primary);font-size:0.95rem;}
.mie-ai-review-btns{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;}
.ai-img-input{width:100%;max-width:520px;font-family:ui-monospace,monospace;font-size:0.82rem;}
/* Selection mode: the panel replaces the card in place, so it carries the concept info. */
.mie-replace-panel{padding:24px;border:1px solid var(--card-border);border-radius:16px;background:var(--bg-color);}
.mie-replace-head{text-align:center;margin-bottom:18px;}
.mie-replace-head .mie-gloss{margin-top:4px;}
.mie-search-row{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap;}
.mie-search-row .theme-input{flex:1;min-width:160px;}
/* Bounded results so the panel never pushes the page into a scroll. */
.mie-replace-panel .manual-results-grid{grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;max-height:55vh;overflow-y:auto;}
.mie-replace-panel .manual-img-card-img{height:110px;}
.mie-hint{text-align:center;color:var(--text-muted);font-size:0.85rem;margin-top:16px;}
.mie-hint kbd{background:#e2e8f0;border:1px solid var(--card-border);border-radius:4px;padding:1px 6px;font-family:monospace;font-size:0.8rem;}

/* Vocabulary Studio (admin) */
/* 70: no chrome — the tab is already a card and each .set-group is its own card (Concept A);
   the old background+border made a visible box-in-a-box. */
.vocab-settings{margin:0;padding:0;background:none;border:none;}
.academy-model-input.input-invalid{border-color:var(--danger);box-shadow:0 0 0 2px var(--danger-glow);} /* FU4 — keep the Voice-model box off the first language row below */
.vocab-langs{max-width:none;}
.vocab-langs .set-group-note{max-width:82ch;}
.vocab-lang-form{display:grid;grid-template-columns:minmax(0,1fr) 116px 132px;gap:12px;margin:14px 0;}
.vocab-lang-form > label{display:flex;flex-direction:column;gap:4px;font-size:0.8rem;color:var(--text-muted);}
.vocab-lang-form input:not([type=checkbox]){width:100%;padding:9px 11px;border:1px solid var(--card-border);border-radius:8px;font-size:0.9rem;background:var(--bg-color);color:var(--text-color);}
.vocab-lang-form .rl-variant-lab{grid-column:1/-1;}
.vocab-lang-form label.vocab-check{flex-direction:row;align-items:center;gap:6px;padding:8px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);color:var(--text-color);}
.vocab-langs>#rl-save{margin-top:2px;}
.vocab-lang-list{margin-top:20px;padding-top:15px;border-top:1px solid var(--card-border);display:flex;flex-direction:column;gap:8px;}
.vocab-lang-list-title{font-size:0.9rem;color:var(--text-color);margin:0 0 4px;}
.vocab-lang-list-title span{font-weight:400;color:var(--text-muted);font-size:0.8rem;}
.vocab-lang-row{
    display:flex;
    align-items:center;
    justify-content:space-between;
    flex-wrap:wrap;
    gap:6px 12px;
    padding:10px 14px;
    border:1px solid var(--card-border);
    border-radius:10px;
    background:var(--card-bg);
    cursor:pointer;
    text-align:left;
}
.vocab-lang-row:hover{border-color:var(--primary);background:var(--bg-color);}
.vocab-lang-row-main{display:inline-flex;align-items:center;gap:8px;color:var(--text-color);}
.vocab-lang-code{color:var(--text-muted);font-size:0.8rem;text-transform:uppercase;}
.vocab-lang-row-grammar{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border:1px solid transparent;border-radius:999px;font-size:0.72rem;font-weight:700;white-space:nowrap;}
.vocab-lang-row-grammar.is-built-in{border-color:rgba(111,95,228,0.22);background:rgba(111,95,228,0.1);color:var(--primary-hover);}
.vocab-lang-row-grammar.is-generated{border-color:rgba(16,185,129,0.24);background:rgba(16,185,129,0.1);color:var(--success);}
.vocab-lang-row-grammar.is-missing{border-color:rgba(244,162,60,0.28);background:rgba(244,162,60,0.11);color:#a56205;}
.vocab-lang-row-grammar.is-checking,.vocab-lang-row-grammar.is-unavailable{border-color:var(--card-border);background:var(--bg-color);color:var(--text-muted);}
.vocab-lang-row-counts{color:var(--text-muted);font-size:0.82rem;white-space:nowrap;}
/* 71.5 prompt-template editor (Academy -> Settings -> Generation) */
.prompt-tpl-list{display:flex;flex-direction:column;gap:14px;}
.prompt-tpl{border:1px solid var(--card-border);border-radius:10px;padding:12px 14px;background:var(--card-bg);}
.prompt-tpl-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px;}
.prompt-tpl-name{font-weight:600;color:var(--text-color);}
.prompt-tpl-badge{font-size:0.72rem;padding:2px 8px;border-radius:999px;background:var(--primary);color:#fff;}
.prompt-tpl-hint{font-size:0.78rem;line-height:1.45;color:var(--text-muted);margin:0 0 8px;}
.prompt-tpl-ph{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px;}
.prompt-tpl-ph code{font-size:0.72rem;padding:2px 7px;border:1px solid var(--card-border);border-radius:6px;color:var(--text-muted);}
.prompt-tpl-ph code.missing{border-color:var(--danger);color:var(--danger);}
.prompt-tpl textarea{width:100%;min-height:260px;resize:vertical;padding:10px 12px;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);color:var(--text-color);font-family:ui-monospace,Consolas,monospace;font-size:0.78rem;line-height:1.5;}
.prompt-tpl-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px;}
.prompt-tpl-meta{margin-left:auto;font-size:0.75rem;color:var(--text-muted);font-variant-numeric:tabular-nums;}
.vocab-lang-row-variant{margin-right:auto;color:var(--text-muted);font-size:0.8rem;padding:2px 8px;border:1px solid var(--card-border);border-radius:999px;}
.vocab-lang-row-variant em{font-style:normal;opacity:0.65;}
.rl-variant-lab{flex:1 1 260px;min-width:220px;}
.vocab-stats-wrap{margin-top:8px;}
.vocab-stats-scroll{overflow-x:auto;}
.vocab-stats-table{width:100%;border-collapse:collapse;font-size:0.88rem;}
.vocab-stats-table th{
    text-align:right;
    padding:10px 12px;
    color:var(--text-muted);
    font-weight:700;
    border-bottom:2px solid var(--card-border);
    white-space:nowrap;
}
.vocab-stats-table th:first-child,.vocab-stats-table td:first-child{text-align:left;}
.vocab-stats-table td{text-align:right;padding:9px 12px;border-bottom:1px solid var(--card-border);white-space:nowrap;}
.vocab-stats-table tbody tr:hover{background:rgba(111,95,228,0.04);}
.vocab-lang{font-weight:700;color:var(--text-color);}
.vocab-lang .fi{margin-right:8px;}
.vocab-gap{
    display:inline-block;
    background:var(--accent-glow);
    color:var(--accent-hover);
    font-weight:800;
    padding:2px 9px;
    border-radius:999px;
}
.vocab-ok{color:var(--text-muted);}
.vocab-search-row{display:flex;gap:10px;margin-bottom:12px;flex-wrap:wrap;}
.vocab-search-input{
    flex:1;
    min-width:180px;
    padding:9px 12px;
    border:1px solid var(--card-border);
    border-radius:10px;
    background:var(--card-bg);
    color:var(--text-color);
    font-size:0.9rem;
}
.vocab-results{display:flex;flex-direction:column;gap:4px;margin-bottom:14px;}
.vocab-result{
    text-align:left;
    background:var(--bg-color);
    border:1px solid var(--card-border);
    border-radius:8px;
    padding:8px 12px;
    cursor:pointer;
    font-size:0.9rem;
    color:var(--text-color);
}
.vocab-result:hover{background:rgba(111,95,228,0.06);}
.vocab-result span{color:var(--text-muted);margin-left:6px;}
.vocab-empty{color:var(--text-muted);font-size:0.88rem;padding:6px 0;}
.vocab-edit-card{background:var(--bg-color);border:1px solid var(--card-border);border-radius:14px;padding:18px;}
.vocab-edit-title{font-size:1.15rem;margin-bottom:14px;color:var(--text-color);}
.vocab-etabs{display:flex;gap:6px;flex-wrap:wrap;border-bottom:1px solid var(--card-border);margin:0 0 14px;}
.vocab-etab{
    border:none;
    background:none;
    color:var(--text-muted);
    font-weight:700;
    font-size:0.85rem;
    padding:8px 12px;
    cursor:pointer;
    border-bottom:2px solid transparent;
    display:inline-flex;
    align-items:center;
    gap:6px;
}
.vocab-etab:hover{color:var(--text-color);}
.vocab-etab.active{color:var(--primary);border-bottom-color:var(--primary);}
.vocab-subsection{margin-top:18px;padding-top:14px;border-top:1px solid var(--card-border);}
.vocab-subsection h5{margin:0 0 10px;font-size:0.78rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);}
.vocab-pos{font-size:0.8rem;color:var(--text-muted);font-weight:600;}
.vocab-sense,.vocab-def{background:var(--card-bg);border:1px solid var(--card-border);border-radius:10px;padding:12px;margin-bottom:10px;}
.vocab-sense-head,.vocab-def-head{font-weight:700;color:var(--text-color);margin-bottom:8px;}
.vocab-fields{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;margin-bottom:10px;}
.vocab-field{display:flex;flex-direction:column;gap:4px;font-size:0.78rem;color:var(--text-muted);}
.vocab-fields .vs-gender,.vocab-fields .vs-display{
    padding:7px 10px;
    border:1px solid var(--card-border);
    border-radius:8px;
    font-size:0.88rem;
    background:var(--card-bg);
    color:var(--text-color);
}
.vocab-check{flex-direction:row;align-items:center;gap:6px;}
.vocab-ex-label{font-size:0.78rem;color:var(--text-muted);margin:8px 0 6px;}
.vocab-ex-label span{opacity:0.7;}
.vocab-ex-row{
    display:flex;
    justify-content:space-between;
    align-items:center;
    gap:8px;
    padding:5px 0;
    font-size:0.88rem;
    border-bottom:1px dashed var(--card-border);
}
.vocab-ex-row em{color:var(--text-muted);}
.vocab-ex-add{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap;}
.vocab-ex-add input{
    flex:1;
    min-width:120px;
    padding:7px 10px;
    border:1px solid var(--card-border);
    border-radius:8px;
    font-size:0.85rem;
    background:var(--card-bg);
    color:var(--text-color);
}
.vocab-x{background:none;border:none;color:#ef4444;cursor:pointer;font-size:1.1rem;line-height:1;padding:0 4px;}
.vocab-ex-add select{
    padding:7px 10px;
    border:1px solid var(--card-border);
    border-radius:8px;
    font-size:0.85rem;
    background:var(--card-bg);
    color:var(--text-color);
}
.vocab-switch{display:inline-flex;align-items:center;gap:8px;font-size:0.82rem;color:var(--text-color);cursor:pointer;}
.vocab-switch input{position:absolute;opacity:0;width:0;height:0;}
.vocab-switch-track{
    position:relative;
    width:38px;
    height:22px;
    background:var(--card-border);
    border-radius:999px;
    transition:background 0.2s;
    flex-shrink:0;
}
.vocab-switch-track::after{
    content:"";
    position:absolute;
    top:2px;
    left:2px;
    width:18px;
    height:18px;
    background:#fff;
    border-radius:50%;
    transition:transform 0.2s;
    box-shadow:0 1px 3px rgba(0,0,0,0.2);
}
.vocab-switch input:checked + .vocab-switch-track{background:var(--primary);}
.vocab-switch input:checked + .vocab-switch-track::after{transform:translateX(16px);}
.vocab-chips,.vocab-chip-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px;}
.vocab-chip-lbl{font-size:0.72rem;font-weight:800;text-transform:uppercase;color:var(--text-muted);width:30px;}
.vocab-chip{
    display:inline-flex;
    align-items:center;
    gap:6px;
    background:var(--card-bg);
    border:1px solid var(--card-border);
    border-radius:999px;
    padding:4px 6px 4px 12px;
    font-size:0.85rem;
    color:var(--text-color);
}
.vocab-chip.is-syn{border-color:rgba(111,95,228,0.4);}
.vocab-chip.is-ant{border-color:rgba(244,130,92,0.45);}
.vocab-chip-str{
    border:none;
    background:rgba(111,95,228,0.12);
    color:var(--primary);
    font-size:0.68rem;
    font-weight:800;
    text-transform:uppercase;
    padding:2px 7px;
    border-radius:999px;
    cursor:pointer;
}
.vocab-chip-str.is-distant{background:var(--bg-color);color:var(--text-muted);}
.vocab-chip-x{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:1.1rem;line-height:1;padding:0 2px;}
.vocab-chip-x:hover{color:#ef4444;}
.vocab-thes-def{background:var(--card-bg);border:1px solid var(--card-border);border-radius:10px;padding:12px;margin-bottom:10px;}
.vocab-infl-group{margin-bottom:14px;}
.vocab-infl-ft{font-size:0.72rem;font-weight:800;text-transform:uppercase;letter-spacing:0.05em;color:var(--primary);margin-bottom:6px;}
.vocab-infl-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;}
.vocab-infl-person{font-size:0.78rem;color:var(--text-muted);min-width:80px;}
.vocab-infl-row .vi-form{
    flex:1;
    max-width:240px;
    padding:7px 10px;
    border:1px solid var(--card-border);
    border-radius:8px;
    font-size:0.88rem;
    background:var(--bg-color);
    color:var(--text-color);
}
.vocab-profile-row{
    display:grid;
    grid-template-columns:minmax(150px,0.8fr) minmax(280px,2fr) auto;
    align-items:end;
    gap:12px;
    background:var(--card-bg);
    border:1px solid var(--card-border);
    border-radius:10px;
    padding:12px;
    margin-bottom:10px;
}
.vocab-profile-form-head{display:flex;flex-direction:column;gap:3px;align-self:center;}
.vocab-profile-form-head span{font-size:0.74rem;color:var(--text-muted);overflow-wrap:anywhere;}
.vocab-profile-reps{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;min-width:0;}
.vocab-profile-reps .vocab-field em{font-size:0.68rem;color:var(--primary);font-style:normal;}
.vocab-profile-reps input{width:100%;min-width:0;box-sizing:border-box;}
.vocab-profile-form-actions{display:flex;align-items:center;gap:6px;}
.vocab-profile-add{display:grid;grid-template-columns:minmax(180px,0.8fr) minmax(280px,2fr) auto;align-items:end;gap:12px;}
.vocab-profile-add>.dd{width:100%;align-self:end;}
.vocab-addform{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-bottom:14px;}
.vocab-addform label{display:flex;flex-direction:column;gap:4px;font-size:0.8rem;color:var(--text-muted);}
.vocab-addform input:not([type=checkbox]){
    padding:8px 11px;
    border:1px solid var(--card-border);
    border-radius:8px;
    font-size:0.9rem;
    background:var(--card-bg);
    color:var(--text-color);
}
.vocab-adderr{color:#ef4444;font-size:0.85rem;margin-top:8px;}
.vocab-addform-actions{display:flex;gap:10px;flex-wrap:wrap;}
.vocab-gen-langrow{margin-bottom:14px;}
/* Accordion generator cards (Phase 37) — collapsed to a single head row showing a spinner when
   running, the title, the pending summary and a caret. One card expands at a time. Cards render
   from the config in admin-generators.js under small group headers. */
.vocab-gen-group{margin:16px 0 8px;font-size:0.78rem;font-weight:800;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);}
.vocab-gen-card{background:var(--bg-color);border:1px solid var(--card-border);border-radius:12px;margin-bottom:8px;overflow:hidden;}
.vocab-gen-head{
    display:flex;
    align-items:center;
    gap:10px;
    width:100%;
    padding:10px 14px;
    border:none;
    background:none;
    font:inherit;
    text-align:left;
    cursor:pointer;
}
.vocab-gen-head:hover{background:rgba(111,95,228,0.05);}
.vocab-gen-spin{color:var(--primary);flex-shrink:0;}
.vocab-gen-light{width:10px;height:10px;border-radius:50%;flex-shrink:0;background:#c9c9d4;box-shadow:0 0 0 2px rgba(0,0,0,0.04);transition:background 0.2s ease;}
.vocab-gen-light.is-green{background:#37c26b;box-shadow:0 0 0 2px rgba(55,194,107,0.18);}
.vocab-gen-light.is-yellow{background:#f2b134;box-shadow:0 0 0 2px rgba(242,177,52,0.2);}
.vocab-gen-light.is-red{background:#e5533c;box-shadow:0 0 0 2px rgba(229,83,60,0.18);}
/* Phase 60: position of this generator in its tab's run sequence. Generators outside a sequence render
   an `.is-empty` chip — invisible but space-holding, so every card title lines up on the same x. */
.vocab-gen-seqno{display:grid;place-items:center;min-width:19px;height:19px;padding:0 5px;border-radius:6px;background:rgba(111,95,228,0.13);border:1px solid rgba(111,95,228,0.18);color:var(--text-muted);font-size:0.7rem;font-weight:800;font-variant-numeric:tabular-nums;flex-shrink:0;}
.vocab-gen-seqno.is-empty{background:transparent;border-color:transparent;}
/* Phase 61: terms the generator parked after repeated failures — never hidden, always restorable. */
.vocab-gen-skips{margin-top:0.5rem;}
.vocab-gen-skiplist{margin-top:0.5rem;display:flex;flex-direction:column;gap:0.35rem;}
.vocab-skip-row{display:flex;align-items:center;gap:0.6rem;padding:0.45rem 0.6rem;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);}
.vocab-skip-term{flex:none;font-size:0.86rem;}
.vocab-skip-why{flex:1;min-width:0;font-size:0.76rem;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vocab-skip-all{align-self:flex-start;}
.vocab-gen-review{padding:0.5rem 0.65rem;border:1px solid rgba(229,83,60,0.28);border-radius:8px;background:rgba(229,83,60,0.05);}
.vocab-blocker-toggle{width:100%;justify-content:flex-start;color:var(--text-color);}
.vocab-blocker-list{display:flex;flex-direction:column;gap:0.4rem;margin-top:0.45rem;}
.vocab-blocker-row{display:flex;align-items:center;gap:0.6rem;padding-top:0.4rem;border-top:1px solid var(--card-border);}
.vocab-blocker-copy{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px;font-size:0.82rem;}
.vocab-blocker-copy small,.vocab-blocker-empty{color:var(--text-muted);font-size:0.74rem;overflow-wrap:anywhere;}
.vocab-blocker-all{align-self:flex-start;}
.vocab-gen-workflow{display:flex;align-items:center;gap:0.75rem;padding:0.55rem 0.7rem;border:1px solid rgba(111,95,228,0.24);border-radius:8px;background:rgba(111,95,228,0.06);}
.vocab-gen-workflow>span{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px;font-size:0.82rem;}
.vocab-gen-workflow small{color:var(--text-muted);font-size:0.74rem;}
.vocab-gen-light.is-off{background:#c9c9d4;}
.vocab-gen-title{color:var(--text-color);font-size:0.95rem;flex-shrink:0;}
.vocab-gen-engine{color:var(--text-muted);font-size:0.8rem;flex-shrink:0;opacity:0.7;margin-left:-4px;}
.vocab-gen-headstatus{flex:1;min-width:0;text-align:right;color:var(--text-muted);font-size:0.82rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.vocab-gen-caret{color:var(--text-muted);font-size:0.75rem;transition:transform 0.2s ease;flex-shrink:0;}
.vocab-gen-card:not(.is-collapsed) .vocab-gen-caret{transform:rotate(180deg);}
.vocab-gen-body{padding:0 16px 12px;display:flex;flex-direction:column;gap:10px;}
.vocab-gen-desc{color:var(--text-muted);font-size:0.85rem;}
/* Phase 104 WS-B — where a generator's output goes. The default is the quiet informational line; the
   .is-live variant is the loud one because it marks the EXCEPTION (no review layer), and an operator
   assuming a review step that is not there would publish unreviewed machine content. */
.vocab-gen-dest{display:flex;align-items:flex-start;gap:7px;font-size:0.8rem;line-height:1.45;color:var(--text-muted);padding:7px 10px;border-radius:8px;border:1px solid var(--card-border);background:var(--card-bg);}
.vocab-gen-dest i{margin-top:2px;flex:none;color:var(--primary);}
.vocab-gen-dest.is-live{color:#b8730a;border-color:rgba(244,162,60,0.4);background:rgba(244,162,60,0.1);font-weight:600;}
.vocab-gen-dest.is-live i{color:#b8730a;}
.vocab-gen-lastrun{color:var(--text-muted);font-size:0.8rem;font-style:italic;}
.vocab-gen-controls{display:flex;align-items:center;gap:12px;}
.vocab-gen-controls .btn{padding:5px 14px;font-size:0.82rem;}
.vocab-gen-num{width:56px;padding:6px 8px;border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);color:var(--text-color);font-size:0.9rem;text-align:center;}
.vocab-gen-num-wide{width:84px;}
.vocab-gen-status{flex:1;color:var(--text-muted);font-size:0.85rem;}
.vocab-gen-bar{height:6px;background:var(--card-border);border-radius:999px;overflow:hidden;}
.vocab-gen-fill{height:100%;width:0;background:var(--primary);border-radius:999px;transition:width 0.3s ease;}
.vocab-insights-rows{display:flex;flex-direction:column;gap:5px;font-size:0.85rem;color:var(--text-muted);}
.vocab-insight strong{color:var(--text-color);}
.vocab-insight.is-warn, .vocab-insight.is-warn strong{color:#b45309;}
.vocab-gen-promote{margin-top:18px;padding-top:16px;border-top:1px dashed var(--card-border);}
.vocab-promote-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:10px;}
.vocab-promote-row label{display:inline-flex;align-items:center;gap:8px;font-size:0.8rem;color:var(--text-muted);}
.vocab-promote-status{color:var(--text-muted);font-size:0.85rem;}

/* --- Admin → Academy (Phase 36): lesson list + block editor --------------------------------- */
.academy-toolbar{align-items:flex-end;margin-bottom:16px;}/* 183: layout is the shared bar's; the language picker sits under its own label, so this bar aligns on the bottom edge. */
.academy-settings-toggle{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:12px;
    width:100%;
    padding:12px 16px;
    border:1px solid var(--card-border);
    border-radius:12px;
    background:var(--bg-color);
    color:var(--text-color);
    font-weight:700;
    font-size:0.9rem;
    cursor:pointer;
}
.academy-settings-toggle span{display:flex;align-items:center;gap:8px;min-width:0;}
.academy-settings-toggle:hover{border-color:var(--primary);color:var(--primary);}
.academy-settings{margin:0;}  /* container now lives on .set-groups (shared across all settings pages) */
.academy-lang-label{display:flex;flex-direction:column;gap:4px;font-size:0.8rem;color:var(--text-muted);font-weight:700;}
.academy-lang-pick{display:flex;align-items:center;gap:8px;}
.academy-lang-flag{font-size:1.2rem;}
.academy-empty{color:var(--text-muted);text-align:center;padding:26px 0;}
.academy-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--card-border);border-radius:12px;margin-bottom:8px;background:var(--bg-color);}
.academy-row-icon{width:20px;text-align:center;color:var(--primary);flex-shrink:0;}
.academy-row-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;}
.academy-row-title{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-weight:800;color:var(--text-color);line-height:1.25;}
.academy-crown{color:var(--secondary);font-size:0.8em;}
.academy-row-sub{font-size:0.76rem;color:var(--text-muted);}
/* 50: the editor status chip stays a pill (its class is .academy-status); the row toggle is an icon
   button (.academy-status-btn on .academy-tool). */
.academy-status{font-size:0.72rem;font-weight:800;text-transform:uppercase;padding:3px 10px;border-radius:999px;}
.academy-status.is-live{background:rgba(16,185,129,0.14);color:#0d9463;}
.academy-status.is-draft{background:rgba(111,95,228,0.12);color:var(--primary);}
.academy-editor{margin-top:22px;padding-top:18px;border-top:2px dashed var(--card-border);scroll-margin-top:120px;}
/* Editor header: title (icon + name + status chip) left, icon-only action chips right. */
.academy-editor-head{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:14px;}
.academy-editor-title{margin:0;display:flex;align-items:center;gap:10px;min-width:0;flex-wrap:wrap;}
.academy-editor-title i{color:var(--primary);}
.academy-editor-actions{display:flex;gap:8px;flex-shrink:0;}
.academy-tool-lg{width:38px;height:38px;border-radius:10px;font-size:0.95rem;}
.academy-tool-lg:hover{border-color:var(--primary);color:var(--primary);}
.academy-tool-x.academy-tool-lg:hover{border-color:#ef4444;color:#ef4444;}
/* 51: the lesson metadata fields (title → icon) sit in ONE tidy container. */
.academy-meta-card{background:var(--bg-color);border:1px solid var(--card-border);border-radius:14px;padding:16px 18px;margin-bottom:16px;}
.academy-meta-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;}
.academy-field{display:flex;flex-direction:column;gap:4px;font-size:0.78rem;color:var(--text-muted);font-weight:700;}
.academy-field input,.academy-field select{padding:8px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);color:var(--text-color);font-size:0.9rem;}
.academy-field-wide{grid-column:span 2;}
.academy-field-check{flex-direction:row;align-items:center;gap:8px;align-self:end;padding-bottom:8px;}
.academy-icon-field{margin-top:12px;}
.academy-field-caption{display:block;font-size:0.78rem;color:var(--text-muted);font-weight:700;margin-bottom:6px;}
.academy-icon-picker{display:flex;flex-wrap:wrap;gap:6px;}
.academy-icon-opt{
    width:36px;
    height:36px;
    border:1px solid var(--card-border);
    border-radius:10px;
    background:var(--card-bg);
    color:var(--text-muted);
    font-size:0.95rem;
    cursor:pointer;
    transition:border-color 0.15s ease,color 0.15s ease;
}
.academy-icon-opt:hover{border-color:var(--primary);color:var(--primary);}
.academy-icon-opt.is-active{border-color:var(--primary);background:rgba(111,95,228,0.12);color:var(--primary);}
.academy-blocks-title{margin:0;}
.academy-blocks-hint{margin:0 0 10px;font-size:0.8rem;color:var(--text-muted);}
/* 51: Add-block dropdown on the Content-blocks heading row. */
.academy-blocks-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:18px 0 4px;}
.academy-block-add{position:relative;flex-shrink:0;}
.academy-add-caret{font-size:0.7em;margin-left:2px;}
.academy-block-add-menu {
    position:absolute;
    top:calc(100% + 6px);
    right:0;
    z-index:20;
    min-width:180px;
    display:flex;
    flex-direction:column;
    gap:2px;
    padding:6px;
    background:var(--card-bg);
    border:1px solid var(--card-border);
    border-radius:12px;
    box-shadow:0 12px 30px -8px rgba(17,24,39,0.25);
}
.academy-block-add-menu.hidden{display:none;}
.academy-block-add-menu button{display:flex;align-items:center;gap:10px;width:100%;padding:8px 12px;border:none;background:none;border-radius:8px;font:inherit;font-size:0.9rem;color:var(--text-color);cursor:pointer;text-align:left;}
.academy-block-add-menu button:hover{background:var(--bg-color);}
.academy-block-add-menu button i{color:var(--primary);width:16px;text-align:center;}
.academy-block{border:1px solid var(--card-border);border-radius:12px;background:var(--bg-color);margin-bottom:8px;overflow:hidden;}
.academy-block-head{display:flex;align-items:center;gap:10px;padding:8px 12px;background:rgba(111,95,228,0.06);cursor:pointer;user-select:none;}
/* 51: content-block drag handle. */
.academy-block-grip{color:var(--text-muted);cursor:grab;font-size:0.85rem;flex-shrink:0;}
.academy-block.is-dragging{opacity:0.5;border-style:dashed;}
.academy-block-head:hover{background:rgba(111,95,228,0.10);}
.academy-block-type{font-weight:800;font-size:0.82rem;color:var(--primary);text-transform:capitalize;flex-shrink:0;min-width:86px;}
.academy-block-type i{margin-right:6px;}
/* Block ↑/↓ stacked just right of the grip + a faded-purple number chip, mirroring the lesson/chapter rows. */
.academy-block-arrows{display:flex;flex-direction:column;flex-shrink:0;}
.academy-block-arrows .academy-tool{width:22px;height:15px;padding:0;font-size:0.58rem;border:none;background:none;}
.academy-block-num{font-size:0.68rem;font-weight:800;color:var(--primary);background:rgba(111,95,228,0.1);border-radius:7px;min-width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;}
/* Academy settings — Gemini model override text inputs. */
.academy-model-input{width:100%;max-width:280px;font-family:ui-monospace,monospace;font-size:0.82rem;padding:7px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);color:var(--text-color);}
.model-input-wrap{display:flex;gap:6px;align-items:center;}
.model-test-btn{flex:0 0 auto;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--card-border);background:var(--card-bg);border-radius:8px;cursor:pointer;color:var(--text-muted);font-size:0.82rem;}
.model-test-btn:hover{border-color:var(--primary);color:var(--primary);}
.model-test-btn:disabled{opacity:0.55;cursor:default;}
.academy-block-summary{flex:1;min-width:0;font-size:0.82rem;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
/* 42: checkpoint provenance chips — fresh (Gemini-rewritten, review it) vs verbatim copies. */
.academy-block-prov{flex:none;font-size:0.68rem;font-weight:800;color:var(--text-muted);background:rgba(111,95,228,0.08);border-radius:999px;padding:2px 8px;white-space:nowrap;max-width:180px;overflow:hidden;text-overflow:ellipsis;}
.academy-block-prov.is-fresh{color:#7c3aed;background:rgba(124,58,237,0.12);}
.academy-fresh-note{font-size:0.8rem;color:#7c3aed;background:rgba(124,58,237,0.07);border-radius:10px;padding:8px 12px;margin:0 0 10px;}
.academy-block-caret{color:var(--text-muted);font-size:0.75rem;margin-left:4px;transition:transform 0.2s ease;align-self:center;}
.academy-block.is-open .academy-block-caret{transform:rotate(180deg);}
.academy-block-tools{display:flex;gap:6px;align-items:center;flex-shrink:0;}
.academy-tool{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--card-border);background:var(--card-bg);border-radius:8px;width:28px;height:28px;cursor:pointer;color:var(--text-muted);}
/* 51: lesson ↑/↓ stacked at the far left (next to the grip), mirroring the chapter arrows. */
.academy-row-arrows{display:flex;flex-direction:column;flex-shrink:0;}
.academy-row-arrows .academy-tool{width:22px;height:15px;padding:0;font-size:0.58rem;border:none;background:none;}
.academy-tool:disabled{opacity:0.35;cursor:default;}
.academy-tool-x:hover{color:#ef4444;border-color:#ef4444;}
.academy-block-body{padding:12px 14px;display:flex;flex-direction:column;gap:10px;}
.academy-block-body label{display:flex;flex-direction:column;gap:4px;font-size:0.78rem;color:var(--text-muted);font-weight:700;}
.academy-block-body input,.academy-block-body textarea,.academy-block-body select{padding:8px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);color:var(--text-color);font-size:0.9rem;font-family:inherit;}
.academy-pair{display:flex;gap:8px;align-items:center;}
.academy-pair input{flex:1;}
/* 53: contrast edits its two sides side by side — the form should read like the block does. */
.academy-ct-sides{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
.academy-ct-side{display:flex;flex-direction:column;gap:8px;border:1px solid var(--card-border);border-radius:10px;padding:10px;}
.academy-mini-x{border:none;background:none;color:var(--text-muted);font-size:1.1rem;cursor:pointer;padding:0 4px;}
.academy-mini-x:hover{color:#ef4444;}
.academy-chiprow{display:flex;flex-wrap:wrap;gap:6px;}
.academy-chip{display:inline-flex;align-items:center;gap:5px;background:rgba(111,95,228,0.12);color:var(--primary);border-radius:999px;padding:4px 10px;font-size:0.85rem;font-weight:700;}
.academy-chip-x{border:none;background:none;color:inherit;cursor:pointer;font-size:1rem;padding:0;}
.academy-sense-search{position:relative;}
.academy-sense-search input{width:100%;}
.academy-sense-results{display:flex;flex-direction:column;gap:2px;margin-top:6px;}
.academy-sense-hit{text-align:left;border:1px solid var(--card-border);background:var(--card-bg);border-radius:8px;padding:6px 10px;cursor:pointer;font-size:0.88rem;color:var(--text-color);}
.academy-sense-hit:hover{border-color:var(--primary);}
.academy-sense-gloss{color:var(--text-muted);font-size:0.82rem;margin-left:6px;}
.academy-sense-dict{font-size:0.68rem;font-weight:800;color:var(--secondary);margin-left:6px;text-transform:uppercase;}
.academy-sense-none{color:var(--text-muted);font-size:0.85rem;}
.academy-gen{margin-bottom:16px;}
/* 44.6 admin polish: the Academy panels (generator / tests / audio maintenance) share ONE padded
   layout — the old cards had no padding and mixed unrelated control rows. */
.academy-panel{padding:16px 18px;}
#academy-list{margin-bottom:22px;}/* keep the last chapter off the Audio/Localization panels when there's no exam row between them */
.academy-panel-head{margin-bottom:14px;}
.academy-panel-title{display:block;font-size:0.95rem;color:var(--text-color);margin-bottom:4px;}
.academy-panel-title i{color:var(--primary);margin-right:7px;}
.academy-panel-actions{display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap;}
.academy-panel-actions .btn{white-space:nowrap;}
.academy-panel-status{margin:10px 0 0;}
.academy-panel-status:empty{display:none;}
.academy-panel-sep{border:none;border-top:1px solid var(--card-border);margin:16px 0 14px;}
/* 51: always-visible prepare-audio progress bar. */
.academy-prep-bar{height:8px;background:var(--card-border);border-radius:999px;overflow:hidden;margin:10px 0 0;}
.academy-prep-bar span{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--primary),var(--secondary));border-radius:999px;transition:width 0.3s ease;}
.academy-unmatched{margin:0;font-size:0.82rem;color:var(--secondary);font-weight:700;}
.academy-unmatched i{margin-right:4px;}
/* Focus mode: while a lesson is open, the picker/settings/level pills/list step aside and the editor
   owns the tab (every exit path — save, close, delete — removes is-editing). */
#tab-academy-lessons.is-editing .academy-toolbar,#tab-academy-lessons.is-editing .academy-gen,#tab-academy-lessons.is-editing .academy-levels,#tab-academy-lessons.is-editing .academy-level-head,#tab-academy-lessons.is-editing .academy-level-overview,#tab-academy-lessons.is-editing .academy-level-bar,#tab-academy-lessons.is-editing .academy-search-row,#tab-academy-lessons.is-editing .academy-acd{display:none;}
#tab-academy-lessons.is-editing .academy-editor{margin-top:0;padding-top:0;border-top:none;animation:academy-editor-in 0.25s ease;}
@media(prefers-reduced-motion:reduce){#tab-academy-lessons.is-editing .academy-editor{animation:none;}}
/* Reorder affordances: grip for drag-and-drop + per-row arrows (both persist via lessons_reorder). */
.academy-grip{color:var(--text-muted);cursor:grab;padding:0 2px;font-size:0.9rem;}
.academy-row.is-dragging{opacity:0.45;border-style:dashed;}
.academy-row .academy-tool{flex-shrink:0;}
.academy-inline-check{flex-direction:row !important;align-items:center;gap:8px;}
/* 40A3: grammar-extras group, cloze authoring guard + the live preview pane (renders through the
   REAL learner registries — academy.css styles apply since it's a shell asset). */
.academy-extras{border:1px dashed var(--card-border);border-radius:10px;padding:10px 12px;}
.academy-extras summary{cursor:pointer;font-size:0.8rem;font-weight:800;color:var(--text-muted);}
.academy-extras[open] summary{margin-bottom:10px;}
.academy-extras label{margin-bottom:10px;}
.academy-check-form{display:flex;flex-direction:column;gap:8px;border-top:1px dashed var(--card-border);padding-top:10px;}
.academy-field-caption{font-size:0.78rem;font-weight:700;color:var(--text-muted);}
.academy-cloze-status{margin:0;font-size:0.8rem;font-weight:700;color:var(--success,#10b981);}
.academy-cloze-status.is-bad{color:var(--secondary);}
.academy-form-note{margin:0;font-size:0.78rem;color:var(--text-muted);font-style:italic;}
.academy-live-preview{border-top:1px dashed var(--card-border);margin-top:6px;padding-top:10px;}
.academy-preview-cap{display:block;font-size:0.72rem;font-weight:800;text-transform:uppercase;letter-spacing:0.05em;color:var(--primary);margin-bottom:6px;}
.academy-preview-body{background:rgba(255,255,255,0.55);border-radius:14px;padding:6px 16px;}
.academy-preview-body .acd-block{border-bottom:none;}
.academy-preview-note{margin:0;font-size:0.85rem;color:var(--text-muted);}
/* 41: cadence chips ("N published lessons since the last checkpoint"). */
.academy-cp-count{font-size:0.72rem;font-weight:800;color:var(--text-muted);background:rgba(111,95,228,0.08);border-radius:999px;padding:2px 8px;}
.academy-cp-count.is-due{color:#b45309;background:rgba(244,162,60,0.16);}
.academy-row.is-checkpoint{border-color:rgba(244,162,60,0.5);}
.academy-kind-badge{font-size:0.62rem;font-weight:800;text-transform:uppercase;letter-spacing:0.05em;color:#b45309;background:rgba(244,162,60,0.16);border-radius:999px;padding:2px 7px;margin-left:6px;vertical-align:1px;}
/* 49: level pills (learner-style) + level header (count / cadence / level-wide actions). */
.academy-levels{display:flex;gap:8px;flex-wrap:wrap;margin:4px 0 14px;}
.academy-level-field .academy-levels{margin:0;}
.academy-level-pill{font-weight:800;font-size:0.85rem;padding:7px 16px;border:1px solid var(--card-border);border-radius:999px;background:var(--card-bg);color:var(--text-muted);cursor:pointer;transition:all 0.15s ease;}
.academy-level-pill:hover{border-color:var(--primary);color:var(--primary);}
.academy-level-pill.is-active{background:var(--primary);border-color:var(--primary);color:#fff;}
/* Phase 171: the level head and its stats strip are ONE bar above the chapter list, not two panels
   stacked — they are a single statement about the level the operator is looking at. The wrapper is
   the only markup this phase added; the head and the strip keep their own ids, classes and order.
   ⚠️ The overview is filled by admin-academy.js AFTER the list loads, so it is empty on first paint:
   without `:empty` the bar would open with 10px of nothing under the title. */
.academy-level-bar{display:flex;flex-direction:column;gap:10px;}
.academy-level-bar>.academy-level-head,.academy-level-bar>.academy-level-overview{margin:0;}
.academy-level-bar>.academy-level-overview:empty{display:none;}
.academy-level-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin:0 0 12px;}
.academy-level-title{margin:0;font-size:1.05rem;color:var(--text-color);display:flex;align-items:center;gap:8px;}
.academy-level-title i{color:var(--primary);}
.academy-level-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
/* Chapter accordion (49): collapsed by default — only one section open at a time. */
.academy-acd{display:flex;flex-direction:column;gap:10px;}
.academy-chap{border:1px solid var(--card-border);border-radius:14px;background:var(--card-bg);overflow:hidden;}
.academy-chap.is-dragging{opacity:0.5;border-style:dashed;}
.academy-chap-head{display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(111,95,228,0.055);}
.academy-chap-grip{color:var(--text-muted);cursor:grab;font-size:0.9rem;flex-shrink:0;width:14px;text-align:center;}
.academy-chap-grip.is-off{cursor:default;opacity:0;}
/* PR4: chapter ↑/↓ arrows (stacked, compact), progress bar, level overview strip. */
.academy-chap-arrows{display:flex;flex-direction:column;flex-shrink:0;}
.academy-chap-arrows .academy-tool{width:22px;height:15px;padding:0;font-size:0.58rem;border:none;background:none;}
.academy-level-overview{display:flex;flex-wrap:wrap;gap:8px 16px;margin:0 0 14px;font-size:0.8rem;font-weight:700;color:var(--text-muted);}
.academy-level-overview span{display:inline-flex;align-items:center;gap:5px;}
.academy-level-overview i{color:var(--text-muted);font-size:0.85em;}
.academy-level-overview .is-live,.academy-level-overview .is-live i{color:#0d9463;}
.academy-level-overview .is-draft,.academy-level-overview .is-draft i{color:#b45309;}
.academy-level-overview .is-alert,.academy-level-overview .is-alert i{color:#dc2626;}
/* PR-C: separator between the difficulty pills and the content below + compact white action buttons. */
.academy-sep{border:none;border-top:1px solid var(--card-border);margin:14px 0;}
/* Phase 171: the pills row above it and the level head below it are both framed bars now, so the
   separator is a third line drawn between two frames — and its 14px margins put 61px between two
   panels the pane already spaces at 16px. The <hr> stays in the markup; the workspace stops
   drawing it. */
.lframe-layout .admin-tab-content>.academy-sep{display:none;}
.academy-act-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 11px;font-size:0.8rem;font-weight:700;border:1px solid var(--card-border);border-radius:9px;background:#fff;color:var(--text-color);cursor:pointer;}
.academy-act-btn:hover{border-color:var(--primary);color:var(--primary);}
.academy-act-btn i{font-size:0.85em;color:var(--text-muted);}
.academy-act-btn:hover i{color:var(--primary);}
/* PR-C: row ⋯ menu — portaled to <body> (position:fixed) so the accordion's overflow:hidden can't clip it. */
.academy-row-menu{position:fixed;z-index:2100;min-width:172px;padding:5px;display:flex;flex-direction:column;gap:2px;background:var(--card-bg);border:1px solid var(--card-border);border-radius:10px;box-shadow:0 12px 30px -8px rgba(17,24,39,0.35);}
.academy-row-menu.hidden{display:none;}
.academy-row-menu-item{display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border:none;border-radius:7px;background:none;cursor:pointer;font:inherit;font-size:0.85rem;color:var(--text-color);text-align:left;}
.academy-row-menu-item:hover{background:var(--bg-color);}
.academy-row-menu-item i{width:16px;text-align:center;color:var(--text-muted);}
.academy-chap-toggle {
    flex:1;
    min-width:0;
    display:flex;
    align-items:center;
    gap:10px;
    background:none;
    border:none;
    cursor:pointer;
    text-align:left;
    padding:4px 2px;
    color:var(--text-color);
    font:inherit;
}
/* 50: chapter title inherits the app UI font (Nunito) — a button doesn't inherit font-family, so it
   was falling back to the browser default. */
.academy-chap-name{font-weight:800;font-size:0.95rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.academy-chap-count{font-size:0.72rem;font-weight:700;color:var(--text-muted);flex-shrink:0;}
.academy-chap-count i{color:#b45309;}
.academy-chap-live{font-weight:800;color:#0d9463;}
.academy-chap-live.is-partial{color:#b45309;}
/* 50: the row status toggle is an icon button (.academy-tool) tinted by state — green = live, muted
   = draft. Its hover comes from .academy-tool. */
.academy-status-btn.is-live{color:#0d9463;}
.academy-status-btn.is-live:hover{border-color:#0d9463;}
.academy-status-btn.is-draft{color:var(--text-muted);}
/* 50: the within-chapter number chip carries the category colour (was a separate dot). */
.academy-num-grammar{background:rgba(139,92,246,0.16);color:#7c4dff;}
.academy-num-vocab{background:rgba(59,130,246,0.16);color:#2f6fed;}
.academy-num-conversation{background:rgba(16,185,129,0.16);color:#0d9463;}
.academy-num-culture{background:rgba(245,158,11,0.18);color:#b45309;}
.academy-row-warn{color:#f59e0b;font-size:0.85em;margin-left:3px;}
/* PR3: the title filter bar. 52: + the lint-state filter select sits beside the search. */
.academy-search-row{margin:0 0 12px;}/* 183: layout is the shared bar's. */
.academy-search-wrap{display:flex;align-items:center;gap:8px;border:1px solid var(--card-border);border-radius:10px;padding:0 12px;background:var(--card-bg);flex:1 1 260px;max-width:420px;}
.academy-search-icon{color:var(--text-muted);font-size:0.85rem;}
.academy-search{flex:1;border:none;background:none;padding:9px 0;font:inherit;color:var(--text-color);outline:none;min-width:0;}
/* 52: lint-state filter — matches the search wrap's chrome; the select fills it. */
.academy-lint-filter-wrap{display:flex;align-items:center;gap:8px;border:1px solid var(--card-border);border-radius:10px;padding:0 10px 0 12px;background:var(--card-bg);flex:0 1 auto;}
.academy-lint-filter-icon{color:var(--text-muted);font-size:0.85rem;}
.academy-lint-filter{border:none;background:none;padding:9px 4px 9px 0;font:inherit;color:var(--text-color);outline:none;cursor:pointer;}
/* 52: the row Lint button is tinted by cached lint state (grey when never checked). Icon stays the same
   so the button's position is stable, and colour + a matching hover border carry the state. */
.academy-lint-row.is-broken{color:#dc2626;}
.academy-lint-row.is-broken:hover{border-color:#dc2626;}
.academy-lint-row.is-issues{color:#d97706;}
.academy-lint-row.is-issues:hover{border-color:#d97706;}
.academy-lint-row.is-clean{color:#0d9463;}
.academy-lint-row.is-clean:hover{border-color:#0d9463;}
/* 52 PR-A: stale = content changed since the cached check. An amber dot over whatever colour the last
   known state was, so the admin knows the count is outdated and a re-lint is due. */
.academy-lint-row.is-stale{position:relative;}
.academy-lint-row.is-stale::after{content:"";position:absolute;top:-2px;right:-2px;width:8px;height:8px;border-radius:50%;background:#d97706;border:1.5px solid var(--card-bg);}
.academy-chap-chevron{margin-left:auto;color:var(--text-muted);transition:transform 0.2s ease;flex-shrink:0;}
.academy-chap.is-open .academy-chap-chevron{transform:rotate(180deg);}
.academy-chap-actions{display:flex;gap:6px;flex-shrink:0;}
/* 51: animate expand/collapse via the grid-template-rows 0fr↔1fr trick (height is not transitionable;
   an inner overflow:hidden wrapper carries the padding so it clips cleanly when collapsed). */
.academy-chap-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows 0.28s ease;}
.academy-chap.is-open .academy-chap-body{grid-template-rows:1fr;}
/* Vertical padding ONLY when open — a 0fr grid track can't shrink below the inner's top/bottom
   padding (that left a ~16px sliver when collapsed). Side padding is safe — it never sizes the row. */
.academy-chap-body-inner{overflow:hidden;min-height:0;padding:0 12px;}
.academy-chap.is-open .academy-chap-body-inner{padding-top:12px;padding-bottom:4px;}

/* 52 — Bulk Course Builder (admin, light-only). PR-C: now in a modal — the modal card owns the chrome. */
.academy-course-body{margin-top:6px;}
.course-lead{color:var(--text-muted);font-size:0.9rem;margin:12px 0 14px;}
.course-muted{color:var(--text-muted);font-size:0.88rem;}
.course-fld{
    display:flex;flex-direction:column;gap:4px;
    font-size:0.82rem;font-weight:600;color:var(--text-color);
}
.course-fld input,.course-fld select,.course-fld textarea{
    font:inherit;font-weight:400;padding:8px 10px;
    border:1px solid var(--card-border);border-radius:9px;background:var(--bg-color);color:var(--text-color);
}
#course-status.is-err{color:var(--danger);}
.course-jobs{margin-top:16px;}
.course-jobs-h{font-size:0.8rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--text-muted);margin:0 0 8px;}
.course-job-row{
    display:flex;align-items:center;gap:10px;padding:9px 11px;
    border:1px solid var(--card-border);border-radius:10px;margin-bottom:7px;
    cursor:pointer;transition:border-color 0.15s ease;
}
.course-job-row:hover{border-color:var(--primary);}
.course-job-title{flex:1;font-weight:600;color:var(--text-color);}
.course-job-lvl{
    display:inline-block;font-size:0.72rem;font-weight:700;color:var(--primary);
    background:var(--primary-glow);padding:1px 7px;border-radius:20px;margin-left:4px;
}
.course-job-prog{font-size:0.82rem;color:var(--text-muted);}
.course-job-badge{
    font-size:0.7rem;font-weight:700;text-transform:uppercase;letter-spacing:0.03em;
    padding:2px 8px;border-radius:20px;
}
.course-job-del{background:none;border:0;color:var(--text-muted);cursor:pointer;padding:4px;}
.course-job-del:hover{color:var(--danger);}
.course-job-head{display:flex;align-items:center;gap:10px;margin:14px 0 12px;}
.course-job-h{flex:1;font-weight:700;font-size:1.02rem;color:var(--text-color);}
.course-progress{display:flex;align-items:center;gap:12px;margin-bottom:10px;}
.course-bar{flex:1;height:9px;background:var(--card-border);border-radius:20px;overflow:hidden;}
.course-bar-fill{
    display:block;height:100%;border-radius:20px;position:relative;overflow:hidden;
    background:linear-gradient(90deg,var(--primary),var(--accent));transition:width 0.4s ease;
}
.course-prog-num{font-size:0.82rem;font-weight:600;color:var(--text-muted);white-space:nowrap;}
.course-ladder{margin:6px 0 14px;}
.course-chapters{display:flex;flex-direction:column;gap:12px;}
.course-ch{border:1px solid var(--card-border);border-radius:12px;padding:10px 12px;background:var(--bg-color);}
.course-ch-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px;}
.course-ch-num{
    display:inline-flex;align-items:center;justify-content:center;
    width:22px;height:22px;border-radius:50%;flex:0 0 auto;
    background:var(--primary);color:#fff;font-size:0.75rem;font-weight:700;
}
.course-ch-name{font-weight:700;color:var(--text-color);}
.course-ch-goalr{color:var(--text-muted);font-size:0.85rem;}
.course-ch-title{flex:1;min-width:140px;font-weight:700;}
.course-ch-goal{flex:2;min-width:160px;}
.course-ch-cp{color:var(--accent);margin-left:auto;}
.course-ls-list{display:flex;flex-direction:column;gap:7px;}
.course-ls{
    display:flex;gap:8px;align-items:flex-start;padding:7px 8px;
    border-radius:9px;background:var(--card-bg);border:1px solid var(--card-border);
}
.course-ls-locked{align-items:center;}
.course-ls-title{flex:1;font-weight:600;color:var(--text-color);}
.course-ls-meta{font-size:0.76rem;color:var(--text-muted);}
.course-ls-fields{flex:1;display:flex;flex-direction:column;gap:6px;}
.course-ls-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap;}
.course-in{
    font:inherit;font-weight:400;padding:6px 8px;
    border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);color:var(--text-color);
}
.course-in-title{font-weight:700;}
.course-ls-fields>.course-in{width:100%;}
.course-in-gr{flex:1;min-width:120px;}
.course-pro{display:inline-flex;align-items:center;gap:4px;font-size:0.8rem;color:var(--text-muted);}
.course-badge{
    flex:0 0 auto;width:22px;height:22px;display:inline-flex;
    align-items:center;justify-content:center;border-radius:50%;font-size:0.72rem;margin-top:2px;
}
.course-badge.is-done{background:var(--success);color:#fff;}
.course-badge.is-fail{background:var(--danger);color:#fff;}
.course-badge.is-pending{background:var(--card-border);color:var(--text-muted);}
.course-badge.is-review{background:var(--accent);color:#fff;}
.course-ls-rm{background:none;border:0;color:var(--text-muted);cursor:pointer;padding:4px;flex:0 0 auto;}
.course-ls-rm:hover{color:var(--danger);}
.course-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px;}
.course-done-msg{color:var(--success);font-weight:600;}
.course-add-ls{margin-top:8px;}
.btn-xs{padding:4px 10px;font-size:0.78rem;}
.course-asm-report{margin-top:12px;}
.course-asm-list{
    list-style:none;margin:0;padding:0;
    display:flex;flex-direction:column;gap:5px;font-size:0.86rem;color:var(--text-color);
}
.course-asm-list li{display:flex;align-items:baseline;gap:7px;}
.course-ok{color:var(--success);}
.course-warn{color:var(--accent);}
.course-err{color:var(--danger);}
.course-muted-i{color:var(--text-muted);}

/* 52 — coherence-lint findings modal (fixed header + scrolling accordion). Compound selector so the
   panel's flex/overflow/padding beat the base .academy-modal-card rule defined later in the file. */
.academy-modal-card.academy-lint-panel{display:flex;flex-direction:column;overflow:hidden;padding:0;}
.academy-lint-head{flex:0 0 auto;position:relative;padding:16px 20px 12px;border-bottom:1px solid var(--card-border);}
.academy-lint-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;padding:12px 20px 18px;}
.academy-lint-title{margin:0 30px 0 0;color:var(--text-color);display:flex;align-items:center;gap:7px;min-width:0;text-transform:none;}
.academy-lint-title>i{color:var(--primary);flex:0 0 auto;}
.academy-lint-title-name{font-weight:600;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.academy-lint-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:0.74rem;color:var(--text-muted);margin:9px 0 0;}
.academy-lint-count{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:0.8rem;font-weight:600;color:var(--text-muted);margin:8px 0 0;}
.academy-lint-loading{color:var(--text-muted);display:flex;align-items:center;gap:10px;font-size:0.9rem;padding:8px 0;}
.academy-lint-clean{color:var(--success);font-weight:600;display:flex;align-items:center;gap:8px;}
.academy-lint-err{color:var(--danger);display:flex;align-items:center;gap:8px;margin:0 0 8px;}
.academy-lint-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px;}
.academy-lint-item{border:1px solid var(--card-border);border-radius:10px;overflow:hidden;}
.academy-lint-item.is-broken{border-left:4px solid var(--danger);}
.academy-lint-item.is-misleading{border-left:4px solid var(--accent);}
.academy-lint-item.is-minor{border-left:4px solid var(--text-muted);}
.academy-lint-rowhead{display:flex;align-items:center;gap:9px;width:100%;padding:9px 11px;border:none;background:none;cursor:pointer;text-align:left;font:inherit;color:inherit;}
.academy-lint-rowhead:hover{background:var(--bg-color);}
.academy-lint-sev{font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.03em;padding:2px 7px;border-radius:20px;flex:0 0 auto;background:var(--bg-color);color:var(--text-muted);}
.academy-lint-item.is-broken .academy-lint-sev{background:#fde2e2;color:#c0392b;}
.academy-lint-item.is-misleading .academy-lint-sev{background:#fdeede;color:#c9781a;}
.academy-lint-cat{flex:1;min-width:0;font-size:0.78rem;font-weight:700;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.academy-lint-cat em{font-style:normal;font-weight:600;color:var(--text-muted);font-size:0.68rem;}
.academy-lint-chev{flex:0 0 auto;color:var(--text-muted);font-size:0.8rem;transition:transform 0.2s ease;}
.academy-lint-item.is-open .academy-lint-chev{transform:rotate(180deg);}
.academy-lint-drawer{display:none;flex-direction:column;gap:7px;padding:2px 11px 11px;}
.academy-lint-item.is-open .academy-lint-drawer{display:flex;}
.academy-lint-detail{font-size:0.85rem;color:var(--text-color);line-height:1.45;}
.academy-lint-tags{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.academy-lint-tags:empty{display:none;}
.academy-lint-src{font-size:0.72em;color:var(--text-muted);opacity:0.75;margin-left:2px;}
.academy-lint-conf{display:inline-flex;align-items:center;color:var(--text-muted);font-size:0.92rem;cursor:help;}
.academy-lint-verd{font-size:0.66rem;font-weight:700;color:var(--text-muted);}
.academy-lint-verd.is-confirmed{color:var(--danger);}
.academy-lint-item.is-fixed{opacity:0.55;}
.academy-lint-item.is-fixed .academy-lint-detail{text-decoration:line-through;}
.academy-lint-item.is-fixed .academy-lint-cat::after{content:' · fixed';color:var(--success);font-weight:700;font-size:0.66rem;}
.academy-lint-item.is-fixing{opacity:0.7;}
.academy-lint-item.is-failed .academy-lint-cat::after{content:' · fix failed';color:var(--danger);font-weight:700;font-size:0.66rem;}

/* Manage quality: whole-language control centre — fixed head (controls + progress) · scrolling findings
   · fixed footer (scope + Fix-all). Findings reuse the .academy-lint-item accordion above. */
.academy-modal-card.academy-manage-card{display:flex;flex-direction:column;overflow:hidden;padding:0;}
/* 71.7b: the tab strip now draws the separating hairline itself, so the head must not draw a second
   one, and its tabs sit flush at the head's bottom edge (no 18px trailing gap inside the head). */
/* The modal has no tinted wrapper - its head keeps the separating border and the pill tabs sit above it. */
.academy-manage-head{flex:0 0 auto;position:relative;padding:18px 22px 12px;border-bottom:1px solid var(--card-border);}
#academy-manage-modal .nb-tabs{margin:12px 0 0;}
/* The wrapper IS the settings container: tinted, rounded, holding the strip and the panes. */
.panel-stack{background:var(--bg-color);border:1px solid var(--card-border);border-radius:12px;padding:8px;margin-top:14px;}
.panel-stack .nb-tabs{margin:0 0 8px;}
/* .set-groups gave up its own chrome inside a tabbed wrapper here; Phase 170 retired that chrome
   outright (the wrapper is a bare grid now and each .set-group is its own card), so the override
   cleared properties nothing sets and was deleted rather than left reading as live. */
.academy-manage-pane{flex:0 0 auto;padding:14px 22px 16px;}
.academy-manage-pane.academy-manage-scroll{flex:1 1 auto;}
#academy-manage-tab-revision .academy-manage-fix{border-top:none;padding:0;}
/* Summary stat cards — quiet dashboard tiles. */
.amq-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px;}
.amq-card{display:flex;flex-direction:column;gap:2px;padding:12px 14px;border:1px solid var(--card-border);border-radius:12px;background:var(--bg-color);}
.amq-val{font-family:var(--font-display);font-weight:800;font-size:1.25rem;color:var(--text-color);font-variant-numeric:tabular-nums;}
.amq-lab{font-size:0.74rem;color:var(--text-muted);}
.amq-card.amq-bad .amq-val{color:var(--danger);}
.amq-card.amq-warn .amq-val{color:#b58a2a;}
.amq-card.amq-good .amq-val{color:var(--success);}
.amq-card.amq-accent .amq-val{color:var(--primary);}
.academy-manage-title{margin:0 30px 4px 0;color:var(--text-color);display:flex;align-items:center;gap:8px;}
.academy-manage-title>i{color:var(--primary);}
.academy-manage-sub{margin:0 0 12px;font-size:0.8rem;color:var(--text-muted);line-height:1.45;}
.academy-manage-ctl{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.academy-manage-stat{font-size:0.76rem;color:var(--text-muted);font-weight:600;}
.academy-manage-bar{height:6px;background:var(--card-border);border-radius:999px;overflow:hidden;margin:10px 0 0;}
.academy-manage-bar span{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--primary),var(--secondary));border-radius:999px;transition:width 0.3s ease;}
.academy-manage-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 22px 16px;display:flex;flex-direction:column;gap:14px;}
.academy-manage-empty{font-size:0.85rem;color:var(--text-muted);line-height:1.5;margin:6px 0;}
.academy-manage-group{display:flex;flex-direction:column;gap:7px;}
.academy-manage-ghead{display:flex;align-items:center;gap:9px;}
.academy-manage-gtitle{font-size:0.85rem;font-weight:800;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.academy-manage-gcount{font-size:0.68rem;font-weight:700;color:var(--text-muted);flex:0 0 auto;margin-left:auto;}
.academy-manage-gb{font-size:0.6rem;font-weight:800;text-transform:uppercase;letter-spacing:0.03em;color:#c0392b;background:#fde2e2;border-radius:20px;padding:1px 7px;flex:0 0 auto;}
.academy-manage-gstale{font-size:0.6rem;font-weight:800;text-transform:uppercase;letter-spacing:0.03em;color:#8a6d1f;background:#fdf3d6;border-radius:20px;padding:1px 7px;flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;}
.academy-manage-group.is-stale .academy-lint-list{opacity:0.55;}
.academy-manage-fix{flex:0 0 auto;border-top:1px solid var(--card-border);padding:12px 22px 16px;}
.academy-manage-fixrow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
@media(prefers-reduced-motion:reduce){.academy-chap-body{transition:none;}}
.academy-chap-empty{margin:6px 0;font-size:0.85rem;color:var(--text-muted);font-style:italic;text-align:center;padding:12px 0;border:1px dashed var(--card-border);border-radius:10px;}
.academy-row-num{font-size:0.72rem;font-weight:800;color:var(--primary);background:rgba(111,95,228,0.1);border-radius:8px;min-width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;}
.academy-exam-wrap{margin-top:12px;}
.academy-row.is-exam{border-color:rgba(111,95,228,0.5);background:rgba(111,95,228,0.05);}
/* Academy popups (49): the +Lesson generator + the checkpoint/exam assembly modals. */
.academy-modal-overlay{position:fixed;inset:0;z-index:2000;}
.academy-modal-overlay.hidden{display:none;}
.academy-modal-title{margin:0 34px 4px 0;color:var(--text-color);display:flex;align-items:center;gap:9px;}
.academy-modal-title i{color:var(--primary);}
.academy-modal-sub{margin:0 0 16px;font-size:0.85rem;color:var(--text-muted);}
.academy-modal-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px;}
.academy-modal-spacer{flex:1;}
@media(max-width:640px){
    .mie-stage{flex-wrap:wrap;}
    .mie-nav-btn{order:2;}
    .mie-stage-main{order:1;flex-basis:100%;}
}

/* Voice-per-language settings (collapsible panels) */
.voices-list{display:flex;flex-direction:column;gap:10px;}
.voice-lang-panel{border:1px solid var(--card-border);border-radius:12px;background:var(--bg-color);overflow:hidden;}
.voice-lang-header {
    width:100%;
    display:flex;
    align-items:center;
    gap:12px;
    padding:14px 16px;
    background:none;
    border:none;
    cursor:pointer;
    text-align:left;
    font:inherit;
}
.voice-lang{display:flex;align-items:center;gap:10px;font-weight:700;}
/* ⚠ ONE auto margin in this row, not two. The caret gained `margin-left:auto` from the shared
   `.set-group-caret`, and with the summary also claiming it the free space was SPLIT — the summary
   drifted to the middle of the header instead of sitting against the chevron. The summary keeps it;
   the caret follows it directly. */
.voice-lang-summary{margin-left:auto;color:var(--text-muted);font-weight:600;font-size:0.9rem;}
.admin-page .voice-lang-header>.set-group-caret{margin-left:8px;}
.voice-lang-caret{color:var(--text-muted);transition:transform 0.2s;}
.voice-lang-body{display:flex;flex-direction:column;gap:8px;padding:0 16px 16px;}
.voice-field-label{font-weight:700;font-size:0.8rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-top:6px;}
.voice-field-row{display:flex;gap:10px;align-items:center;}
.voice-model-row{flex-wrap:wrap;}
.voice-provider + .dd{flex:0 0 190px;}
.voice-model{flex:1 1 180px;min-width:0;}
.voice-model[readonly]{color:var(--text-muted);background:transparent;}
.voice-route-note{margin:0;font-size:0.8rem;line-height:1.4;color:var(--text-muted);}
.voice-route-note.danger-text{color:var(--danger);}
.voice-select{width:100%;min-width:0;}
.voice-preview-input{flex:1;min-width:0;}
.voice-style{width:100%;resize:vertical;min-height:48px;font-family:inherit;font-size:0.9rem;}
.voice-preview-btn {
    flex-shrink:0;
    display:inline-flex;
    align-items:center;
    gap:7px;
    padding:10px 18px;
    font-size:0.85rem;
    font-weight:800;
    white-space:nowrap;
    border:none;
    border-radius:10px;
    background:var(--primary);
    color:#fff;
    cursor:pointer;
    box-shadow:0 2px 6px var(--primary-glow);
    transition:all 0.15s;
}
.voice-preview-btn:hover{background:var(--primary-hover);transform:translateY(-1px);}
.voice-preview-btn:disabled{opacity:0.6;cursor:not-allowed;box-shadow:none;}
.voice-lang-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:10px;}
.voice-sync-btn{display:inline-flex;align-items:center;gap:7px;padding:7px 14px;font-size:0.82rem;font-weight:700;border:1px solid var(--primary);border-radius:9px;background:none;color:var(--primary);cursor:pointer;transition:all 0.15s;}
.voice-sync-btn:hover{background:var(--primary);color:#fff;}
.voice-sync-btn:disabled{opacity:0.6;cursor:not-allowed;}
.voice-delete-btn{display:inline-flex;align-items:center;gap:7px;padding:7px 14px;font-size:0.82rem;font-weight:700;border:1px solid var(--danger);border-radius:9px;background:none;color:var(--danger);cursor:pointer;transition:all 0.15s;}
.voice-delete-btn:hover{background:var(--danger);color:#fff;}
.voice-delete-btn:disabled{opacity:0.6;cursor:not-allowed;}

/* Phase 170: a table is a framed list — a real tinted head row, zebra rows and a hover, inside a
   rounded frame. ⚠️ The FRAME is only on a wrapper that stands on the scenery (a direct child of
   the tab). Six of the eight theme-tables sit inside a .set-group already, and framing those would
   put a card inside a card, which is the hierarchy this phase is removing. */
/* Phase 170 (the fix round's finding): `.vocab-hint` is --text-muted (#7A7596) at 13.6px, which reads 4.09:1 on the
   promoted panel and 3.25:1 where a tab's copy sits on the scenery — under the 4.5:1 the AA floor asks of normal
   text. The token moves one step darker INSIDE THE WORKSPACE ONLY: the same class carries learner copy on Practice
   and the dictionary, where it sits on different material and is not this phase's to re-colour. */
.lframe-layout .vocab-hint{color:var(--text-secondary);}

.users-table-wrapper{width:100%;overflow:visible;margin-top:0;}
.lframe-layout .admin-tab-content>.users-table-wrapper{padding:6px;border:1px solid var(--surface-edge-shade);border-radius:20px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
.theme-table{width:100%;border-collapse:separate;border-spacing:0;text-align:left;font-size:0.95rem;}
.theme-table th,.theme-table td{padding:13px 14px;border-bottom:1px solid rgba(79,62,143,0.08);}
.theme-table th{background:rgba(85,68,154,0.07);border-bottom:1px solid rgba(79,62,143,0.12);font-weight:800;color:var(--text-secondary);text-transform:uppercase;font-size:0.68rem;letter-spacing:1px;}
.theme-table thead th:first-child{border-top-left-radius:14px;border-bottom-left-radius:0;}
.theme-table thead th:last-child{border-top-right-radius:14px;border-bottom-right-radius:0;}
.theme-table tbody tr:last-child>td{border-bottom:0;}
.theme-table tbody tr:nth-child(even)>td{background:rgba(85,68,154,0.028);}
/* ⚠️ The hover is an AFFORDANCE, so it is written off the rows that are not actionable: Content
   Review's group divider and its expanded draft body, and the Operator API grants' per-language
   sub-headers (which the matrix's own hover at line ~2049 already excluded, in `th` and `td` both).
   Specificity alone is not enough here — those three rules and this one both compute (0,2,3), so
   without the exclusions the winner is whichever sheet position happens to be later. */
.theme-table tbody tr:not(.review-group-row):not(.review-expand-row):not(.operator-api-grant-lang-row):hover>td{background:rgba(111,95,228,0.06);}
.table-loading-text{text-align:center;padding:30px!important;color:var(--text-muted);font-weight:700;}
.badge-admin{background:rgba(239,68,68,0.1);color:#ef4444;border:1.5px solid rgba(239,68,68,0.25);}
.badge-active{background:rgba(16,185,129,0.1);color:#10b981;border:1.5px solid rgba(16,185,129,0.25);}
.badge-warning{background:rgba(244,162,60,0.1);color:#F4A23C;border:1.5px solid rgba(244,162,60,0.25);}
.badge-moderator{background:rgba(20,184,166,0.1);color:#0d9488;border:1.5px solid rgba(20,184,166,0.28);}
.badge-pro{background:rgba(234,179,8,0.12);color:#b8860b;border:1.5px solid rgba(234,179,8,0.35);}
.badge-manual{background:rgba(59,130,246,0.09);color:#356dc4;border:1px solid rgba(59,130,246,0.22);}
.badge-test{background:rgba(168,85,247,0.1);color:#7e3ec4;border:1px solid rgba(168,85,247,0.22);}
.badge-signup{background:rgba(16,185,129,0.09);color:#128568;border:1px solid rgba(16,185,129,0.2);}
.user-pro-crown{color:#eab308;margin:0 6px 0 2px;font-size:0.9em;}
.users-toolbar{margin-bottom:16px;}/* 183: layout is the shared bar's. */
.users-add-btn{margin-left:auto;}
/* 71.7: the SEARCH field absorbs the leftover width and shrinks first, so a trailing icon button
   (Refresh) keeps its place on the row instead of being pushed onto a second line. */
.users-tool{display:inline-flex;align-items:center;gap:7px;font-size:0.82rem;color:var(--text-muted);}
.users-tool select{padding:7px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);color:var(--text-color);font-size:0.85rem;font-weight:700;cursor:pointer;}
.users-tool input[type="search"]{padding:7px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);color:var(--text-color);font-size:0.85rem;min-width:150px;}
.users-tool i{color:var(--primary);}
.users-tool-sep{width:1px;height:22px;background:var(--card-border);margin:0 2px;}
/* Armed (first click of a two-click confirm). An icon-only control cannot say "Confirm cancel" in its own label without destroying its glyph, so the armed state has to be visible here. */
.users-dir-btn.is-arming{background:rgba(232,93,117,0.14);border-color:var(--secondary);color:var(--secondary);}
.user-cell-meta{display:flex;flex-direction:column;gap:2px;}
.user-cell-title{font-weight:800;color:var(--text-color);}
.user-id-badge {
    font-size:0.75rem;
    color:var(--text-muted);
    background:rgba(255,255,255,0.05);
    padding:2px 6px;
    border-radius:4px;
    margin-left:6px;
    font-weight:700;
}
.user-cell-sub{font-size:0.8rem;color:var(--text-muted);margin-top:2px;}
.user-cell-badges{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-top:4px;}
.user-cell-badges .badge{padding:3px 7px;font-size:0.65rem;}
.user-stats-cell{display:flex;flex-direction:column;gap:4px;font-size:0.85rem;font-weight:700;}
.text-gold{color:#F4A23C;}
.text-orange{color:#f97316;}
.badge-locked{background:rgba(239,68,68,0.1);color:#ef4444;border:1.5px solid rgba(239,68,68,0.25);}
.admin-inline-input{flex-shrink:0;width:120px;padding:8px 10px;font-family:ui-monospace,monospace;font-size:0.9rem;text-align:center;color:var(--text-color);background:var(--card-bg);border:1px solid var(--card-border);border-radius:8px;}
.admin-inline-input:read-only{opacity:0.6;cursor:not-allowed;}
.admin-inline-input:focus{outline:none;border-color:var(--primary);}

.user-action-dropdown-container{position:relative;display:inline-block;}
.user-menu-trigger-btn {
    background:var(--bg-color);
    border:1px solid var(--card-border);
    color:var(--text-muted);
    width:32px;
    height:32px;
    padding:0;
    border-radius:50%;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    cursor:pointer;
    transition:all 0.2s;
}
.user-menu-trigger-btn:hover{background:rgba(111,95,228,0.12);border-color:var(--primary);color:var(--primary);}
.user-actions-dropdown {
    position:absolute;
    right:0;
    top:calc(100% + 8px);
    background:var(--card-bg);
    border:1px solid var(--card-border);
    border-radius:16px;
    box-shadow:0 18px 40px -12px rgba(17,24,39,0.3),0 6px 14px -8px rgba(17,24,39,0.14);
    z-index:1000;
    width:214px;
    display:flex;
    flex-direction:column;
    padding:8px;
    gap:1px;
    animation:fadeSlideUp 0.16s ease forwards;
}
.user-actions-dropdown.hidden{display:none;}
.user-actions-dropdown.drop-up{top:auto;bottom:calc(100% + 8px);}/* flip above the trigger on last rows */
.user-actions-dropdown .dropdown-divider{height:1px;background:var(--card-border);margin:6px 6px;}
.user-actions-dropdown .dropdown-item {
    width:100%;
    text-align:left;
    background:transparent;
    border:none;
    color:var(--text-color);
    padding:9px 11px;
    font-size:0.88rem;
    font-weight:700;
    display:flex;
    align-items:center;
    gap:11px;
    cursor:pointer;
    border-radius:10px;
    white-space:nowrap;
    transition:background 0.15s,color 0.15s;
}
.user-actions-dropdown .dropdown-item i{width:16px;text-align:center;font-size:0.95rem;color:var(--text-muted);transition:color 0.15s;}
.user-actions-dropdown .dropdown-item:hover{background:rgba(111,95,228,0.1);color:var(--primary);}
.user-actions-dropdown .dropdown-item:hover i{color:var(--primary);}
.user-actions-dropdown .dropdown-item.danger{color:var(--danger);}
.user-actions-dropdown .dropdown-item.danger i{color:var(--danger);}
.user-actions-dropdown .dropdown-item.danger:hover{background:rgba(239,68,68,0.12);}
/* No pointer-events:none — it would suppress the not-allowed cursor AND the title tooltip. A native
   disabled <button> already blocks clicks, so hover feedback is safe to keep. */
.user-actions-dropdown .dropdown-item:disabled{opacity:0.4;cursor:not-allowed;color:var(--text-muted);}
.user-actions-dropdown .dropdown-item:disabled:hover{background:transparent;color:var(--text-muted);}
.user-actions-dropdown .dropdown-item:disabled i{color:var(--text-muted);}

/* User-details modal (admin) */
.user-modal-overlay{position:fixed;inset:0;z-index:2000;}
.user-modal-overlay.hidden{display:none;}
.user-modal-card{scrollbar-width:thin;scrollbar-color:var(--card-border) transparent;}
.user-modal-card::-webkit-scrollbar{width:8px;}
.user-modal-card::-webkit-scrollbar-track{background:transparent;}
.user-modal-card::-webkit-scrollbar-thumb{background:var(--card-border);border-radius:8px;border:2px solid var(--modal-surface);}
.user-modal-card::-webkit-scrollbar-thumb:hover{background:var(--text-muted);}
.user-modal-close{position:absolute;width:34px;height:34px;border-radius:50%;border:1px solid var(--card-border);background:var(--bg-color);color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.2s;}
.user-modal-close:hover{background:var(--danger);color:#fff;border-color:var(--danger);}
.user-modal-kicker{display:flex;align-items:center;gap:7px;margin:0 44px 7px 0;color:var(--primary);font-size:0.72rem;font-weight:850;letter-spacing:0.11em;text-transform:uppercase;}
.user-modal-title{margin:0 44px 7px 0;color:var(--text-color);}
.user-modal-intro{margin:0 30px 20px 0;color:var(--text-muted);font-size:0.9rem;line-height:1.55;}
.user-create-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 14px;}
.user-optional{color:var(--text-muted);font-size:0.72rem;font-weight:700;}
.user-test-choice {
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:18px;
    margin:2px 0 20px;
    padding:13px 14px;
    border:1px solid var(--card-border);
    border-radius:12px;
    background:rgba(111,95,228,0.045);
}
.user-test-choice>div{display:flex;flex-direction:column;gap:3px;}
.user-test-choice strong{font-size:0.9rem;}
.user-test-choice span{color:var(--text-muted);font-size:0.76rem;line-height:1.4;}
.user-password-mode{display:flex;gap:7px;padding:5px;border:1px solid var(--card-border);border-radius:12px;background:rgba(111,95,228,0.035);}
.user-mode-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:9px 11px;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--text-muted);font:700 0.78rem var(--font-body);cursor:pointer;transition:background 0.15s,color 0.15s,border-color 0.15s;}
.user-mode-btn.active{background:var(--card-bg);border-color:rgba(111,95,228,0.18);color:var(--primary);box-shadow:0 5px 14px -10px rgba(54,41,132,0.6);}
.user-custom-password{margin:12px 0 0;}
.user-field-hint{margin:6px 0 0;color:var(--text-muted);font-size:0.72rem;}
.user-form-error{margin:12px 0 0;padding:10px 12px;border:1px solid rgba(239,68,68,0.2);border-radius:9px;background:rgba(239,68,68,0.07);color:var(--danger);font-size:0.82rem;font-weight:700;}
.user-modal-actions{display:flex;align-items:center;justify-content:flex-end;gap:9px;margin-top:20px;}
.user-credential-warning{display:flex;align-items:flex-start;gap:9px;margin-bottom:16px;padding:11px 12px;border:1px solid rgba(244,162,60,0.24);border-radius:10px;background:rgba(244,162,60,0.08);color:#966018;font-size:0.78rem;line-height:1.45;}
.user-credential-warning i{margin-top:2px;color:var(--secondary);}
.user-credential-row{margin-top:13px;}
.user-credential-row>label{display:block;margin-bottom:6px;color:var(--text-muted);font-size:0.72rem;font-weight:800;text-transform:uppercase;letter-spacing:0.06em;}
.user-credential-row>div{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:7px;}
.user-credential-row input{min-width:0;padding:10px 11px;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);color:var(--text-color);font:700 0.86rem ui-monospace,monospace;}
/* No bottom margin since Phase 188: the head sits above the dialog's scroller, and the first section's
   20px top margin no longer collapses with it, so 4px here made the gap 24. */
.udt-head{display:flex;align-items:center;gap:16px;}
.udt-avatar{width:60px;height:60px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:1.8rem;color:var(--primary);background:var(--bg-color);border:1px solid var(--card-border);}
.udt-name{font-family:var(--font-display);margin:0 0 6px;font-size:1.3rem;}
.udt-badges{display:flex;gap:8px;flex-wrap:wrap;}
.udt-section{margin-top:20px;}
.udt-section-title{display:flex;align-items:center;gap:8px;font-size:0.78rem;text-transform:uppercase;letter-spacing:1px;color:var(--text-muted);font-weight:800;margin:0 0 8px;padding-bottom:8px;border-bottom:1px solid var(--card-border);}
.udt-section-title i{color:var(--primary);}
.udt-row{display:flex;justify-content:space-between;gap:14px;padding:6px 0;font-size:0.92rem;}
.udt-label{color:var(--text-muted);font-weight:700;}
.udt-value{font-weight:700;text-align:right;word-break:break-word;}
.udt-yes{color:#10b981;font-weight:800;}
.udt-no{color:var(--text-muted);font-weight:700;}
/* PR2/PR4: role + Pro management inside the details modal */
.udt-manage .udt-value select.udt-role-select{width:auto;min-width:150px;padding:8px 34px 8px 12px;font-size:0.9rem;}
.udt-pro-controls{margin-top:14px;display:flex;flex-direction:column;gap:9px;}
.udt-pro-line{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.udt-pro-lbl{font-size:0.76rem;font-weight:800;text-transform:uppercase;letter-spacing:0.4px;color:var(--text-muted);width:72px;flex:none;}
.udt-pro-inline{display:inline-flex;align-items:center;gap:6px;}
.udt-pro-amount{width:56px;padding:7px 8px;border:1px solid var(--card-border);border-radius:7px;background:var(--background);color:var(--text);font-size:0.85rem;text-align:center;}
.udt-pro-date{padding:7px 9px;border:1px solid var(--card-border);border-radius:7px;background:var(--background);color:var(--text);font-size:0.85rem;}
.udt-pro-btn{padding:7px 12px;border:1px solid var(--card-border);border-radius:7px;background:var(--bg-color);color:var(--text-color);font-size:0.82rem;font-weight:700;cursor:pointer;white-space:nowrap;display:inline-flex;align-items:center;gap:5px;transition:all 0.15s;}
.udt-pro-btn:hover{border-color:var(--primary);color:var(--primary);}
.udt-pro-btn.is-primary{background:var(--primary);border-color:var(--primary);color:#fff;}
.udt-pro-btn.is-primary:hover{filter:brightness(1.06);color:#fff;}
.udt-pro-btn.is-danger{color:var(--danger);border-color:rgba(239,68,68,0.3);}
.udt-pro-btn.is-danger:hover{background:rgba(239,68,68,0.1);border-color:var(--danger);color:var(--danger);}
.udt-pro-note{margin:4px 0 0;font-size:0.77rem;color:var(--text-muted);line-height:1.5;display:flex;gap:7px;}
.udt-pro-note i{color:var(--primary);margin-top:2px;flex:none;}
.udt-account-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:12px;}
/* ===== Admin Dashboard (Galaxy look) — rendered by assets/js/admin-dashboard.js ===== */
.dash-body{position:relative;z-index:1;}
.dash-galaxy{display:flex;flex-direction:column;gap:16px;}
/* engagement pulse hero — headline + mini-stats on the left, a real 14-day activity chart on the right */
/* Phase 170: the hero and every tile below it are KIT cards — the same base surface, edge and depth
   the settings cards and the learner panels take. The violet/coral wash and the gradient-clipped
   numerals went with them: eight tinted boxes in a row erased the one number that matters. */
.dash-hero{display:flex;align-items:center;justify-content:space-between;gap:28px;flex-wrap:wrap;padding:16px 18px;border-radius:20px;border:1px solid var(--surface-edge-shade);background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
.dash-hero-left{display:flex;flex-direction:column;gap:14px;}
.dash-hero-num{font-family:var(--font-display);font-weight:800;font-size:2.75rem;line-height:1;color:var(--primary);}
.dash-hero-lab{color:var(--text-secondary);font-size:0.82rem;font-weight:600;margin-top:2px;max-width:240px;}
.dash-hero-minis{display:flex;gap:22px;flex-wrap:wrap;}
.dash-mini{display:flex;flex-direction:column;gap:2px;}
.dash-mini b{font-family:var(--font-display);font-size:1.38rem;font-weight:800;color:var(--text-color);}
.dash-mini span{font-size:0.64rem;font-weight:800;color:var(--text-secondary);text-transform:uppercase;letter-spacing:1px;}
.dash-hero-chart{flex:1 1 420px;min-width:260px;max-width:760px;margin-left:auto;}
.dash-hero-chart-cap{font-size:0.72rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:0.6px;margin-bottom:8px;}
.dash-sparkline{position:relative;width:100%;height:clamp(72px,7vw,92px);}
.dash-sparkline svg{width:100%;height:100%;display:block;overflow:visible;}
.dash-spark-end {
    position:absolute;
    left:calc(98.333% - 4px);
    top:calc(var(--dash-end-y) - 4px);
    width:8px;
    height:8px;
    border-radius:50%;
    background:var(--secondary);
    box-shadow:0 0 0 3px rgba(244,130,92,0.12),0 0 12px rgba(244,130,92,0.28);
    pointer-events:none;
}
.dash-spark-empty {
    position:absolute;
    top:50%;
    left:50%;
    padding:2px 8px;
    border-radius:999px;
    background:var(--card-bg);
    color:var(--text-muted);
    font-size:0.72rem;
    transform:translate(-50%,-50%);
}
/* attention chips (shown only when non-zero) */
.dash-flags{display:flex;gap:10px;flex-wrap:wrap;}
.dash-flag{display:inline-flex;align-items:center;gap:7px;font-size:0.85rem;font-weight:600;padding:6px 12px;border-radius:999px;background:var(--card-glass);border:1px solid var(--card-border);color:var(--text-muted);}
.dash-flag.warn{color:var(--danger);border-color:rgba(239,68,68,0.3);}
.dash-flag i{opacity:0.8;}
.dash-sec{font-family:var(--font-display);font-weight:700;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.8px;color:var(--text-secondary);margin:2px 2px -4px;}
/* stat grid */
.dash-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;}
/* The stat card is a ROW: a tinted icon tile, then the number, its label and its sub-line in one
   text column (`.dash-card-text`, added in admin-dashboard.js beside the classes that were already
   there). It reads at a glance where four stacked lines per box did not. */
.dash-card{position:relative;display:flex;align-items:center;gap:14px;padding:16px 18px;border-radius:20px;border:1px solid var(--surface-edge-shade);background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);transition:border-color 0.18s ease,box-shadow 0.18s ease;}
.dash-card:hover{border-color:rgba(79,62,143,0.3);box-shadow:0 22px 48px -38px rgba(40,28,91,0.5),inset 0 1px rgba(255,255,255,0.88);}
.dash-card-ic{width:36px;height:36px;flex:0 0 36px;border-radius:11px;display:grid;place-items:center;font-size:1rem;color:var(--tc);background:var(--tc-soft);}
.dash-card-text{display:flex;flex-direction:column;gap:2px;min-width:0;}
.dash-card-big{font-family:var(--font-display);font-weight:800;font-size:1.6rem;line-height:1;color:var(--text-color);}
.dash-card-big.sm{overflow:hidden;font-size:1.1rem;text-overflow:ellipsis;white-space:nowrap;}
.dash-card-lab{overflow:hidden;font-size:0.78rem;color:var(--text-secondary);font-weight:700;text-overflow:ellipsis;white-space:nowrap;}
/* --text-secondary, not --text-muted, for the reason recorded on .sa-sub below: #7A7596 is ~3.9:1 on
   the card and these are normal text at 11.5px, under the 4.5:1 AA floor. */
.dash-card-sub{overflow:hidden;font-size:0.72rem;color:var(--text-secondary);text-overflow:ellipsis;white-space:nowrap;}
.dash-card.t-violet,.dash-bar.t-violet{--tc:var(--primary);--tc-soft:rgba(111,95,228,0.14);}
.dash-card.t-coral,.dash-bar.t-coral{--tc:var(--secondary);--tc-soft:rgba(244,130,92,0.16);}
.dash-card.t-gold,.dash-bar.t-gold{--tc:var(--accent);--tc-soft:rgba(244,162,60,0.16);}
.dash-card.t-mint,.dash-bar.t-mint{--tc:var(--success);--tc-soft:rgba(16,185,129,0.14);}
/* lower panels */
.dash-low{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);gap:14px;}
.dash-panel{padding:16px 18px;border-radius:20px;border:1px solid var(--surface-edge-shade);background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
/* A card TITLE, not the admin's uppercase kicker — it names the panel it sits on rather than
   labelling a run of controls, so it resets the transform, spacing and colour the global h3 gives. */
.dash-panel h3{display:flex;align-items:center;gap:9px;margin:0 0 14px;color:var(--text-color);font-family:var(--font-display);font-size:1.06rem;font-weight:700;letter-spacing:normal;text-transform:none;}
.dash-panel h3 i{color:var(--primary);}
.dash-skeleton-shell{pointer-events:none;}
.dash-skeleton-shell .dash-hero-left{flex:0 1 310px;}
.dash-skeleton-shell .dash-hero-left>.dash-hero-minis{margin-top:2px;}
.dash-skeleton-mini{display:flex;flex-direction:column;gap:7px;width:62px;}
.dash-skeleton-mini .sk-short{width:74%;}
.dash-skeleton-mini .sk-medium{width:100%;height:9px;}
.dash-skeleton-chart .sk-chart{width:100%;height:76px;margin-top:12px;border-radius:16px;}
.dash-skeleton-section{width:92px;height:12px;margin:2px 2px -4px;}
/* The skeleton keeps its stacked shape (the placeholder markup in views/admin.php is unchanged) but
   sits at the height the real row card now takes, so nothing jumps when the payload lands. */
.dash-skeleton-card{display:block;min-height:86px;}
.dash-skeleton-card .skeleton-bar{margin-bottom:10px;}
.dash-skeleton-card .sk-icon{margin-bottom:16px;}
.dash-skeleton-panel{display:flex;flex-direction:column;gap:14px;min-height:170px;}
.dash-skeleton-table{display:flex;flex-direction:column;gap:15px;}
.dash-skeleton-row{display:grid;grid-template-columns:1.2fr 1.5fr 0.7fr;gap:18px;padding-top:3px;}
.dash-skeleton-row .skeleton-bar{width:100%;}
/* coverage bars (current vs full-coverage target) */
.dash-bar{display:flex;align-items:center;gap:12px;margin:11px 0;}
.dash-bar-lab{font-size:0.85rem;color:var(--text-color);width:148px;flex:none;}
.dash-bar-track{flex:1;height:9px;border-radius:9px;background:rgba(111,95,228,0.10);overflow:hidden;}
.dash-bar-fill{display:block;height:100%;border-radius:9px;background:var(--tc);transition:width 0.4s ease;}
.dash-bar-val{font-family:var(--font-display);font-weight:700;font-size:0.85rem;color:var(--text-color);min-width:58px;text-align:right;white-space:nowrap;}
.dash-bar-val small{font-weight:600;color:var(--text-muted);}
/* languages — baseline-align the name and the meta so their text sits on one line; centre the flag */
.dash-lang{display:flex;align-items:baseline;gap:11px;padding:9px 0;border-bottom:1px dashed var(--card-border);}
.dash-lang:last-child{border-bottom:0;}
.dash-lang .fi{align-self:center;flex:none;}
.dash-lang-name{flex:1;font-weight:600;font-size:0.9rem;}
.dash-lang-meta{font-size:0.8rem;color:var(--text-muted);}
/* recent signups */
.dash-rows-scroll{overflow-x:auto;}
.dash-rows{width:100%;border-collapse:collapse;}
.dash-rows td{padding:10px 6px;font-size:0.88rem;border-bottom:1px solid var(--card-border);white-space:nowrap;}
.dash-rows tr:last-child td{border-bottom:0;}
.dash-nm{font-weight:700;}
.dash-muted{color:var(--text-muted);}
.dash-tag-cell{text-align:right;}
.dash-tag{display:inline-block;font-family:var(--font-display);font-weight:700;font-size:0.68rem;letter-spacing:0.4px;padding:3px 9px;border-radius:999px;text-transform:uppercase;}
.dash-tag.pro{background:rgba(244,162,60,0.18);color:var(--accent-hover);}
.dash-tag.free{background:rgba(122,117,150,0.14);color:var(--text-muted);}
.dash-tag.admin{background:rgba(111,95,228,0.16);color:var(--primary);margin-left:6px;}
@media(max-width:900px){
.dash-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
.dash-low{grid-template-columns:1fr;}
.dash-hero-chart{max-width:none;margin-left:0;}
}
@media(max-width:520px){
.dash-hero-num{font-size:2.6rem;}
.dash-hero-chart{min-width:0;}
/* ⚠️ ONE column on a phone. Two 176px stat cards leave ~90px of text beside a 36px tile, and every
   label but the shortest ellipsised to nothing readable — the row card is full width here. */
.dash-grid{grid-template-columns:minmax(0,1fr);}
}

.img-cat-picker{
    display:inline-flex;
    align-items:center;
    gap:6px;
    font-size:0.85rem;
    color:var(--text-muted);
    margin-right:4px;
}
.img-cat-picker select{padding:6px 8px;min-width:120px;}
.img-cat-picker .dd{min-width:120px;}
.vocab-stats-actions{margin-top:14px;}

/* Generator activity viewer (Admin → General → Activity log) — assets/js/admin.js activityRender */
.gen-log-filters{margin-bottom:14px;}/* 183: layout is the shared bar's. */
/* Segmented pill selector for the log filters — one filled segment marks the active filter. */
.gen-log-seg{display:inline-flex;background:var(--bg-color);border:1px solid var(--card-border);border-radius:999px;padding:3px;gap:2px;flex-wrap:wrap;}
.gen-log-chip{border:none;background:transparent;color:var(--text-muted);font-weight:700;font-size:0.82rem;padding:6px 14px;border-radius:999px;cursor:pointer;white-space:nowrap;transition:color 0.15s,background 0.15s;}
.gen-log-chip:hover{color:var(--primary);}
.gen-log-chip.active{background:var(--primary);color:#fff;box-shadow:0 2px 7px -2px rgba(111,95,228,0.5);}
.gen-log-chip.active:hover{color:#fff;}
.gen-log-refresh-btn{width:34px;height:34px;border-radius:50%;border:1px solid var(--card-border);background:var(--card-bg);color:var(--text-muted);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:all 0.15s;}
.gen-log-refresh-btn:hover{border-color:var(--primary);color:var(--primary);}
.gen-log-list{
    display:flex;
    flex-direction:column;
    gap:6px;
}
.gen-log-row{border:1px solid var(--card-border);border-radius:12px;overflow:hidden;background:var(--card-bg);}
.gen-log-row.warn{border-color:rgba(232,152,63,0.5);}
.gen-log-head{
    display:flex;
    align-items:center;
    gap:12px;
    width:100%;
    padding:10px 14px;
    background:none;
    border:0;
    cursor:pointer;
    text-align:left;
    font:inherit;
    color:var(--text-color);
}
.gen-log-head:hover{background:rgba(111,95,228,0.05);}
.gen-log-fam{
    font-family:var(--font-display);
    font-weight:700;
    font-size:0.8rem;
    color:var(--primary);
    min-width:120px;
    flex:none;
    text-transform:capitalize;
}
.gen-log-sum{flex:1;font-size:0.85rem;color:var(--text-color);min-width:0;}
.gen-log-detail{
    margin:0;
    padding:12px 14px;
    border-top:1px solid var(--card-border);
    background:var(--bg-color);
    font-size:0.78rem;
    overflow-x:auto;
    white-space:pre;
    color:var(--text-color);
}
.gen-log-shown{font-size:0.78rem;color:var(--text-muted);}
.sys-log-user{font-weight:800;color:var(--text-color);flex:none;min-width:90px;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* The admin-only activity tints. Their siblings (act-login, act-logout, act-session, act-settings,
   act-security) are in pages.css because the You page draws them too — see the note there. */
.sys-log-row.act-sub .sys-log-icon{background:rgba(234,179,8,0.14);color:#b8860b;}
.sys-log-row.act-complete .sys-log-icon{background:rgba(16,185,129,0.12);color:#10b981;}
.sys-log-row.act-admin .sys-log-icon{background:rgba(244,162,60,0.14);color:#e8983f;}
.support-source{display:inline-flex;align-items:center;padding:1px 5px;border-radius:5px;background:rgba(111,95,228,0.08);color:var(--primary);font-size:0.66rem;font-weight:800;text-transform:uppercase;}
.support-source--guest{background:rgba(244,130,92,0.09);color:#B4532F;}
.support-readiness{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:11px;}
.support-ready{display:inline-flex;align-items:center;gap:5px;padding:5px 8px;border-radius:8px;font-size:0.72rem;font-weight:700;}
.support-ready.is-ready{background:rgba(16,185,129,0.09);color:#047857;}
.support-ready.is-missing{background:rgba(239,68,68,0.08);color:#B91C1C;}
/* An opened message is three things — who and when, the text, the attachments — and they were
   running together as loose blocks. Each gets a card. */
.mail-card{background:var(--card-glass);border:1px solid var(--card-border);border-radius:12px;padding:16px 18px;margin-bottom:14px;}
.mail-card-head .support-detail-subject{margin:0 0 8px;}
.mail-card-head .support-detail-meta{margin:0 0 2px;}
.mail-meta-from{font-weight:700;color:var(--text-color);}
.mail-meta-addr{color:var(--text-muted);}
.mail-card-body{padding:18px;}
/* A destructive action must not look like the two beside it. */
.btn-ghost.is-danger{color:var(--danger);}
.btn-ghost.is-danger:hover{background:var(--danger-glow);color:var(--danger-hover);}
.btn.is-danger{background:var(--danger);border-color:var(--danger);}
.btn.is-danger:hover{background:var(--danger-hover);border-color:var(--danger-hover);}

/* A mail row is not a ticket row. ⚠️ `.support-row`'s first column is 110px because a ticket shows a
   REF CODE there, and `.support-row-ref` styles it as one — 800 weight, 0.82rem, accent colour. A
   person's name in that cell wrapped onto two lines and read as a code. The sender gets the room
   (a name is the thing you scan a mailbox by) and the subject gives some up; the name is ellipsised
   on one line rather than wrapped, and typed as text rather than as an identifier.
   Scoped to .mail-row: .adm-support-row is shared with the ticket queue and must not change. */
.support-row.mail-row{grid-template-columns:minmax(160px,220px) 1fr auto auto;}
.mail-row .support-row-ref{font-weight:600;font-size:0.9rem;color:var(--text-color);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;}
.mail-row.is-unseen .support-row-ref{font-weight:800;}
.mail-row .support-row-subject{font-weight:500;}
.mail-row.is-unseen .support-row-subject{font-weight:700;}
@media (max-width:700px){.support-row.mail-row{grid-template-columns:1fr auto;}.mail-row .support-row-meta{grid-column:1 / -1;}}

/* Compose (122.2). A compose form is not a settings screen: the fields ARE the content, so they run
   full width under their labels rather than sitting right-aligned opposite them. */
.mail-compose-head{display:flex;flex-direction:column;gap:14px;margin-bottom:16px;}
.mail-field{display:block;}
.mail-field-lab{display:block;font-weight:700;font-size:0.88rem;margin-bottom:6px;color:var(--text-color);}
.mail-field .theme-input{width:100%;}
/* The Save bar was hugging the settings card above it. */
.mail-settings-actions{margin-top:18px;}
/* Clear-the-password reads as a text action under the field, not a control competing with it. */
.link-btn{background:none;border:0;padding:0;font:inherit;color:var(--primary);cursor:pointer;text-decoration:underline;}
.link-btn:hover{opacity:0.8;}

/* --- Admin support + Turnstile panes (Phase 59) --- */
/* ⚠️ Circular, not a pill: the number is one or two digits, and a 9px radius on a 7px-padded box
   read as a lozenge with a cramped figure inside. A fixed min-width with the radius at 999px keeps a
   single digit round and lets a three-digit count grow into a stadium without the text touching the
   edge. The figure is larger and the ground is the accent's own strong tone, so a white numeral has
   real contrast rather than sitting on a mid-tone. */
.admin-nav-badge{background:var(--primary);color:#fff;font-size:0.78rem;font-weight:800;line-height:1;border-radius:999px;min-width:20px;height:20px;padding:0 6px;margin-left:auto;flex:none;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 1px 3px rgba(0,0,0,0.25);}
.adm-support-row.is-needs-reply{border-left:3px solid var(--secondary);}
.adm-support-user{display:block;font-size:0.74rem;font-weight:400;color:var(--text-muted);}
.adm-support-grid{display:grid;grid-template-columns:1fr 250px;gap:18px;align-items:start;}
.adm-support-card{border:1px solid var(--card-border);border-radius:12px;padding:14px;margin-bottom:12px;background:var(--card-glass);}
.adm-support-card h4{margin:0 0 6px;font-size:0.82rem;}
.adm-support-card h4:not(:first-child){margin-top:12px;}
.adm-support-card p{margin:0 0 4px;font-size:0.8rem;color:var(--text-muted);overflow-wrap:anywhere;}
.support-msg.is-note{background:rgba(244,162,60,0.09);border-color:rgba(244,162,60,0.4);align-self:stretch;max-width:100%;}
.adm-side-label{display:block;font-size:0.72rem;font-weight:800;text-transform:uppercase;letter-spacing:0.4px;color:var(--text-muted);margin:10px 0 4px;}
.adm-side-label:first-child{margin-top:0;}
.adm-support-card .theme-input{width:100%;}
.adm-support-delete{width:100%;justify-content:center;margin-top:2px;}
.support-actions-spacer{flex:1;}
.adm-support-noterow{display:flex;gap:8px;align-items:flex-start;border-top:1px dashed var(--card-border);padding-top:9px;}
.adm-support-noterow textarea{flex:1;}
/* 59.2: the composer spans the FULL panel width below the thread/side grid. */
.support-replybox-full{margin-top:18px;border-top:1px solid var(--card-border);padding-top:16px;}
.asd-canned-wrap{flex:none;width:200px;}
.asd-sendstatus-wrap{flex:none;width:190px;}
.support-waiting{color:var(--secondary);font-weight:800;}
.adm-turnstile-key{margin:0 0 6px;font-size:0.86rem;}
.support-stars-inline{color:var(--accent);font-weight:800;}
.ass-canned-row{display:grid;grid-template-columns:220px 1fr auto;gap:8px;align-items:start;margin-bottom:8px;}

/* --- Phase 122 WS-B: admin Mailbox panel — reuses .support-row/.adm-support-card/.support-chip/
   .support-replybox* etc. verbatim (see admin-support-mail.js); only two rules are actually new. */
.adm-support-row.is-unseen{border-left:3px solid var(--secondary);}
.mail-body{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.6;margin-bottom:14px;}

/* --- Phase 123 WS-A: the Activation panel (Admin -> General -> Activation). Reuses .support-empty,
   .theme-input and .btn verbatim; the selects are plain <select> stylised by watchSelects() into the
   compact .dd, so nothing here styles a dropdown. Deliberately restrained: this is a reading
   surface, and a table that competes for attention is harder to read, not easier. */
/* ⚠️ ONE HEIGHT FOR EVERY CONTROL IN THE ROW. Measured before this rule existed: the date inputs
   were 50px (.theme-input, 12px padding / 2px border / 1.05rem), the stylised selects 31px
   (.dd-btn, 7px 11px / 1px / 0.85rem) and the button 44px (.btn, 12px 28px / 1rem) — a 19px
   spread across four controls that read as one row. The token is what stops the three rules
   drifting apart again.
   ⚠️ The INPUTS KEEP A 16px FONT while the buttons do not, and that asymmetry is deliberate:
   below 16px iOS zooms the viewport when a form field takes focus, which Phase 120.3 established
   as a functional floor rather than a style choice. .dd-btn and .btn are buttons and carry no
   such risk, so they keep the compact type the UI contract asks for in a filter toolbar. The row
   is squared up by HEIGHT, never by shrinking the one thing that must not shrink. */
/* 183: the direction, the gap, the control height and the button's metrics are the shared bar's now
   (§ Phase 183 near the end of this file) — this component was where that idea started, and keeping a
   second copy of it here is what the owner's standardise-and-reuse instruction is about. What is left
   is the ONE thing this bar does differently: its controls sit under their own labels (`.act-field`),
   so they align on their BOTTOM edge, not their middle. */
.act-toolbar{align-items:flex-end;margin-bottom:18px;}
/* .theme-input's large form-field padding still has to come down to a toolbar's; its font size does not. */
.act-toolbar .theme-input{padding:0 10px;border-width:1px;font-size:1rem;}
.act-field{display:flex;flex-direction:column;gap:5px;}
.act-lab{font-size:0.78rem;font-weight:700;color:var(--text-muted);}
/* A wide table must scroll in its OWN box - the admin panel body must never scroll sideways. */
.act-scroll{overflow-x:auto;margin-bottom:22px;}
.act-table{width:100%;border-collapse:collapse;font-size:0.9rem;}
.act-table th{text-align:left;font-size:0.74rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--text-muted);padding:0 10px 8px;white-space:nowrap;}
/* Numeric headers share their column's right edge. `.act-table th` (0,1,1) outranks a bare `.act-num` (0,1,0), so
   a numeric heading sat left while its values sat right and every column read as slightly misaligned. */
.act-table th.act-num{text-align:right;}
/* These sections now live inside .admin-tool-block, which brings its own padding — drop the trailing margin so
   the card does not carry a band of dead space under its last row. */
.admin-tool-block>.act-scroll:last-child,.admin-tool-block>.act-ret:last-child{margin-bottom:0;}
.act-table td{padding:9px 10px;border-top:1px solid var(--card-border);white-space:nowrap;}
.act-bucket{font-weight:700;}
.act-src{color:var(--text-muted);}
.act-num{text-align:right;font-variant-numeric:tabular-nums;}
/* The share sits under its count rather than beside it: a number and a percentage on one line read
   as two competing figures, and the count is the one being compared down the column. */
.act-share{display:block;font-size:0.72rem;color:var(--text-muted);}
.act-activated{font-weight:800;color:var(--primary);}
/* Expenses (Phase 143) - the daily spend chart. Plain flex bars rather than inline SVG: the chart
   is generated per request, so it can never be a stored asset. */
.exp-chart{display:flex;align-items:flex-end;gap:2px;height:180px;padding-bottom:2px;border-bottom:1px solid var(--card-border);margin-bottom:22px;}
.exp-bar{flex:1 1 0;min-width:2px;background:var(--primary);opacity:0.9;border-radius:2px 2px 0 0;}
.exp-bar[style*="height:0%"]{opacity:0.15;min-height:2px;background:var(--card-border);}
/* Models (Phase 143) - the kill switch and the monthly ceiling, on every model row. */
.model-guards{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:8px;}
.model-guard-cap{display:inline-flex;align-items:center;gap:4px;}
.model-guard-cur{color:var(--text-muted);font-size:0.9rem;}
.model-guard-cap input{width:156px;}
/* A switched-off row is dimmed so the page reads at a glance, but stays fully operable - an
   operator must be able to reconfigure a purpose they have just turned off. */
.set-arow:has(input[data-enabled-key]:not(:checked)) .model-inputs>*:not(.model-guards){opacity:0.55;}
.act-ret{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-bottom:22px;}
/* 183: `--card-glass` is the LEARNER's deliberate 80% glass (the starfield shows through it); on an
   admin page it is simply a see-through panel. The token is untouched; these two read the card. */
.act-ret-curve{background:var(--surface-base-bg);border:1px solid var(--card-border);border-radius:12px;padding:16px 18px;}
.act-ret-title{margin:0 0 4px;font-size:1rem;}
.act-ret-note{margin:0 0 14px;font-size:0.8rem;color:var(--text-muted);line-height:1.5;}
.act-ret-row{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;}
.act-ret-cell{display:flex;flex-direction:column;gap:2px;text-align:center;}
.act-ret-day{font-size:0.72rem;color:var(--text-muted);font-weight:700;}
.act-ret-rate{font-size:1.3rem;font-weight:800;font-variant-numeric:tabular-nums;}
.act-ret-den{font-size:0.72rem;color:var(--text-muted);}
/* margin-top matches .admin-tool-block's 18px so this card keeps the panel's rhythm. ⚠️ It used to inherit its
   separation from .act-ret's margin-bottom, which 127.1 zeroed when the curves moved inside a card — leaving
   this flush against the block above it. A section must own its own spacing, not borrow a sibling's. */
.act-basis{margin-top:18px;background:var(--surface-base-bg);border:1px solid var(--card-border);border-radius:12px;padding:14px 18px;}
.act-basis-sum{font-weight:700;cursor:pointer;}
.act-basis-list{margin:12px 0 0;padding-left:20px;color:var(--text-muted);font-size:0.86rem;line-height:1.65;}
.act-basis-list li{margin-bottom:7px;}
@media (max-width:820px){.act-ret{grid-template-columns:1fr;}.act-field{flex:1 1 140px;}}


/* --- Grouped settings cards (59.4 — Concept A, the admin settings template) --- */
/* 1-column by default (descriptions are lengthy); .set-groups--2col opts into two columns.
   ⚠️ Phase 170: .set-groups is the GRID ONLY. It used to be a tinted panel around the cards, which
   put a frame inside a frame inside the tab's own card; the tab card is gone and each .set-group is
   now a KIT card standing on the scenery, so the wrapper has nothing left to draw. */
.set-groups{display:grid;grid-template-columns:1fr;gap:16px;align-items:start;}
/* Phase 171: an UNFILLED grid still takes a slot in the pane's flex column. #models-openrouter is
   present on every load and filled only while a row actually routes to OpenRouter, so Models opened
   with 48px between the last card and Save — its own 16px margin plus a 16px gap on each side of a
   box with no height. Empty, it draws nothing; now it occupies nothing either. */
.set-groups:empty{display:none;}
/* ⚠️ FROM THE 29-TAB SWEEP. These generic blocks were built as a TINT inside the tab's white card —
   white at 0.55–0.6 alpha. With the card retired they stand on the scenery, where that alpha over a
   warm backdrop reads as almost nothing and a table appears to float. As a DIRECT child of a tab a
   block is a panel now and takes the KIT card recipe; nested inside another panel each keeps its own
   quiet tint, so this never turns into a card inside a card. */
.lframe-layout .admin-tab-content>:is(.admin-snapshot,.admin-tool-block,.sync-panel,.vocab-stats-wrap,.vocab-gens){margin-top:0;padding:16px 18px;border:1px solid var(--surface-edge-shade);border-radius:20px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}

/* ── Phase 171 (D-12, 2026-09-17): NOTHING IN A TAB STANDS ON THE SCENERY ─────────────────────────
   Phase 170 retired the giant `.card` each tab lived in. The toolbars, picker rows, action bars,
   status lines and empty states that had been sitting loose INSIDE it were left standing directly on
   the unpainted scenery, which is what the owner saw. Each is a QUIET BAR now: the `.surface-base`
   MATERIAL (components.css owns the recipe — the rules below read the same three tokens and never
   re-declare it), compact geometry, and `margin:0` at tab level because the pane's own 16px flex gap
   is what spaces panels.
   ⚠️ THE CHILD COMBINATOR IS THE WHOLE SAFETY MECHANISM. A bar takes the material only when it is the
   tab's own child, or the child of one of the bare roots a lazily-rendered tab paints into. Operator
   API's four `.users-toolbar` rows live inside a `.set-group` and Expenses' `.admin-btn-group` inside
   `#exp-price-editor`; a descendant selector would frame those too and rebuild exactly the
   card-inside-a-card hierarchy Phase 170 removed. Section headings are NOT bars — a kicker that
   titles the panel under it is supposed to sit on the scenery (ui-contracts.md § Phase 170). */
/* THE PANE'S GAP IS THE SPACING, and a flex gap does not absorb a margin — it adds to it. Panels
   written when the tab was still one scrolling card kept their own vertical margins, and once every
   piece became a panel those margins read as a rhythm nobody chose: 36px under Image sync's log and
   under Progress Override's block, 30px under the Operator API readiness line, 32px between the
   Academy generator panels, against 16px everywhere else. Measured across the 29 panes, this rule
   changes five gaps and no others — every other consecutive pair was already exactly 16px. It leaves
   `#academy-list`'s 22px alone (an id beats it), which is deliberate: that margin is a commented
   grouping break between the chapter list and the tool panels under it, not an accident. Horizontal
   margins are untouched so a child that centres itself still can. */
.lframe-layout .admin-tab-content>*{margin-top:0;margin-bottom:0;}
.lframe-layout .admin-tab-content>:is(.academy-toolbar,.academy-search-row,.academy-level-bar,.admin-btn-group,.settings-actions,.users-toolbar,.mie-toolbar,.mie-hint,.gen-log-filters,.operator-api-state){margin:0;padding:10px 14px;border:1px solid var(--surface-edge-shade);border-radius:14px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
/* ⚠️ A BAR WITH NOTHING IN IT MUST NOT PAINT. Content Review's `#review-chapter-groups` is a
   `.users-toolbar` that its JS empties whenever the filter matches no chapters. Unpainted that was
   invisible; as a quiet bar it became a 22px empty pill floating between the toolbar and the table —
   found by looking at the tab, not by the probe, which only walks text. Written as a descendant so
   it holds at both levels. */
.lframe-layout .admin-tab-content :is(.act-toolbar,.academy-toolbar,.academy-search-row,.academy-level-bar,.admin-btn-group,.settings-actions,.users-toolbar,.mie-toolbar,.mie-hint,.gen-log-filters,.operator-api-state):empty{display:none;}
/* The lazily-rendered tabs paint into a bare root (`#adm-activation-root`, `#adm-support-root`,
   `#academy-settings`, the two settings <form>s): the root is the tab's child, so ITS children stand
   on the scenery too. The `:not()` list is the set of level-one wrappers that already paint — a bar
   inside one of those is in a frame already. No `margin` reset here: these roots are ordinary block
   containers with no gap, so their children's margins ARE the spacing. */
.lframe-layout .admin-tab-content>:not(.admin-snapshot,.admin-tool-block,.sync-panel,.vocab-stats-wrap,.vocab-gens,.set-group,.panel-stack,.users-table-wrapper)>:is(.act-toolbar,.settings-actions,.users-toolbar,.admin-btn-group){padding:10px 14px;border:1px solid var(--surface-edge-shade);border-radius:14px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
/* A loading line, an empty state or a readiness line IS the panel while it is the only thing there.
   Every call site replaces the wrapper wholesale (`root.innerHTML = …`), so the placeholder never
   coexists with the framed content that succeeds it — which is why this is a panel and not a card
   floating above a second frame. `:only-child` is what says "this is the whole content right now",
   and it also keeps the `<td class="table-loading-text">` inside a real table out of this. */
.lframe-layout .admin-tab-content>:not(.admin-snapshot,.admin-tool-block,.sync-panel,.vocab-stats-wrap,.vocab-gens,.set-group,.panel-stack,.users-table-wrapper)>:is(p.table-loading-text:only-child,p.vocab-hint.text-center,.support-empty,.mie-hint){margin:0;border:1px solid var(--surface-edge-shade);border-radius:20px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
.lframe-layout .admin-tab-content>:not(.admin-snapshot,.admin-tool-block,.sync-panel,.vocab-stats-wrap,.vocab-gens,.set-group,.panel-stack,.users-table-wrapper)>:is(p.vocab-hint.text-center,.mie-hint){padding:18px 20px;}
.set-group{border:1px solid var(--surface-edge-shade);border-radius:20px;padding:16px 18px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
.set-group-head{display:flex;align-items:center;gap:10px;padding:0 0 12px;font-family:var(--font-display);font-weight:800;font-size:1.06rem;color:var(--text-color);}
.set-group-head .sg-ic{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;color:#fff;flex:none;font-size:0.85rem;}
.set-group-note{font-size:0.79rem;line-height:1.45;color:var(--text-muted);padding:9px 2px 3px;}
.sg-ic.v{background:var(--primary);}
.sg-ic.c{background:var(--secondary);}
.sg-ic.g{background:var(--accent);}
.sg-ic.t{background:#8B7FE8;}
.set-arow{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 2px;border-bottom:1px solid rgba(79,62,143,0.1);}
.set-arow:last-child{border-bottom:none;}
.set-arow .sa-txt{min-width:0;}
.set-arow .sa-lab{font-weight:700;font-size:0.88rem;color:var(--text-color);margin:0;display:flex;align-items:center;gap:7px;flex-wrap:wrap;}
/* --text-secondary, not --text-muted: #7A7596 measures 4.36:1 on white and misses the AA floor for
   normal text, and every setting's second line is normal text (ui-contracts.md, Phase 144). */
.set-arow .sa-sub{font-size:0.78rem;line-height:1.42;color:var(--text-secondary);margin-top:2px;}
/* Phase 216: a sub-line in ANY admin table is a sub-line. `.sa-sub` had been styled table by table (the
   Operator API's tables, then Content Review's), so every other table's rendered at full body size beside
   its own value. #review-table keeps its own darker ink below, measured on its ground. */
.theme-table .sa-sub{font-size:0.78rem;line-height:1.4;color:var(--text-secondary);}
.set-arow .sa-ctl{flex:none;display:flex;align-items:center;gap:8px;}
.set-arow-wide{flex-direction:column;align-items:stretch;gap:10px;}
.set-arow .sa-ctl.model-inputs{flex-direction:column;align-items:stretch;gap:8px;width:100%;}
.model-inputs .model-input-wrap{display:flex;gap:6px;width:100%;}
.model-inputs .academy-model-input{flex:1;min-width:0;}
.model-provider-wrap{display:flex;align-items:center;gap:8px;}
.model-provider-lab{font-size:0.78rem;color:var(--text-muted);flex:0 0 auto;}
.model-provider-wrap .dd{flex:0 0 auto;min-width:190px;}
#models-openrouter{margin-top:16px;}
#models-openrouter .sa-sub code{font-size:0.74rem;padding:1px 5px;border-radius:5px;background:var(--bg-soft,rgba(228,222,245,0.45));}
/* Model picker (2026-08-16; SUMMARY-FIRST rework 2026-08-17): the Models page picks ids from each
   provider's own catalogue instead of having them typed. QUIET BY CONSTRUCTION — nothing here is a
   .btn, the filter reuses the .users-tool search idiom, so the page's one accent stays "Save models".
   At REST a slot is a single line: purpose word + the configured id as the chip that opens the list.
   The catalogue exists only while one is open, and only one exists (see MODEL_CATALOG_ID). */
.model-picker{display:flex;flex-direction:column;gap:8px;min-width:0;}
.model-slot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0;}
/* Fixed width so "Model" and "Fallback" line up as a column; 52px is the wider word (46.5px) plus air,
   and every pixel above that is one the value loses on a 375px screen. */
.model-slot-lab{flex:0 0 auto;width:52px;font-size:0.78rem;color:var(--text-muted);}
/* The value and its Clear are ONE shrinking unit: as loose siblings they wrap apart at 375px and leave
   the × orphaned on a line of its own. The pair shrinks (min-width:0) and the chip's text wraps inside
   it instead, so the control stays beside the value it clears at every width. */
.model-slot-pick{display:inline-flex;align-items:center;gap:8px;min-width:0;}
.model-slot-chip{display:inline-flex;align-items:center;gap:8px;max-width:100%;min-width:0;padding:5px 11px;border:1px solid var(--card-border);border-radius:999px;background:var(--bg-color);color:var(--text-color);font-family:ui-monospace,monospace;font-size:0.8rem;font-weight:700;text-align:left;cursor:pointer;overflow-wrap:anywhere;transition:all 0.15s ease;}
.model-slot-chip>span{min-width:0;overflow-wrap:anywhere;}
.model-slot-chip>i{flex:0 0 auto;font-size:0.76em;color:var(--text-muted);}
.model-slot-chip:hover,.model-picker.is-open .model-slot-chip{border-color:var(--primary);color:var(--primary);}
.model-slot-chip:hover>i,.model-picker.is-open .model-slot-chip>i{color:var(--primary);}
.model-slot-chip.is-empty{font-family:inherit;font-weight:600;color:var(--text-muted);}
.model-picker.is-invalid .model-slot-chip{border-color:var(--danger);box-shadow:0 0 0 2px var(--danger-glow);}
@media(prefers-reduced-motion:reduce){.model-slot-chip{transition:none;}}
/* Quiet icon button — the fallback's Clear, the catalogue's Close and its manual-entry toggle. Sized
   below .users-dir-btn on purpose: these sit inline with a value, not in a toolbar of their own. */
.model-slot-icon{width:28px;height:28px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);color:var(--text-muted);font-size:0.76rem;cursor:pointer;transition:all 0.15s ease;}
.model-slot-icon:hover{border-color:var(--primary);color:var(--primary);}
@media(prefers-reduced-motion:reduce){.model-slot-icon{transition:none;}}
/* A shape mismatch is persistent STATE, not a failure — amber note beside the chip it is about, in the
   app's established warning ink (#b45309), never the danger colour. */
.model-picker-warn{display:inline-flex;align-items:center;gap:5px;min-width:0;font-size:0.74rem;color:#b45309;overflow-wrap:anywhere;}
.model-picker-warn i{flex:0 0 auto;font-size:0.9em;}
.model-catalog{display:flex;flex-direction:column;gap:9px;min-width:0;padding:9px 10px;border:1px solid var(--card-border);border-radius:10px;background:var(--bg-color);}
.model-catalog-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0;}
.model-catalog-title{flex:1 1 auto;min-width:0;font-size:0.8rem;font-weight:700;color:var(--text-color);}
.model-catalog-prov{flex:0 0 auto;font-size:0.72rem;color:var(--text-muted);}
.model-catalog-tools{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.model-picker-filter-wrap{flex:1 1 150px;min-width:0;}
.model-picker-filter-wrap input[type="search"]{width:100%;min-width:0;}
.model-picker-manual{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.model-picker-manual .academy-model-input{flex:1 1 190px;min-width:0;max-width:none;}
.model-picker-grid{display:flex;flex-direction:column;gap:4px;max-height:212px;overflow-y:auto;padding-right:2px;}
.model-vendor-group{display:flex;align-items:center;flex-wrap:wrap;gap:4px;padding-top:5px;}
/* Soft separator between the groups that are actually on screen — a general sibling so the filter
   hiding a group cannot leave a rule floating above the first visible one. */
.model-vendor-group:not(.hidden)~.model-vendor-group:not(.hidden){border-top:1px solid var(--card-border);}
.model-vendor-lab{flex:1 1 100%;font-size:0.72rem;font-weight:600;letter-spacing:0.2px;color:var(--text-muted);}
.model-chip{max-width:100%;padding:4px 10px;border:1px solid var(--card-border);border-radius:999px;background:var(--card-bg);color:var(--text-color);font-family:ui-monospace,monospace;font-size:0.75rem;cursor:pointer;overflow-wrap:anywhere;}
.model-chip:hover{border-color:var(--primary);color:var(--primary);}
.model-chip.is-selected{border-color:var(--primary);background:rgba(111,95,228,0.1);color:var(--primary-hover);font-weight:700;box-shadow:inset 0 0 0 1px var(--primary);}
/* The saved-but-unlisted id: selected, and visibly not from the list. */
.model-vendor-group[data-stray="1"] .model-chip{border-style:dashed;}
.model-chip.is-failed{border-color:var(--danger);background:rgba(239,68,68,0.08);color:var(--danger);}
.model-chip:disabled{opacity:0.6;cursor:default;}
.model-picker-note{margin:0;font-size:0.78rem;line-height:1.45;color:var(--text-muted);}
.model-picker-note.is-unconfigured{color:var(--danger);}
.set-tier{font-size:0.66rem;font-family:var(--font-display);font-weight:800;letter-spacing:0.4px;padding:2px 7px;border-radius:6px;}
.set-tier.free{background:rgba(111,95,228,0.12);color:var(--primary-hover);}
.set-tier.pro{background:rgba(244,162,60,0.16);color:#b4770f;}
.set-tier.explicit{background:rgba(244,162,60,0.16);color:#b4770f;}
/* Phase 106 OPS-01: background-maintenance health. A verdict pill reusing .set-tier's chip metrics, plus
   a quiet refresh affordance in the group head — read-only state, so no accent control anywhere here. */
.maint-pill{font-size:0.66rem;font-family:var(--font-display);font-weight:800;letter-spacing:0.4px;padding:2px 7px;border-radius:6px;white-space:nowrap;}
.maint-pill.ok{background:rgba(34,197,94,0.14);color:#15803d;}
.maint-pill.warn{background:rgba(239,68,68,0.10);color:var(--danger);}
.maint-pill.idle{background:var(--surface-item-bg);color:var(--text-muted);}
.settings-actions{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:18px;}
.settings-actions .set-note-inline{color:var(--text-muted);font-size:0.82rem;margin:0;}

/* Phase 97.1: grouped Operator API controls. Bearer material exists only in the reveal textarea. */
.operator-api-state{margin:0 0 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;color:var(--text-muted);font-size:0.84rem;font-weight:700;}
.operator-api-state-text{margin:0;min-width:0;}
.operator-api-state-text.is-blocked{color:var(--danger);}
/* The authority window as live state, rendered from admin-operator-api.js into the tab wrapper. Kept
   quiet on purpose: it reports a condition, it is not an outcome announcement (those are toasts). */
.operator-api-authority{margin:0 0 12px;display:inline-flex;align-items:center;font-size:0.74rem;font-weight:700;color:#b4770f;background:rgba(244,162,60,0.16);border-radius:999px;padding:3px 11px;}
.operator-api-secret {
    display:grid;
    grid-template-columns:minmax(0,1fr) minmax(280px,1.2fr);
    gap:14px;
    align-items:center;
    margin:0 0 16px;
    padding:16px;
    border:1px solid color-mix(in srgb,var(--primary) 42%,var(--card-border));
    border-radius:14px;
    background:color-mix(in srgb,var(--primary) 7%,var(--card-bg));
}
.operator-api-secret h3{margin:0 0 5px;font-size:1rem;}
.operator-api-secret p{margin:0;color:var(--text-muted);font-size:0.8rem;line-height:1.45;}
.operator-api-secret textarea{width:100%;resize:none;font:0.78rem/1.45 var(--font-mono,monospace);word-break:break-all;}
.operator-api-secret-actions{grid-column:2;display:flex;gap:8px;flex-wrap:wrap;}
/* ⚠️ minmax(0,1fr), never a bare 1fr — the SAME failure #operator-api-tabs .set-group documents
   below. A grid item's automatic minimum size is its min-content width, so ONE greedy item (the
   selector picker, whose "No selector published for this resource" label plus its 240px floor is
   334px wide) forced this single track — and every field row riding it — past a 375px viewport.
   Caught in harness verification 2026-08-15: the Work-orders pane, not the narrow controls, was
   what pushed the page into horizontal scroll. */
.operator-api-form {
    display:grid;
    grid-template-columns:minmax(0,1fr);
    gap:14px;
    padding:12px 2px 6px;
}
/* Owner round 2 on the Programs form (2026-09-02): rows were hugging (8px), the 38px stepper sat
   beside a 46px input-style dropdown, and three narrow steppers were spread across three equal
   340px tracks. Measured, not eyeballed: the dropdown is 12px padding + 1.6px border each side
   around 16.8px text = 46.4px, so a 44px stepper interior plus its 1px borders lands on the same
   line. The triple row packs to content width; a slider cell needs a floor or it collapses. */
.operator-api-form .stepper button,.operator-api-form .stepper input[type="number"]{height:44px;}
#operator-api-v2-program-params{display:grid;gap:14px;}
.operator-api-range{display:flex;align-items:center;gap:10px;height:46px;}
.operator-api-range input[type="range"]{flex:1;min-width:140px;margin:0;accent-color:var(--primary);cursor:pointer;}
.operator-api-range output{font-family:var(--font-display);font-weight:800;font-size:0.95rem;color:var(--text-color);min-width:3.5ch;text-align:right;}
.operator-api-form-triple>.operator-api-form-slider{min-width:240px;}
/* The stepper is a grid item: without this it stretches the whole column and strands its buttons
   beside a wide empty field. Inside a two-column pair cell it is a plain block instead, so the same
   intent needs align-self on the flex cell. */
.operator-api-form .stepper{justify-self:start;}
.operator-api-form>.btn{justify-self:start;margin-top:6px;}
/* Two-column field rows (2026-08-15). Same grid recipe as .review-word-grid — one cell per
   label+control, two cells to a row — and it collapses to one column in the SAME 820px block at the
   bottom of this file rather than growing a breakpoint of its own. */
.operator-api-form-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 14px;align-items:start;}
.operator-api-form-pair>div{display:flex;flex-direction:column;min-width:0;}
.operator-api-form-pair>div>.stepper{align-self:flex-start;}
/* Width discipline (2026-08-15, owner refinement R2). A dropdown or a quiet button that spans the
   whole panel reads as the page's primary control, which none of these are. Inside a pair cell a
   control fills its HALF — that is the point of the pair — but a direct child of the form takes its
   natural width and starts at the left edge like every other quiet control in this tab. */
.operator-api-form-pair .dd,.operator-api-form-pair textarea{width:100%;}
/* Phase 133 (owner 2026-09-01): the program-params form grouped its flat label/control/note stack
   into rows. Integer params (course_author's three steppers; dict_build/dict_qc's lone `items` sits
   fine alone in the first cell) share one three-column row, same recipe as the pair row above. A long
   free-text param (`notes`) gets a plain full-width row instead of squeezing beside a neighbour. */
.operator-api-form-triple{display:grid;grid-template-columns:repeat(3,max-content);gap:8px 36px;align-items:start;}
.operator-api-form-triple>div{display:flex;flex-direction:column;min-width:0;}
.operator-api-form-triple>div>.stepper{align-self:flex-start;}
.operator-api-form-wide{display:flex;flex-direction:column;min-width:0;}
/* The per-field description and "Defaults to…" note now live behind an (i) icon beside the label
   (reviewChip()'s own keyboard-reachable idiom) instead of a permanent .sa-sub line under the control. */
.operator-api-form label>.fa-circle-info{margin-left:6px;color:var(--text-muted);cursor:help;}
/* dropdown.js inserts the .dd as the select's NEXT SIBLING, so a select authored as a direct child of
   the form yields a .dd that is one too. #operator-api-v2-order-selector-fields carries
   display:contents, which changes layout but not the DOM tree the selectors match on — hence its own
   rule rather than a `>` that would silently miss. */
.operator-api-form>.dd,#operator-api-v2-order-selector-fields>.dd,#operator-api-v2-order-selector-row>.dd{justify-self:start;width:auto;min-width:240px;max-width:100%;}
.operator-api-form>.btn-ghost,#operator-api-edit-fields>.btn-ghost{justify-self:start;}
/* The catalogue-driven selector fields are appended into their own box so the panel can replace them
   wholesale; display:contents keeps every label/control a direct grid item of the form above it. */
#operator-api-v2-order-selector-fields{display:contents;}
/* The credential editor's fields (2026-08-15) hide together until a credential is chosen, so they
   need a wrapper — and display:contents keeps every one of them a direct grid item of the form, at
   the form's own gap, exactly as if the wrapper were not there. `.hidden` carries !important
   (variables.css), so hiding still wins over this. */
#operator-api-edit-fields{display:contents;}
.operator-api-form .set-arow{padding:6px 0;}
/* Some of these tables sit inside two-column cards, so they are narrow long before the 768px
   breakpoint where the shared wrapper starts scrolling. Contain the overflow at every width: wide
   content scrolls in its own box, the page never scrolls sideways.
   ⚠️ Scope this to the TAB CONTAINER, never to data-operator-api-v2 — that attribute marks which
   surfaces v2-readiness reveals, and when it moved off the Work pane these rules silently stopped
   matching and both tables spilled out of their cards. Layout must not key on a behaviour marker. */
#operator-api-tabs .users-table-wrapper{overflow-x:auto;-webkit-overflow-scrolling:touch;}
/* Was scoped to the Work pane only (min-width:0 on its .set-group). Every pane in this tab wrapper
   is really the same failure: .set-groups is a ONE-column grid, so all its cards share a single
   track — a min-width:auto item (a .set-group is a normal block, no overflow of its own) forces
   that track, and every sibling riding it, to the width of its widest content. The v2 flags/grants/
   credentials/claims tables carry an explicit min-width (see below and .operator-api-list-section
   .theme-table), and that min-width was propagating straight through .users-table-wrapper's
   overflow-x:auto — the wrapper only contains overflow visually, it does not stop the browser's
   intrinsic-size calculation from using its child's min-width when sizing the grid track. min-width:0
   here is what lets the wrapper's own scrollbar actually take over instead of the whole .set-groups
   grid (and therefore the page) growing past the viewport. Harmless at desktop widths: the grid
   track already has more room than any table needs, so this never competes with content there. */
#operator-api-tabs .set-group{min-width:0;}
/* 183: the 520px floor went with the 760px one. Work orders carries four compact columns now, which
   fit well under it, so the only thing this could still do was force a sideways scroll on a phone. */
/* #48 address allowlist: one entry per line, so a monospace box that does not wrap an IPv6 address
   into something that looks like two entries. */
.operator-api-ips{width:100%;resize:vertical;font:0.8rem/1.5 var(--font-mono,monospace);}
/* The proxy-masking warning is live state about this deployment, not a notification, so it lives in
   the card rather than in a toast (AGENTS.md §1). */
.operator-api-ips-warn{margin:0 0 4px;padding:10px 12px;border-radius:10px;border:1px solid color-mix(in srgb,var(--danger) 40%,var(--card-border));background:color-mix(in srgb,var(--danger) 8%,var(--card-bg));color:var(--text-color);font-size:0.8rem;line-height:1.5;}
/* The re-authentication prompt: one narrow card, and the change it authorises stated inside it. */
/* Solid, not the translucent --card-bg glass: a password prompt reading the page through
   itself looks broken and makes the fields hard to read. */
.operator-api-reauth-what{margin:10px 0 0;padding:10px 12px;border-radius:10px;background:var(--bg-color);border:1px solid var(--card-border);font-size:0.85rem;font-weight:650;color:var(--text-color);overflow-wrap:anywhere;}
.operator-api-reauth-card .operator-api-secret-actions{grid-column:auto;margin-top:14px;}
/* armDestructiveAction() keeps an icon button's glyph and moves the confirmation into a visible
   armed state (see the note above it in admin-operator-api.js). The row actions are .users-dir-btn,
   which already has this; the family revoke is a labelled ghost button and needs its own. */
.btn-ghost.is-arming{background:rgba(232,93,117,0.14);border-color:var(--secondary);color:var(--secondary);}
.operator-api-list-section{margin-top:16px;}
/* Inside a tab pane the .set-groups grid already supplies the gap — the standalone margin would
   double it. The `:first-child` half is the Claims tab (2026-08-15): its list section is the pane's
   FIRST child rather than a sibling following a .set-groups block, so the standalone margin pushed
   that one tab's content down and Claims alone started lower than every other tab. A later sibling —
   the Credentials tab's table under its two cards — still needs the margin, which is what scopes
   this to the first child rather than dropping the rule. */
.nb-tabpane>.set-groups>.operator-api-list-section,.nb-tabpane>.operator-api-list-section:first-child{margin-top:0;}
.operator-api-list-section>.sa-sub{margin:10px 2px 0;}
.operator-api-list-section>#operator-api-load-more{margin-top:10px;}
/* 183: the 760px floor is GONE. The column beside the admin menu is 735px at 1440 and narrower
   below it, so this minimum guaranteed a horizontal scrollbar on five of the six lists in this
   panel — measured 25px over on Credentials, Gates, Grants, Programs and Claims. A list that
   needs 760px to be readable is a list carrying columns that belong in its details dialog. */
.operator-api-list-section .theme-table{min-width:0;}
.operator-api-list-section .theme-table td{vertical-align:middle;}
/* Per-resource gate families: one header row per family, collapsing its own rows (change 4, owner
   directive 2026-08-13: collapsed by default even with something enabled — the count badge below is
   what keeps that state visible). Quiet by design — the switches are the only controls that should
   draw the eye, so the bulk actions stay ghost/icon buttons like everything else in this tab. */
.operator-api-gate-family>th{padding:0;background:var(--surface-2,rgba(124,58,237,0.05));}
/* Change 3: the family's OWN column cells sit beside the zero-padded toggle cell above, so they carry
   their own padding rather than inheriting padding:0. Since 2026-08-15 they hold ONLY that column's
   bulk pair — the column NAMES moved to the table's single <thead> row, so a family header no longer
   repeats "CLAIM COMMIT REVIEW" above every group. */
.operator-api-gate-family>th.operator-api-gate-col-label{padding:9px 12px 9px 4px;font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;color:var(--text-muted);text-align:left;}
/* Phase 133 sub-family divider (dictionary's generator/sequence/mint rows) — same muted-caption look
   as the family header's own column label above, applied to a plain colspan-wide row instead of a
   new control. */
.operator-api-gate-matrix .operator-api-subfamily-row>td,.operator-api-grant-matrix .operator-api-subfamily-row>td{padding:6px 12px 4px;font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;color:var(--text-muted);border-bottom:none;}
/* Column discipline for BOTH matrices (2026-08-15): the four trailing columns are toggle cells, so
   they take a fixed narrow track and the resource key keeps everything left over. Declared on the
   <thead> cells, which is what the auto table layout sizes the columns from. */
.operator-api-gate-matrix thead th:not(:first-child),.operator-api-grant-matrix thead th:not(:first-child){width:92px;}
/* Visual rhythm: a row highlight on hover, in the SAME purple tint .operator-api-gate-toggle:hover
   and the language-refiner row already use — no new colour enters the palette. Family header rows
   own their own background and the refiner row is a continuation of the row above it, so neither
   takes the hover. */
.operator-api-gate-matrix tbody tr:not(.operator-api-gate-family):not(.operator-api-grant-lang-row):hover,.operator-api-grant-matrix tbody tr:not(.operator-api-gate-family):not(.operator-api-grant-lang-row):hover{background:rgba(124,58,237,0.045);}
/* A resource row inside a family is subordinate to it, so its separator is softer than the family
   boundary above — border discipline instead of zebra striping, which would fight the hover. */
.operator-api-gate-matrix tbody tr:not(.operator-api-gate-family)>td,.operator-api-grant-matrix tbody tr:not(.operator-api-gate-family)>td{border-bottom-color:var(--border-soft,rgba(228,222,245,0.6));}
.operator-api-gate-row{display:flex;align-items:center;gap:6px;width:100%;}
.operator-api-gate-toggle{display:flex;align-items:center;gap:10px;flex:1;min-width:0;padding:9px 12px;border:0;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer;}
.operator-api-gate-toggle:hover{background:rgba(124,58,237,0.08);}
.operator-api-gate-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;}
.operator-api-gate-toggle>i{width:12px;font-size:0.75rem;opacity:0.7;}
.operator-api-gate-name{font-weight:600;}
/* Owner round 3 (2026-08-13): "X / Y" only, as a quiet pill — no "of"/"enabled" wording. Muted by
   default, matching the .academy-level-pill "unselected" recipe (card-bg + border + muted text); a
   nonzero count keeps the same soft gold tint every other count badge in this file uses (.set-tier.pro,
   .academy-kind-badge) instead of the loud solid accent text this cell used to carry. */
.operator-api-gate-count{margin-left:auto;flex:none;display:inline-flex;align-items:center;font-size:0.72rem;font-weight:700;padding:2px 9px;border-radius:999px;background:var(--card-bg);border:1px solid var(--card-border);color:var(--text-muted);}
.operator-api-gate-count.is-on{background:rgba(244,162,60,0.16);border-color:transparent;color:#b4770f;}
/* Owner round 3: enable-all/disable-all moved OFF the family header and onto each Claim/Commit/Read
   column label as an icon-only pair (never Review — it has no flags, draft-only invariant v1.0.461),
   scoped to exactly that column's flags (see v2GateFamilyFlags in admin-operator-api.js). Reuses
   .users-dir-btn's colour/hover via iconButton() and only resizes it down for this dense header row. */
/* The family header's per-column cell. It held a label + a bulk pair until 2026-08-15; now that the
   column NAMES live once in the table's own <thead>, only the bulk pair is left, so the row is a
   plain right-aligned holder and the rule that styled that label is deleted rather than orphaned. */
.operator-api-gate-col-row{display:flex;align-items:center;justify-content:flex-end;gap:6px;}
.operator-api-gate-col-bulk{display:flex;gap:4px;flex:none;}
.operator-api-gate-col-bulk-btn{width:20px;height:20px;border-radius:5px;}
.operator-api-gate-col-bulk-btn>i{font-size:0.6rem;}
/* Phase 101 D6 — Content Review. Toolbar/table reuse .users-toolbar/.theme-table verbatim; only the
   batch button's right-alignment and the per-row expansion (preview lines / reject note) are new. */
#review-approve-batch{margin-left:auto;}
/* ⚠️ Selector raised to (0,2,1) in Phase 170, for the same reason as the group divider below: the
   shared .theme-table gained zebra rows and a row hover at (0,2,3), and the expanded draft body an
   operator reads before approving or rejecting is neither striped nor clickable. */
.theme-table tbody tr.review-expand-row>td{background:var(--bg-color);padding:4px 16px 16px;}
.review-preview{display:flex;flex-direction:column;gap:8px;padding:10px 4px;}
.review-preview-title{font-weight:800;font-size:0.85rem;color:var(--text-color);}
.review-preview-line{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);}
.review-preview-label{font-weight:700;font-size:0.85rem;color:var(--text-color);overflow-wrap:anywhere;}
.review-preview-detail{font-size:0.8rem;color:var(--text-muted);overflow-wrap:anywhere;}
.review-reject-note{display:flex;flex-direction:column;gap:6px;padding:8px 4px;max-width:480px;}
.review-reject-note textarea{width:100%;resize:vertical;}
/* Owner feedback (2026-08-12) items 1-4 on the Content Review panel. */
/* Phase 104 WS-B — the origin discriminator, reusing the shared .badge box like every other badge on
   this page. Broker keeps the neutral machine tone, panel the product accent (a human in this very
   interface), worker the informational blue of something that ran unattended. */
.badge-origin-broker{background:rgba(122,117,150,0.1);color:var(--text-muted);border:1.5px solid rgba(122,117,150,0.25);}
.badge-origin-panel{background:rgba(111,95,228,0.1);color:var(--primary);border:1.5px solid rgba(111,95,228,0.25);}
.badge-origin-worker{background:rgba(59,130,246,0.1);color:#1d69d6;border:1.5px solid rgba(59,130,246,0.25);}
.review-actions{display:flex;flex-wrap:nowrap;align-items:center;gap:6px;}
/* 136: two badges in the status cell (Pending + Orphaned). An inner div, never flex on the <td> itself — the same table-cell-sizing reason .review-actions exists. */
.review-status-badges{display:flex;flex-wrap:wrap;align-items:center;gap:6px;}
.review-actions .users-dir-btn:disabled{opacity:0.35;cursor:not-allowed;}
.review-actions .users-dir-btn:disabled:hover{background:var(--bg-color);border-color:var(--card-border);}
/* 144: the chapter divider. A full-width <td> row inside the same tbody rather than a second table,
   so the columns still line up and one scroll container holds the lot. Quiet by construction — it is
   a signpost, not a control, and ui-contracts.md caps the accents on a view at one primary action. */
/* ⚠️ NOT --text-muted, MEASURED: that token is #7A7596, which is 3.78:1 on this row's own tinted
   --bg-color band and 4.36:1 on white — both below the 4.5:1 AA floor for text this size, and a
   chapter heading is the one thing on the row that must be readable at a glance. */
/* No `text-transform:uppercase`: the heading carries the chapter's own TITLE, and shouting a proper
   name back at the operator ("RUTINAS DE CASA") is worse than the weight and the band already are. */
/* ⚠️ Selector raised to (0,2,1) in Phase 170: the shared .theme-table gained zebra rows and a row
   hover at that weight, and a divider that stripes or lights up is not a divider. */
.theme-table tbody tr.review-group-row>td{background:var(--bg-color);font-weight:700;font-size:0.82rem;letter-spacing:0.02em;color:var(--text-color);padding:10px 12px;border-top:1.5px solid var(--card-border);}
/* The one relationship that decides what to act on first. State, not a notification (ui-contracts.md):
   it describes what is true of this row now, which is the same exception the job tracker's line takes. */
/* #5F5A7A rather than --text-muted for the reason above: the token measures 4.36:1 on white, just
   under AA, and these two lines are NEW uses of it. The app-wide token is a separate question and a
   separate pass — changing it here alone would make this table disagree with every other. */
.review-dep-line{margin-top:3px;font-size:0.78rem;color:#5F5A7A;}
/* Measured 4.9:1 on white. .badge-warning's #F4A23C is a CHIP colour — it works on a tinted pill and
   fails badly as plain text, so the amber is darkened here rather than borrowed. */
.review-dep-blocked{color:#8a6d1f;}
/* #45 unit bar: both controls start disabled (components.css has no :disabled for .btn-ghost), and
   the bar is a text line + two text buttons, so it wraps — unlike the icon row above, which mustn't. */
.review-actions .btn-ghost:disabled{opacity:0.4;cursor:not-allowed;}
.review-actions .btn-ghost:disabled:hover{border-color:var(--card-border);color:var(--text-color);background:var(--bg-color);}
.review-unit-bar{flex-wrap:wrap;margin-top:2px;padding-top:8px;border-top:1px dashed var(--card-border);}
.review-word-head input[type="checkbox"]{margin:0 2px 0 0;align-self:center;}
.review-word-block:has(input[data-review-unit]:checked){border-color:var(--primary);}
#review-table-body input[type="checkbox"]:disabled{opacity:0.4;cursor:not-allowed;}
/* 144: this column now carries the row's whole identity — the lesson's title, its id and its type —
   where it used to be half of a pair, so 280px wrapped every lesson title onto three lines. Measured
   in the browser, not guessed: at 280 the longest fixture title broke mid-phrase. */
.review-col-draft{max-width:420px;overflow-wrap:break-word;}
/* ⚠️ .sa-sub is defined ONLY as `.set-arow .sa-sub`, so every sub-line this table has ever rendered
   — the broker's credential label since Phase 104, the old item count — inherited nothing and read at
   full size beside its own heading. Phase 216 made that global (`.theme-table .sa-sub`, beside
   `.set-arow .sa-sub`); what stays here is this table's own darker ink and its 2px lead. */
#review-table .sa-sub{font-size:0.78rem;line-height:1.4;color:#5F5A7A;margin-top:2px;}
.review-word-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
.review-word-block{display:flex;flex-direction:column;gap:8px;padding:10px 12px;border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);}
.review-word-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;}
.review-word-headword{font-weight:800;font-size:0.9rem;color:var(--text-color);overflow-wrap:anywhere;}
.review-word-meaning{font-size:0.82rem;color:var(--text-muted);overflow-wrap:anywhere;}
.review-example-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px;}
.review-example{display:flex;flex-direction:column;gap:1px;padding:6px 8px;border-radius:6px;background:var(--bg-color);}
.review-example-sentence{font-size:0.82rem;color:var(--text-color);overflow-wrap:anywhere;}
.review-example-gloss{font-size:0.76rem;color:var(--text-muted);overflow-wrap:anywhere;}
.review-existing{margin-top:2px;padding-top:6px;border-top:1px dashed var(--card-border);}
.review-existing-label{font-size:0.72rem;font-weight:800;text-transform:uppercase;letter-spacing:0.4px;color:var(--text-muted);margin-bottom:4px;}
.review-existing .review-example{background:rgba(122,117,150,0.08);}
/* Phase 189 § 7 — the Quality panel (preview.quality): one .admin-tool-block card reusing .set-arow
   rows (badges, the (i) tooltip and .review-example already exist for every other preview line), so
   only what is genuinely new needs a rule here. */
.review-quality{margin-top:14px;}
.review-quality-check .sa-lab .fa-circle-info{color:var(--text-muted);font-size:0.85rem;cursor:help;}
/* A structural refusal reads as data, not merely as a fail — the same left-accent idiom
   .academy-lint-item.is-broken already uses, for the same reason. */
.review-quality-check.is-refusal{border-left:3px solid var(--danger);padding-left:9px;margin-left:-2px;}
.review-quality-refusal-flag{color:var(--danger);font-size:0.85rem;cursor:help;}
.review-quality-findings{margin-top:10px;}
.review-quality-finding-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px;}
.review-quality-finding{display:flex;align-items:baseline;gap:8px;font-size:0.8rem;line-height:1.4;color:var(--text-color);}
.review-quality-finding .badge{flex:0 0 auto;}
.review-quality-mistake .review-example-sentence s{color:var(--danger);}
/* Phase 105 WS-C — the diff-preview kind (preview_kind 'lesson_diff': academy.lesson_regenerate,
   .fix_packet, .localization, .checkpoint_freshen). Reuses .review-preview's own wrapper/title/
   provenance line and the existing .badge/.badge-st-resolved/.badge-locked/.badge-st-turn/
   .badge-st-closed palette for its Added/Removed/Changed/Moved chips — no new badge colors invented.
   Two-pane side-by-side layout via CSS grid, collapsing to one column at the same tablet breakpoint
   .review-word-grid already uses, below. */
.diff-lesson{display:flex;flex-direction:column;gap:10px;padding:10px 0;border-top:1px solid var(--card-border);}
.diff-lesson:first-child{border-top:none;padding-top:0;}
.diff-lesson-head{font-weight:800;font-size:0.85rem;color:var(--text-color);overflow-wrap:anywhere;}
.diff-block{display:flex;flex-direction:column;gap:6px;padding:10px 12px;border:1px solid var(--card-border);border-radius:8px;background:var(--card-bg);}
.diff-block-head{display:flex;align-items:center;gap:8px;}
.diff-block-type{font-size:0.76rem;font-weight:700;color:var(--text-muted);text-transform:lowercase;}
/* Phase 121: on a moved block the POSITION is the change, so it reads as data rather than as a
   muted label — an operator approving a reorder needs to see it, not hunt for it. */
.diff-block-move{margin-left:auto;padding:2px 9px;border:1px solid rgba(111,95,228,0.22);border-radius:999px;background:rgba(111,95,228,0.08);color:var(--primary);font-size:0.74rem;font-weight:800;font-variant-numeric:tabular-nums;}
.diff-move-note{margin-bottom:7px;color:var(--text-muted);font-size:0.78rem;font-style:italic;}
.diff-block-body{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;}
.diff-pane{display:flex;flex-direction:column;gap:2px;min-width:0;}
.diff-pane-unified{grid-column:1/-1;}
.diff-pane-empty{font-size:0.78rem;font-style:italic;color:var(--text-muted);padding:6px 8px;}
.diff-line{font-size:0.8rem;line-height:1.45;padding:2px 8px;border-radius:5px;overflow-wrap:anywhere;white-space:pre-wrap;}
.diff-line-replace{color:var(--text-color);}
.diff-word-removed{background:rgba(239,68,68,0.18);color:var(--danger);text-decoration:line-through;text-decoration-color:rgba(239,68,68,0.6);border-radius:3px;padding:0 1px;}
.diff-word-added{background:rgba(16,185,129,0.18);color:var(--success);border-radius:3px;padding:0 1px;font-weight:650;}
/* Owner round 3 (2026-08-13): the per-row review destination is a compact icon, not row text — every
   work row used to carry a two-line "Review: ..." sentence for a fact that is never a lever (draft is
   always on; structural is never a choice), which made these rows visibly taller than every other row
   in the table. The full explanation now lives entirely in the icon's tooltip (admin-operator-api.js
   reviewChip()). Quiet by default (draft, the common case); the rare structurally-deferred resource
   keeps the same soft gold tint as the count badge above, so it reads as the exception it is. */
.operator-api-review-chip{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:999px;background:var(--card-bg);border:1px solid var(--card-border);color:var(--text-muted);font-size:0.68rem;cursor:help;}
.operator-api-review-chip.is-deferred{background:rgba(244,162,60,0.16);border-color:transparent;color:#b4770f;}
.operator-api-review-chip.is-operator{background:rgba(108,117,196,0.14);border-color:transparent;color:#5b63b7;}
/* P106 A11Y-01: #5b63b7 over rgba(108,117,196,0.14) measures ~4.56:1 on the light card surface this
   panel actually renders on (Admin clears hyper-theme on route entry). The codex review's reproduction
   flagged ~2.84:1 against the hyper-theme card surface (below the 3:1 WCAG non-text floor); a direct
   recomputation here (scripts/test_phase106_c2.mjs, group F) instead measures ~3.33:1 against the
   opaque approximation of --card-bg (#0f172a) and ~3.76:1 against the darker body base (#020617) that
   --card-bg's own 0.8 alpha partially reveals — a bare, undocumented pass either way, not a fail; the
   two figures could not be reconciled from the documented tokens, so this corrects the review's number
   rather than repeating it. The underlying finding stands regardless of the exact ratio: there was NO
   dark override, so the pass was accidental (this color was tuned for the light theme, never checked
   against the dark one) rather than designed, and margin was thin either way. #a79cf7 (a lighter tint
   of --primary, which hyper-theme does not itself override) measures ~7.4:1 against the same dark
   surface — a deliberate, comfortable margin instead of a coincidental narrow one. .is-deferred is
   untouched: #b4770f already measures ~4.75:1 there without any override needed. Now also
   focusable (admin-operator-api.js reviewChip(), tabindex="0") — every review chip needs its own
   visible ring, not just the operator one, so this sits outside the is-operator block. */
body.hyper-theme .operator-api-review-chip.is-operator{color:#a79cf7;}
.operator-api-review-chip:focus-visible{outline:3px solid var(--primary-glow);outline-offset:2px;}

/* Grants matrix (2026-08-14) — reuses the gates matrix's .operator-api-gate-family/-row/-toggle/-name/
   -count/-col-label/-col-row/-col-text/-col-bulk/-col-bulk-btn classes verbatim (same file, above),
   so only the parts genuinely new to THIS table are styled here: the per-cell toggle button's on/muted
   state (Read included since Phase 133 — it is the same .users-dir-btn idiom now, no switch of its
   own), and the per-language refiner row/chips. */
.operator-api-grant-matrix .users-dir-btn{width:28px;height:28px;border-radius:6px;}
.operator-api-grant-matrix .users-dir-btn.is-on{background:rgba(244,162,60,0.16);border-color:transparent;color:#b4770f;}
.operator-api-grant-matrix .users-dir-btn:disabled{opacity:0.35;cursor:not-allowed;}
/* Granted-but-ungated (owner brief): the toggle must still read as ON, just visibly muted — opacity
   layers on TOP of .is-on's amber tint rather than replacing it, so "granted" stays legible. Matched
   at the SAME specificity as the plain :disabled rule above (two classes + a pseudo-class each) and
   declared after it, so a muted-but-granted control does not silently fall back to the plain-off
   0.35 — a real bug caught in harness verification (2026-08-14). */
.operator-api-grant-matrix .users-dir-btn.is-gate-muted{opacity:0.55;cursor:not-allowed;}
.operator-api-grant-refine-btn{margin-left:6px;}
.operator-api-grant-matrix td>.sa-sub{margin-right:2px;}
/* ⚠️ Raised to (0,2,1) in Phase 170 — see .review-expand-row. This row is a per-language SUB-HEADER,
   which is also why it is written out of the matrix hover at line 2049 and out of the shared one above. */
.theme-table tbody tr.operator-api-grant-lang-row>td{padding-top:4px;padding-bottom:10px;background:rgba(124,58,237,0.03);}
.operator-api-grant-lang-chips{display:flex;flex-wrap:wrap;gap:6px;}
.operator-api-grant-lang-chip{font-size:0.76rem;font-weight:650;padding:4px 11px;border-radius:999px;cursor:pointer;border:1px solid var(--card-border);background:var(--bg-color);color:var(--text-muted);transition:all 0.15s ease;}
.operator-api-grant-lang-chip:hover{border-color:var(--primary);color:var(--primary);}
.operator-api-grant-lang-chip.on{background:rgba(244,162,60,0.16);border-color:transparent;color:#b4770f;}
@media(prefers-reduced-motion:reduce){.operator-api-grant-lang-chip{transition:none;}}
#operator-api-grants-toolbar{margin-bottom:14px;}

@media (max-width:820px){.vocab-profile-row,.vocab-profile-add{grid-template-columns:1fr;}.vocab-profile-form-actions{justify-content:flex-end;}.set-groups--2col{grid-template-columns:1fr;}.review-word-grid{grid-template-columns:1fr;}.operator-api-form-pair,.operator-api-form-triple{grid-template-columns:minmax(0,1fr);}.diff-block-body{grid-template-columns:1fr;}}

/* ============================================================================================
   Phase 146 — Operator API panel: the tab row, list actions, the shared details dialog.
   ============================================================================================ */

.operator-api-tabrow{display:flex;align-items:center;gap:12px;flex-wrap:wrap;}
.operator-api-tabrow .nb-tabs{flex:1 1 auto;min-width:0;}
/* 218: the authority badge sits at the right end of the status bar (owner, 2026-09-30) — on the tab
   row it wrapped under the tabs on a narrow pane. margin-left:auto pushes it right; its own bottom
   margin belonged to its first life as a block and would make the bar taller than its text. */
.operator-api-state .operator-api-authority{margin:0 0 0 auto;flex:0 0 auto;cursor:help;}

/* Icon-only row actions. ⚠️ An inner div, never display:flex on the <td> itself — that breaks
   table-cell sizing, the same reason .review-actions exists (ui-contracts.md, Phase 136). */
/* 148: nowrap + a floor on the cell. The claims list pushed its second icon onto a new row
   whenever the columns beside it grew, because the action cell was free to wrap and had no
   minimum. Icons are 34px + an 8px gap, so the cell reserves room for three of them. */
/* 148: an opaque reference is 36 characters and was wrapping onto two lines in every row of the
   orders and claims lists, doubling their height. One line, truncated with an ellipsis — the FULL
   value is a click away in the details dialog every row already carries, and it is selectable here
   for a copy because the cell keeps its text. 210: scoped to the Operator API's lists, the only tables with that dialog. */
#tab-operator-api .theme-table td > code{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;}
/* And the reference column takes what it needs rather than being squeezed by the ones beside it. */
.operator-api-orders-table td:first-child,.operator-api-claims-table td:first-child{max-width:19rem;}
.operator-api-row-actions{display:flex;align-items:center;gap:8px;justify-content:flex-start;flex-wrap:nowrap;}
.theme-table td:has(> .operator-api-row-actions){white-space:nowrap;width:1%;min-width:126px;}
.operator-api-row-actions .users-dir-btn.danger{color:var(--secondary);}
.operator-api-row-actions .users-dir-btn.danger:hover{background:rgba(232,93,117,0.14);border-color:var(--secondary);}

/* 148: margin-TOP as well. These sit directly under a card heading and its rule, and with only a
   bottom margin the control hugged the line above it on every list that has one. */
.operator-api-list-toolbar{margin-top:14px;margin-bottom:12px;}
/* Not --text-muted: it measures 4.36:1 on white and 3.78:1 on the tinted band, both under the 4.5:1
   AA floor for normal text (ui-contracts.md, Phase 144). This palette has exactly TWO text tokens, so the readable one is the only correct choice; hierarchy here comes from SIZE. */
.operator-api-list-count{font-size:0.78rem;color:var(--text-color);opacity:0.82;}

/* The (i) that replaced the descriptive blocks. Sized down from its label and given a hit area that
   a pointer can actually land on; `cursor:help` says it is readable rather than clickable. */
.adm-side-label .fa-circle-info,.operator-api-title-info{font-size:0.82em;opacity:0.72;cursor:help;margin-left:4px;}
.adm-side-label .fa-circle-info:hover,.adm-side-label .fa-circle-info:focus-visible,.operator-api-title-info:hover,.operator-api-title-info:focus-visible{opacity:1;outline:none;}
.operator-api-title-info{font-size:0.5em;vertical-align:middle;}

/* The one details dialog every list in this panel opens. */
.operator-api-details-list{display:grid;grid-template-columns:minmax(0,10rem) minmax(0,1fr);gap:8px 16px;margin:14px 0 0;max-height:60vh;overflow-y:auto;}
.operator-api-details-list dt{font-size:0.76rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;color:var(--text-color);opacity:0.85;}
/* pre-wrap because an address allowlist is one entry per line and this dialog is where it is read
   in full; break-word so a long opaque reference cannot widen the dialog past the viewport. */
.operator-api-details-list dd{margin:0;font-size:0.9rem;white-space:pre-wrap;overflow-wrap:anywhere;}

/* ⚠️ MEASURED, NOT EYEBALLED (146). With the full stylesheet set and the real dropdown enhancer, a
   .theme-input and a .dd--input are BOTH 46.4px — they already agree, and the reported mismatch is
   not between those two. What does differ is the stepper: its wrapper measured 45.6px against the
   46.4px of everything beside it, so a Lifetime field sat 0.8px short of the dropdown next to it.
   Matching the neighbour's box exactly rather than nudging a padding, so the two cannot drift apart
   again the next time either one's padding is touched. */
.operator-api-form .stepper{min-height:46.4px;box-sizing:border-box;}

/* ── Phase 179: the phone is You's pattern (the approved canvas, D5 a) ──────────────────────────────────────
   The bare /admin is the MENU and a tab is its own screen with a way back (admin.js sets .is-root from the
   URL). The header rides on the menu only, so a screen starts with its own title. The rows are You's phone
   row cards: a tile, the name and its second line, then the count and the chevron. */
@media (max-width:1024px){
    .admin-workspace{display:block;}
    .admin-workspace.is-root .tab-content-area,.admin-workspace:not(.is-root) .admin-index,.admin-lhero:not(.is-root){display:none;}
    .admin-workspace:not(.is-root) .admin-back{display:inline-flex;align-items:center;gap:8px;min-height:40px;margin:0 0 6px;padding:0 2px;border:0;background:none;color:var(--primary-hover);font:inherit;font-size:0.92rem;font-weight:800;cursor:pointer;}
    .admin-page .admin-index{position:static;gap:0;padding:4px 0;}
    .admin-page .admin-index .you-nav-kicker{display:none;}
    .admin-page .admin-index .you-nav-row{display:grid;grid-template-columns:36px minmax(0,1fr) auto auto;column-gap:12px;row-gap:1px;align-items:center;min-height:62px;padding:8px 12px;border-radius:0;font-size:0.95rem;font-weight:800;text-align:left;white-space:normal;}
    .admin-page .admin-index .you-nav-row+.you-nav-row{border-top:1px solid rgba(79,62,143,0.1);}
    .admin-page .admin-index .you-nav-row>.you-nav-glyph{grid-row:1 / span 2;width:36px;height:36px;border-radius:11px;background:rgba(111,95,228,0.1);color:var(--primary-hover);font-size:1rem;line-height:36px;}
    .admin-page .admin-index .you-nav-label{font-size:0.95rem;font-weight:800;}
    .admin-index-sub{display:block;grid-column:2;grid-row:2;overflow:hidden;color:var(--text-secondary);font-size:0.8rem;font-weight:600;text-overflow:ellipsis;white-space:nowrap;}
    .admin-page .admin-index .you-nav-row>.admin-nav-badge{grid-column:3;grid-row:1 / span 2;margin:0;}
    .admin-page .admin-index .you-nav-row>.you-nav-more{grid-column:4;grid-row:1 / span 2;margin:0;font-size:0.8rem;}
    /* A drilled level keeps its way back on the menu screen; the section's name is in the bar above.
       183: it is a row like the others now, so it needs no size of its own here. */
    .admin-page .admin-head-text h1{font-size:1.625rem;}
    /* On the menu screen nothing is open yet, so no row claims to be (You's phone rows carry no current mark). */
    .admin-workspace.is-root .admin-index .you-nav-row.is-current{background:none;box-shadow:none!important;color:var(--text-color);}
    .admin-workspace.is-root .admin-index .you-nav-row.is-current>.you-nav-glyph{color:var(--primary-hover);}
    .admin-figures{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;}
    .admin-figure{padding:8px 12px;}
    .admin-figure-value{font-size:1.375rem;}
    .admin-figure-label{white-space:normal;}
}
/* "Admin" is a <p> (every tab owns the <h1>), so responsive.css's forced phone p (1rem !important) reached
   it and drew the header's name at body size. The approved canvas draws it at 28-30px. */
@media (max-width:768px){
    .admin-lhero .lhero-title{font-size:1.75rem !important;}
    /* A head's own button (More, How this works) goes under the words instead of squeezing them into a column. */
    .admin-page .admin-head-text{flex-basis:100%;}
    /* The reorder arrows are the phone's way to move a chapter or a block: a finger needs more than 22x15. */
    .academy-row-arrows .academy-tool,.academy-block-arrows .academy-tool,.academy-chap-arrows .academy-tool{width:32px;height:24px;font-size:0.72rem;}
    /* A long generator name wraps instead of pushing its chevron past the card's edge ("Paradigm audit (…)"). */
    .vocab-gen-title{flex-shrink:1;min-width:0;}
    /* A signup row fits a phone: the name's role tag drops under the name (a date wrapped word by word read worse). */
    .dash-rows td.dash-nm{white-space:normal;}
}
/* Phase 179f (owner: use the width): settings cards share two balanced COLUMNS once the pane is wide enough. A column
   flow, not a grid, so a short card never leaves a hole beside a tall one; a card never splits; a wide one spans. */
@media (min-width:1280px){
    /* ⚠ A GRID, NOT A COLUMN FLOW, SINCE PHASE 183 — and this REVERSES a Phase 179f decision, so
       the reasoning matters. 179f chose `column-count` "so a short card never leaves a hole beside a
       tall one": the flow balances by height and every card keeps its own. What a flow cannot do is
       END FLUSH, because a card will not split — which is exactly what the owner reported: "the
       panels should fill the column. Academy › Settings leaves empty space at the bottom of its left
       column." Measured at 1440 before the change: Academy short by 46px, Support by 218px, Models by
       217px. A two-column grid ends flush by construction — each pair of cards shares a row and
       stretches to the taller of the two — and the whitespace 179f was avoiding moves INSIDE the
       shorter card as padding, which is what "fill the column" means. */
    .set-groups.set-groups--flow{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;align-items:stretch;}
    .set-groups--flow>.set-group{margin:0;}
    /* A LONE CARD TAKES THE ROW. A grid fills row by row, so an ODD number of cards leaves the last one
       beside an empty cell - measured 661px of hole on Support - which is the same complaint one row
       lower down. The card that would sit alone spans instead: either it is genuinely last, or the only
       thing after it is a card that already spans (Support ends on its wide "Canned replies"). */
    .set-groups--flow>.set-group:last-child:nth-child(odd){grid-column:1 / -1;}
    #general-settings-form>.set-groups>.set-group:last-child:nth-child(odd){grid-column:1 / -1;}
    /* System settings: its cards and the Turnstile cards (rendered into #adm-turnstile-root) are ONE flow; the Save
       bar spans under them. display:contents lets the two groups' cards balance together. */
    #general-settings-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;align-items:stretch;}
    #general-settings-form>.set-groups,#general-settings-form>#adm-turnstile-root,#adm-turnstile-root>.set-groups{display:contents;}
    #general-settings-form .set-group{margin:0;}
    #general-settings-form>.settings-actions{grid-column:1 / -1;}
}

/* ── Phase 182 — the container, button and list sweep (the owner, 2026-09-20) ──────────────────────────────
   "Everything inside a container … all the lists follow the same style." A list of rows is ONE panel with
   hairline rows, not a column of cards each carrying its own frame; a toolbar, a stage, a status line or a
   footer sits in a panel of its own. The material is the KIT's card read from the same three tokens as
   .set-group — never re-declared (ui-contracts.md § Phase 171).
   ⚠️ EVERY SELECTOR HERE IS SCOPED TO `.admin-page`, and that is not tidiness: admin.css is linked on every
   signed-in page (index.php), and You's Privacy-and-data list uses the SAME class names (you.js). Unscoped,
   this block restyled a learner screen — and You's "Show more" sits OUTSIDE its list, so the footer's
   border-top floated on the page background there (the detached review, 2026-09-20). */
.admin-page .sys-log-list,.admin-page .gen-log-list,.admin-page .academy-acd,.admin-page .voices-list{gap:0;border:1px solid var(--surface-edge-shade);border-radius:20px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);overflow:hidden;}
.admin-page .sys-log-row,.admin-page .gen-log-row,.admin-page .academy-chap,.admin-page .voice-lang-panel{border:0;border-radius:0;background:none;box-shadow:none;}
.admin-page .sys-log-row+.sys-log-row,.admin-page .gen-log-row+.gen-log-row,.admin-page .academy-chap+.academy-chap,.admin-page .voice-lang-panel+.voice-lang-panel{border-top:1px solid var(--surface-edge-shade);}
/* ⚠️ A ROW'S OWN STATE HAS TO SURVIVE LOSING ITS BORDER. `.gen-log-row.warn` and `.academy-chap.is-dragging`
   set only the border's COLOUR and STYLE, so with `border:0` a failed generator run painted exactly like a
   good one and a dragged chapter showed only its fade. The keyline and the outline say the same thing
   without a border, and neither moves the row by a pixel. The amber is the one `.gen-log-row.warn` carries. */
.admin-page .gen-log-row.warn{box-shadow:inset 3px 0 rgba(232,152,63,0.85);}
.admin-page .academy-chap.is-dragging{outline:1px dashed var(--text-muted);outline-offset:-1px;}
/* The list's own footer is its last row, not a bar floating under it. */
.admin-page .gen-log-more-row{display:flex;align-items:center;gap:12px;padding:10px 16px;border-top:1px solid var(--surface-edge-shade);}
/* A toolbar, a status line and a stage each stand in a panel. */
.admin-page .vocab-search-row,.admin-page .operator-api-state,.admin-page .dash-flags{padding:10px 14px;border:1px solid var(--surface-edge-shade);border-radius:14px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
.admin-page .operator-api-state{margin:0 0 16px;}
.admin-page .dash-flags{display:flex;flex-wrap:wrap;gap:8px;}
.admin-page .admin-btn-grid{padding:16px 18px;border:1px solid var(--surface-edge-shade);border-radius:20px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
/* The image selector's stage and the keys that drive it, in one panel. */
.admin-page .mie-stage{padding:16px 18px;border:1px solid var(--surface-edge-shade);border-radius:20px;background:var(--surface-base-bg);box-shadow:var(--surface-shadow-base);}
/* ── Phase 183 — ONE tool bar, and a list panel you can see (the owner's quality pass) ────────────────────────
   "The filter panels are too loose — the button is too big and the controls wrap to a second row." Measured
   before the fix, at 1440, in a 735px column: Content Review 3 rows at control heights 32/34/44, People › Users
   3 rows at 32/34, System › Activity log 2 rows at 27/34, Academy's toolbar 2 rows at 34/35 and its search row
   2 rows at 32/44. Six bars, five heights, five separate definitions of the same component.
   ⚠️ THE FIVE OLD NAMES ARE KEPT AS ALIASES IN ONE SELECTOR, NOT RENAMED IN THE MARKUP. `.admin-bar` is the
   name new code uses; renaming the others would touch eight views, four modules and the suites that name them,
   for no change on screen. What IS gone is the five copies of the layout — this is the only place a bar's
   direction, gap, spacing and control height are decided.
   ⚠️ And the PANEL is not re-declared here: the two rules around line 1924 already put every one of these bars
   on the card material, and a second copy of it is what § Phase 182 forbids. */
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters,.academy-toolbar,.academy-search-row){--bar-ctl-h:32px;display:flex;align-items:center;flex-wrap:wrap;gap:7px;}
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters,.academy-toolbar,.academy-search-row) :is(button,select,input,textarea,.dd-btn,.gen-log-seg,.academy-search-wrap){height:var(--bar-ctl-h);min-height:var(--bar-ctl-h);}
/* ⚠ THE COMPACT TYPE STOPS AT A FIELD SOMEONE TYPES INTO, and that asymmetry is Phase 120.3's, not a
   taste: below 16px iOS ZOOMS THE VIEWPORT when a text field takes focus, which `.act-toolbar` already
   recorded as "a functional floor rather than a style choice". The first version of this rule named
   `input` and quietly overrode it at (0,3,0) — a detached review caught it — which would have zoomed the
   page on the Activation and Expenses date fields and Academy's lesson filter. A bar is squared up by
   HEIGHT, never by shrinking the one thing that must not shrink. */
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters,.academy-toolbar,.academy-search-row) :is(button,select,.dd-btn,.gen-log-seg){font-size:0.78rem;}
/* The one primary action in a bar is a bar-sized button, never the page's 44px one — that 12px of extra
   height on Content Review's "Approve selected" is most of what the owner read as "too big". */
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters,.academy-toolbar,.academy-search-row) :is(.btn,.btn-ghost){padding:0 12px;border-radius:8px;box-shadow:none;font-weight:800;}
/* ⚠ 800 IS FOR THE FILLED PRIMARY, NOT FOR A GHOST (the owner, 2026-09-20: Add user "looks
   different ... it should be adjusted"). Measured in the Users bar: the ghost button was the only 800
   among selects at 700 and icon buttons at 400, so the one control shaped like its neutral neighbours
   was also the heaviest text in the row. A filled .btn earns the weight because its fill already says
   it is the action; a ghost has to sit with what it resembles. */
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters,.academy-toolbar,.academy-search-row) .btn-ghost{font-weight:700;}
/* A chip group is one control: the pill keeps the bar's height and its chips fill it, rather than each chip
   becoming bar-height inside a padded pill and standing 6px proud of everything beside it. */
.admin-page :is(.admin-bar,.gen-log-filters) .gen-log-seg{align-items:center;padding:2px;}
.admin-page :is(.admin-bar,.gen-log-filters) .gen-log-chip{height:calc(var(--bar-ctl-h) - 6px);min-height:0;padding:0 11px;}
/* The search field stops being the widest thing in the row: it takes what is left instead of reserving 200px. */
.admin-page :is(.admin-bar,.users-toolbar) .users-tool input[type="search"]{min-width:0;}
/* ⚠️ A CONTROL SHRINKS BEFORE THE ROW WRAPS, and `min-width:0` is the whole mechanism. A flex item's
   floor is its min-content width unless it is told otherwise, so four filter dropdowns whose combined
   natural width came to 11px more than the bar sent the primary action to a second row rather than
   losing a pixel each. `.dd` already carries `min-width:0` and `.dd-label` already ellipsizes, so the
   shrink is graceful; the 92px floor is what stops it becoming a row of unreadable stubs. */
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters) .users-tool{gap:6px;min-width:0;font-size:0.78rem;}
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters) .users-tool>.dd{min-width:92px;}
/* A toolbar dropdown is a filter, not a form field: 9px of side padding instead of the form-field 12,
   which is what takes Content Review from two rows to one at 1440 without shortening a single label. */
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters) .users-tool .dd-btn{padding:0 9px;}
.admin-page :is(.admin-bar,.users-toolbar,.gen-log-filters) .users-dir-btn{width:var(--bar-ctl-h);}
/* An ACTION row is not a filter row, and keeps the page's own button height — but ONE of them. Media's
   three job pages mixed a 44px primary with 31px ghosts beside it (Image sync, Image generation, Voice
   sync, measured): the same raggedness the owner named in the filter bars, in the other kind of bar. */
.admin-page .admin-btn-group{display:flex;align-items:center;flex-wrap:wrap;gap:10px;}
.admin-page .admin-btn-group :is(.btn,.btn-ghost,button){min-height:44px;}
.admin-page :is(.admin-bar,.users-toolbar) .users-tool-sep{height:18px;}
/* A bar reads filters left and its own action right (§ Phase 179f), so the action anchors rather than floating
   wherever the wrap happens to leave it. */
.admin-page :is(.admin-bar,.users-toolbar,.gen-log-filters) >:is(.btn,.btn-ghost,.gen-log-refresh-btn,.users-dir-btn):last-child{margin-left:auto;}
.admin-page :is(.admin-bar,.gen-log-filters) .gen-log-refresh-btn{width:var(--bar-ctl-h);}
/* A LIST PANEL SHOWS ITS FRAME (the owner, 2026-09-20: "it has no padding — list containers should always have
   some padding so we can actually see the panel the list items are inside of"). Phase 182 gave these lists a
   panel and left the rows running edge to edge inside it, so the frame was there and invisible. 6px is the
   value `.users-table-wrapper` already uses, so the two kinds of list agree rather than differing by 6px. */
.admin-page :is(.sys-log-list,.gen-log-list,.academy-acd,.voices-list){padding:6px;}
/* A LIST YOU CAN SCAN, NOT A SPREADSHEET (Phase 183, the owner: "the tables are too wide and show too
   much ... every row already has a details view"). The modifier is what a narrow list looks like:
   tighter cells, a smaller type size, and a cell that may keep its content on one line. It carries no
   minimum width, because the point is to FIT the column rather than to scroll inside it. */
.admin-page .theme-table.is-compact{font-size:0.86rem;}
.admin-page .theme-table.is-compact :is(th,td){padding:9px 10px;}
.admin-page .theme-table.is-compact :is(th,td).is-tight{white-space:nowrap;width:1%;}
/* ⚠ THE GATE/GRANT FAMILY HEADER KEEPS ITS FULL-BLEED CELL. The compact rule above is (0,3,2) and
   outranks `.operator-api-gate-family>th{padding:0}` (0,1,1), so without these two the family toggle
   stops filling its row — a 10px inset all round, a smaller target, and the bulk cells shifted. */
.admin-page .theme-table.is-compact .operator-api-gate-family>th{padding:0;}
.admin-page .theme-table.is-compact .operator-api-gate-family>th.operator-api-gate-col-label{padding:9px 10px 9px 4px;}
.admin-page .theme-table.is-compact td.is-name{max-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* ── Phase 184 — ONE fold-out panel (the owner, 2026-09-20) ──────────────────────────
   "Collapsed by default, a chevron on the right of the title bar, and clicking the chevron or anywhere
   on that title row expands it with a graceful slide down." One component; the Operator API's four
   forms and Voice settings' languages adopt it, and `.academy-chap` is the copy it was lifted from.
   ⚠ THE SLIDE IS A GRID TRACK, NOT A HEIGHT. A panel's height is unknown until it is open, so
   animating `height` means measuring it and re-measuring whenever its contents change; `0fr → 1fr`
   interpolates to the content's own height with nothing to measure. `min-height:0` on the inner is
   what lets a grid item shrink below its content — without it the track collapses and nothing moves.
   ⚠ AND THE TRACKS ARE WRITTEN `minmax(0,…)`: a bare `0fr` floors at the item's min-content size,
   which still counts its padding. See the rules themselves. */
.admin-page [data-collapsible]>.set-group-head{width:100%;justify-content:flex-start;border:0;background:none;font:inherit;font-family:var(--font-display);font-weight:800;font-size:1.06rem;color:var(--text-color);text-align:left;cursor:pointer;}
.admin-page [data-collapsible]>.set-group-head:focus-visible{outline:3px solid var(--primary-glow);outline-offset:2px;border-radius:10px;}
.admin-page .set-group-caret{margin-left:auto;color:var(--text-muted);font-size:0.82rem;transition:transform 0.28s ease;}
/* ⚠ ON THE HEAD ATTRIBUTE, not on `.set-group-head`. A detached review found the Voice panels'
   chevron never turning: their head is `button.voice-lang-header`, which carries
   `data-collapsible-head` but not the class, so a rule keyed to the class reached only the Operator
   API's heads. The attribute is what every adopter is guaranteed to spell. */
.admin-page [data-collapsible]:not(.is-collapsed)>[data-collapsible-head] .set-group-caret{transform:rotate(180deg);}
/* ⚠ `minmax(0,…)` AND NOT A BARE `0fr` — A BARE ONE DOES NOT MEAN ZERO (185q, the owner: part of a
   collapsed Voice row's content was still visible). `0fr` is `minmax(auto, 0fr)`, and that `auto`
   floor is the item's min-content contribution. `min-height:0` below takes the item's CONTENT out of
   that figure and leaves its padding in — so `.voice-lang-body`'s `padding-bottom:16px` held the
   track open at 16px, and `overflow:hidden` clips at the PADDING box, which is why a strip of the
   first label showed through. MEASURED closed: panel 70px, head 54, track computing `16px`.
   ⚠ BOTH HALVES ARE NEEDED. The explicit 0 minimum is what lets the TRACK reach zero; `min-height:0`
   is what lets the ITEM shrink inside it (a grid item's automatic minimum is its min-content size, so
   without it the content refuses to shrink and overflows a zero track instead). Neither replaces the
   other, and the contract as written — "one child with overflow:hidden;min-height:0" — was only half
   of it: it failed silently for the first adopter that gave that child padding. */
.admin-page .set-group-body{display:grid;grid-template-rows:minmax(0,1fr);transition:grid-template-rows 0.28s ease;}
.admin-page [data-collapsible].is-collapsed>.set-group-body{grid-template-rows:minmax(0,0fr);}
.admin-page .set-group-body-inner{overflow:hidden;min-height:0;}
/* ⚠ AND ITS BLOCK PADDING GOES TO ZERO, or the component only LOOKS closed. MEASURED with the two
   rules above alone: the track is 0px, and the inner is still a 16px border box hanging BELOW it
   (`overflow:hidden` clips at the padding edge, so the padding itself is not clipped by the inner).
   What hid those 16px was `.voice-lang-panel{overflow:hidden}` — the ADOPTER's own clip, which is
   not part of this contract: the next adopter without one would show a strip under its card.
   ⚠ IT IS NOT TRANSITIONED, AND MUST NOT BE. Only the box's bottom edge moves; the content does not,
   because this is padding-BOTTOM. Animating it would fight the track for the same 280ms.
   (`.academy-chap-body-inner` solves the same problem the other way round — it adds vertical padding
   only when open. Two implementations of one fold; this one is the shared component.) */
.admin-page [data-collapsible].is-collapsed>.set-group-body>.set-group-body-inner{padding-block:0;}
/* A collapsed card's head carries the card's whole padding, so the closed panel is a title bar and
   not a title bar with a gap under it. */
.admin-page [data-collapsible].is-collapsed>.set-group-head{padding-bottom:0;}
@media (prefers-reduced-motion:reduce){
    .admin-page .set-group-body,.admin-page .set-group-caret{transition:none;}
}

/* ── The menu's own media blocks (Phase 181). They sit here, with the file's others, because a plain rule after
   a @media block is what the stylesheet lint reads as a mis-ordered file. ─────────────────────────────────── */
@media (prefers-reduced-motion:reduce){
    .admin-menu-viewport.is-sliding,.admin-menu-viewport.is-sliding .admin-menu-level{transition:none;}
}
/* You narrows its column in the band where it first appears; the admin's menu is the same column. */
@media (min-width:1025px) and (max-width:1163px){
    .admin-workspace{grid-template-columns:232px minmax(0,1fr);gap:16px;}
}

/* ── Phase 184 — the owner's second pass (2026-09-20) ─────────────────────────────────────────────
   Models and Voices first (the layout the rebuilt admin.js markup needs), then six smaller asks that
   each turned out to be a SHARED rule rather than a page's own: a list's end caps, a dialog's word
   wrapping, the drilled menu's divider, the filter bar's floating search and active-filter look, and
   one refresh button that had been given a size of its own. */

/* ⚠ THIS SUPERSEDES "THE MODELS PAGE READS ACROSS, NOT DOWN" — that rule is deleted above, not
   overridden. A row is a vertical stack of three parts now (the spend guards, the provider line, the
   model line) and Phase 183's two-column reading moved INTO the last two of them, which is the only
   place it can line a LABEL up with a label rather than a column with a column. */
.admin-page #models-rows .model-inputs{display:flex;flex-direction:column;gap:12px;}
/* ONE two-up grid, used twice per row, so the provider controls and the model controls fall into the
   same two tracks and every label starts at its own column's left edge. */
.admin-page #models-rows .model-duo{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 16px;align-items:start;}
.admin-page #models-rows .model-duo:empty{display:none;}
/* ⚠ A LABEL ABOVE ITS CONTROL IS WHAT ALIGNS THE FOUR, and a fixed label gutter is not: "Provider"
   and "Fallback provider" are not the same width and never will be, so a gutter can only align one
   pair at a time. flex-basis:100% breaks the line after the label; the control and its icon button
   keep the next line to themselves. */
.admin-page #models-rows :is(.model-provider-lab,.model-slot-lab){flex:1 1 100%;width:auto;margin:0 0 2px;font-size:0.74rem;font-weight:700;letter-spacing:0.02em;color:var(--text-secondary);}
.admin-page #models-rows :is(.model-provider-wrap,.model-slot,.model-input-wrap){flex-wrap:wrap;gap:4px 8px;align-items:center;}
.admin-page #models-rows .model-provider-wrap .dd{flex:1 1 auto;min-width:190px;}
/* The spend guards LEAD the row now (the owner), so they carry no top margin and read as their own
   strip rather than as something tacked under the controls. */
.admin-page #models-rows .model-guards{margin-top:0;gap:10px 14px;}
.admin-page #models-rows .model-guard-cap input{width:150px;}
/* The tab set is the shared .panel-stack, minus the 14px it gives itself when it follows a heading —
   here it IS the page's content, and #models-rows already sits in the tab's own flow. */
.admin-page #models-rows>.panel-stack{margin-top:0;}
.admin-page #models-rows .nb-tabpane{min-width:0;}

/* VOICE SETTINGS — the four voices are a 2x2, and Preview shares a line with Nova's voice. Four
   full-width controls stacked under two headings read as a wall, and three of them were told apart
   only by their position under one plural heading (the owner). */
.admin-page :is(.voice-grid,.voice-duo){display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 14px;}
.admin-page :is(.voice-grid-cell,.voice-duo-cell){display:flex;flex-direction:column;gap:4px;min-width:0;}
.admin-page .voice-slot-lab{font-size:0.74rem;font-weight:700;letter-spacing:0.02em;color:var(--text-secondary);}
.admin-page .voice-grid-cell>:is(.dd,input){width:100%;min-width:0;}
.admin-page .voice-duo-cell .voice-field-label{margin-top:0;}
.admin-page .voice-duo-cell .voice-nova+.dd{flex:1 1 auto;min-width:0;}
/* The per-language model field is the Models page's picker; the locked default it replaces reads as a
   chip in the same vocabulary, dashed and muted because it is not this page's to edit. */
.admin-page .voice-model-field{flex:1 1 240px;min-width:0;}
.admin-page .voice-model-default{display:inline-flex;align-items:center;gap:8px;max-width:100%;padding:5px 11px;border:1px dashed var(--card-border);border-radius:999px;background:var(--bg-color);color:var(--text-muted);font-family:ui-monospace,monospace;font-size:0.8rem;font-weight:700;overflow-wrap:anywhere;}
.admin-page .voice-model-default>i{flex:0 0 auto;font-size:0.76em;}
/* An open catalogue takes the whole line instead of the third of it the field sits in. */
.admin-page .voice-model-row:has(.model-picker.is-open) .voice-model-field{flex-basis:100%;}

/* ── item 1: the course index's end caps ──────────────────────────────────────────────────────────
   Measured at 1440: the panel is radius 20 with 6px of padding, and its chapter rows are radius 0
   (the course-index exception in ui-contracts.md). A square row 6px inside a 20px curve leaves a
   wedge of panel at each corner — the owner's "sharp edges look strange against the round corners".
   10px of padding with 10px caps is CONCENTRIC: outer radius minus padding, so the two curves stay
   parallel instead of one cutting across the other. */
.admin-page .academy-acd{padding:10px;}
.admin-page .academy-acd>:first-child{border-top-left-radius:10px;border-top-right-radius:10px;}
.admin-page .academy-acd>:last-child{border-bottom-left-radius:10px;border-bottom-right-radius:10px;}
/* The row's own head and body have to round with it, or the row clips square inside its own cap. */
.admin-page .academy-acd>:first-child>:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit;}
.admin-page .academy-acd>:last-child>:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;}

/* ── item 2: the details dialog's labels ──────────────────────────────────────────────────────────
   The term column is capped at 10rem and its dd already breaks anywhere; the dt did not, so a single
   long unbroken label (a grant id, a header name) ran straight out of its track and UNDER the value
   beside it. That is the overlapping text in the owner's first screenshot. */
.operator-api-details-list dt{overflow-wrap:anywhere;}

/* ── item 4: the drilled menu's divider ───────────────────────────────────────────────────────────
   "All sections" leaves the menu; everything under it moves within one section. They were 2px apart
   with nothing between them, so the way OUT read as the first item of the list it sits above. */
/* ⚠ THE LINE IS DRAWN ON THE GROUP, NOT ON THE BUTTON, for two reasons. The row is You's
   `.you-nav-row` — § Phase 181: a second copy of that anatomy in admin.css is how this menu drifted
   into Arial — so its padding and its 9px hover radius are not ours to re-cut, and a border under a
   rounded button reads as an underline rather than a divider. And profile.css loads AFTER admin.css,
   so `.you-nav-row`'s shorthands beat a bare `.admin-menu-back` at equal specificity (measured: the
   first version of this rule computed `border-bottom: 0px none`). Scoped to `.admin-page` at (0,2,0)
   to clear that, and applied to the group so the line spans the menu's full width. */
.admin-page .admin-menu-back{margin-bottom:0;}
.admin-page .admin-menu-back+.you-nav-group{margin-top:11px;padding-top:11px;border-top:1px solid var(--card-border);}

/* ── items 5 + 6: the filter bar knows what it is carrying ────────────────────────────────────────
   A set filter is tinted in the panel's own accent rather than given a badge or a count: the bar is
   read at a glance, and the question it has to answer is "is anything on?", not "how much". */
.admin-page .users-tool.is-set{color:var(--primary);}
.admin-page .users-tool.is-set>:is(select,input,.dd-btn){border-color:var(--primary);background:rgba(111,95,228,0.09);color:var(--primary-hover);}
.admin-page .bar-search.is-set>.bar-search-btn{border-color:var(--primary);background:rgba(111,95,228,0.12);}
/* The button stays lit while the panel is open, so the control the operator is typing into is the
   one that still looks pressed. */
.admin-page .bar-search.is-open>.bar-search-btn{border-color:var(--primary);background:rgba(111,95,228,0.18);color:var(--primary-hover);}
.bar-search{position:relative;display:inline-flex;}
/* ⚠ RIGHT-ANCHORED, and that is not cosmetic: this control sits at the END of the bar, so a panel
   growing rightwards from it would leave the viewport. Growing leftwards keeps it inside the panel
   whatever the bar's width. */
/* ⚠ THE [hidden] RESTORE, and it is not optional - see the standing rule in ui-contracts.md.
   `[hidden]{display:none}` is a USER-AGENT rule, so the author `display:inline-flex` below beats it on
   ORIGIN alone: no specificity contest, nothing in the markup to hint at it. Without this line the
   panel is painted permanently while every line of JS correctly believes it is hidden - which is what
   the owner saw, a search bar that would not go away. Written beside the display it undoes. */
.bar-search-pop[hidden]{display:none;}
.bar-search-pop{position:absolute;top:calc(100% + 7px);right:0;z-index:40;display:inline-flex;align-items:center;gap:8px;padding:7px 9px;border:1px solid var(--card-border);border-radius:12px;background:var(--card-bg);box-shadow:var(--surface-shadow-raised);}
.bar-search-pop>i{color:var(--primary);font-size:0.8rem;}
.bar-search-pop input[type="search"]{width:clamp(180px,26vw,300px);min-width:0;padding:7px 10px;border:1px solid var(--card-border);border-radius:8px;background:var(--bg-color);color:var(--text-color);}
.bar-search-clear{display:inline-flex;align-items:center;justify-content:center;width:26px;border:0;border-radius:7px;background:none;color:var(--text-muted);cursor:pointer;}
.bar-search-clear:hover{background:rgba(111,95,228,0.12);color:var(--primary);}

/* ── item 7: a one-line note in a bar is not a paragraph ──────────────────────────────────────────
   ⚠ MEASURED: .vocab-hint carries `margin:0 0 14px` for its normal use UNDER a control. A flex row
   with align-items:center centres the MARGIN box, so that 14px lifted the text 7px above everything
   beside it — the Mailbox's "All caught up." sitting high, which the owner read as top-aligned. */
.admin-page :is(.admin-bar,.users-toolbar,.act-toolbar,.gen-log-filters) :is(.vocab-hint,.set-note-inline,p){margin-bottom:0;}

/* ── item 9: one refresh button, not two ──────────────────────────────────────────────────────────
   This was a .btn-ghost with a padding and a font-size of its own, so it read as a small text button
   among a panel of 34px icon buttons. It is a .users-dir-btn now like every other refresh in the
   panel; the only thing it still needs from here is its place at the end of the heading row. */
.set-group-head>.users-dir-btn{margin-left:auto;flex:0 0 auto;}

@media (max-width:900px){
    .admin-page #models-rows .model-duo{grid-template-columns:minmax(0,1fr);}
}
@media (max-width:760px){
    .admin-page :is(.voice-grid,.voice-duo){grid-template-columns:minmax(0,1fr);}
    /* On a phone the floating panel is as wide as the bar allows rather than trailing off its edge. */
    .bar-search-pop input[type="search"]{width:min(62vw,240px);}
}
