/* PuzzleForge — app.css
 * Design tokens: tím-xanh "brainy" palette (Global Constraints — giá trị đúng, không tự đổi). */

/* Display face — Bricolage Grotesque (OFL), self-hosted variable font (700–800), latin subset.
   Headings/brand/stat numbers only; body copy stays on the system stack. No CDN — the woff2
   ships with the app so it works on any host with zero external requests. */
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 700 800;
  font-display: swap;
  src: url("../fonts/bricolage-grotesque-latin.woff2") format("woff2");
}

:root {
  --bg: #f5f3fc;
  --panel: #fff;
  --panel2: #f9f7ff;
  --line: #e5def7;
  --ink: #241d40;
  --mut: #7a7199;
  --mut2: #aaa2c6;
  --acc: #6d4df2;
  --acc2: #9078ff;
  --ok: #16a34a;
  --danger: #e5484d;
  --font-display: "Bricolage Grotesque", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* ---------- Type scale (F7) ---------- */
h1, h2, h3 {
  font-family: var(--font-display);
  letter-spacing: -.01em;
  text-wrap: balance;
}
h1 { font-size: 1.9em; font-weight: 800; margin: 0 0 18px; }
h2 { font-weight: 700; }

* { box-sizing: border-box; }

@media (prefers-reduced-motion: reduce) {
  .fmt-card, .proj-card { transition: none !important; }
  .fmt-card:hover, .proj-card:hover { transform: none !important; }
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

a { color: var(--acc); }

/* The UA sheet's `[hidden] { display: none }` loses to ANY author rule that sets display,
   regardless of specificity — author origin simply outranks user-agent origin. Three elements
   shipped visible while flagged hidden because of it: #project-bar (display:flex),
   #create-hint (display:flex, so its "Got it" button did nothing; since replaced by the Create
   guide card), and #btn-wiz-back
   (button.btn is display:inline-block, so "← Back" showed on step 1 of the wizard).
   One !important here is cheaper — and far more reliable — than auditing every future
   `display:` rule against the list of elements JS toggles with .hidden. */
[hidden] { display: none !important; }

/* ---------- Screens ---------- */
.screen { display: none; }
.screen.on { display: block; }

/* ---------- Form controls ---------- */
input, select, textarea, button {
  font: inherit;
  color: var(--ink);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="url"],
select,
textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--ink);
}

input[type="range"] {
  width: 100%;
  accent-color: var(--acc);
}

textarea { resize: vertical; }

button.btn, a.btn {
  display: inline-block;
  text-decoration: none;
  padding: 10px 18px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}
.btn-ico { width: 14px; height: 14px; vertical-align: -2px; margin-right: 5px; flex: none; }

button.btn:hover, a.btn:hover { border-color: var(--acc2); }

button.btn-primary, a.btn-primary {
  background: var(--acc);
  border-color: var(--acc);
  color: #fff;
}

button.btn-primary:hover, a.btn-primary:hover { background: var(--acc2); }

/* Every interactive element, not just .btn: bare buttons like the modal's ✕ (class "up-close",
   no .btn) and plain links were left on the browser's default ring — a blue that belongs to no
   palette in this app and reads as a validation error next to the purple accent. */
button:focus-visible, a:focus-visible, summary:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--acc2);
  outline-offset: 2px;
}

/* Disabled controls were styled exactly like live ones — full-strength text and border, and
   cursor:pointer from .btn. The upgrade modal's three "Available at launch" CTAs are `disabled`
   and looked completely clickable; so did every field in a locked KDP card. */
.btn:disabled, button:disabled, input:disabled, select:disabled, textarea:disabled {
  opacity: .5;
  cursor: not-allowed;
}
button.btn:disabled:hover, a.btn:disabled:hover { border-color: var(--line); }

/* ---------- Toast ---------- */
.toast {
  position: fixed;
  right: 20px;
  bottom: 20px;
  /* A long friendlyError() on a narrow window otherwise grows the box past the left edge;
     cap it to the viewport and let the message wrap instead. (If the toast ever appears cut
     off past the RIGHT edge, that is trackpad pinch-zoom — fixed elements anchor to the
     layout viewport — reset with Cmd+0, not a layout bug.) */
  max-width: calc(100vw - 40px);
  overflow-wrap: anywhere;
  padding: 12px 18px;
  border-radius: 8px;
  background: var(--ink);
  color: #fff;
  box-shadow: 0 4px 20px rgba(36, 29, 64, .25);
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
  transition: opacity .2s ease, transform .2s ease;
  z-index: 1000;
}
.toast.on { opacity: 1; transform: translateY(0); }
.toast.ok { background: var(--ok); }
.toast.err { background: var(--danger); }

/* ---------- Sales screen (logged-out teaser — one hero card) ---------- */
#sales { padding: 40px 20px; }
.teaser {
  min-height: calc(100vh - 80px);
  display: flex;
  align-items: center;
  justify-content: center;
}
.teaser-card {
  max-width: 620px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 44px 48px;
  text-align: center;
  box-shadow: 0 12px 40px rgba(36, 29, 64, .08);
}
.teaser-card h1 {
  font-size: 1.7em;
  line-height: 1.25;
  margin: 0 0 14px;
  color: var(--ink);
}
.teaser-card h1 em { color: var(--acc); font-style: normal; }
.teaser-card > p { color: var(--mut); margin: 0 0 18px; }
.teaser-mut { font-size: .85em; color: var(--mut2); margin-top: 6px; }

/* ---------- Login screen ---------- */
#login {
  max-width: 420px;
  margin: 60px auto;
  padding: 32px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
}
#login h2 { margin-top: 0; }
#login .panel { margin-bottom: 24px; }
#login label { display: block; margin: 12px 0 4px; color: var(--mut); font-size: .9em; }

/* ---------- App shell ---------- */
/* NOTE: #app is an ID selector (specificity 100) which would otherwise beat
   the class selector `.screen { display: none }` (specificity 10) and stay
   visible even when not the active screen. Keep `display` out of the bare
   `#app` rule — only `#app.on` (matches .screen.on's specificity) sets it. */
/* height (not min-height) + minmax(0,1fr): with `min-height: 100vh` the grid grew past the
   viewport whenever a view was tall, the `1fr` row resolved to the CONTENT height, and
   `#app > main { overflow: auto }` therefore never had anything to scroll — the document
   scrolled instead and took the sidebar and topbar off the top of the screen with it.
   minmax(0,1fr) is required: a bare `1fr` is min-content-floored and would keep growing. */
#app {
  grid-template-columns: 240px 1fr;
  grid-template-rows: 56px minmax(0, 1fr);
  grid-template-areas:
    "sidebar topbar"
    "sidebar main";
  height: 100vh;
  height: 100dvh; /* dvh excludes mobile browser chrome; the vh line above is the fallback */
  overflow: hidden;
}
#app.on { display: grid; }

#app > .topbar {
  grid-area: topbar;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
  height: 56px;
  padding: 0 20px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

#app > .sidebar {
  grid-area: sidebar;
  background: var(--panel2);
  border-right: 1px solid var(--line);
  padding: 16px 0;
  /* #app is now viewport-height with overflow:hidden, so the nav column needs its own scroller
     for short screens — otherwise Settings/Bulk fall off the bottom with no way to reach them. */
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 20px 14px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}
.brand-mark { width: 22px; height: 22px; color: var(--acc); flex: none; }
.brand-login { border-bottom: none; justify-content: center; padding: 0 0 18px; margin: 0; }
.brand-word {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.05em;
  letter-spacing: -.01em;
  color: var(--ink);
}
.nav-ico {
  width: 17px;
  height: 17px;
  flex: none;
  opacity: .55;
}
#app > .sidebar [data-view].on .nav-ico { opacity: 1; }

#app > .sidebar .grp {
  padding: 8px 20px;
  color: var(--mut2);
  font-size: .75em;
  letter-spacing: .05em;
  text-transform: uppercase;
  margin-top: 16px;
}

#app > .sidebar [data-view] {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 20px;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}
#app > .sidebar [data-view]:hover { background: var(--panel); }
#app > .sidebar [data-view].on { background: var(--panel); color: var(--acc); font-weight: 600; }

#app > .sidebar .upsell { opacity: .6; text-decoration: none; display: flex; align-items: center; gap: 8px; padding: 6px 20px; }
#app > .sidebar .upsell.owned { opacity: 1; color: var(--acc); }
#app > .sidebar .upsell .check { display: none; }
#app > .sidebar .upsell.owned .lock { display: none; }
#app > .sidebar .upsell.owned .check { display: inline; }
.lock-ico { width: 13px; height: 13px; flex: none; }

#app > main {
  grid-area: main;
  padding: 24px;
  overflow: auto;
}

/* One content column for the whole app. Before this every view invented its own width — the
   right edge landed at 1084 (Settings/KDP), 1144 (Dashboard) or 1488 (Projects/Guides/Exports)
   depending on where you clicked, so switching views visibly shifted the layout under you.
   Form-only views get a narrower column, but the cap lives on the VIEW, not on the panels inside
   it, so the heading and intro paragraph end where the cards do instead of running past them. */
.view { display: none; max-width: 1180px; }
.view.on { display: block; }
#view-settings, #view-kdp, #view-bulk { max-width: 860px; }

#quota-chip {
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--panel2);
  border: 1px solid var(--line);
  color: var(--mut);
  font-size: .85em;
}

/* ---------- Dashboard ---------- */
/* auto-fit + 1fr so the cards share the row instead of capping at 220px and leaving two thirds
   of a 1200px dashboard empty. max-width keeps them from ballooning on an ultrawide monitor. */
.stat-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin: 8px 0 22px;
}
.stat-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 18px 20px;
  box-shadow: 0 2px 10px rgba(36, 29, 64, .05);
}
.stat-num { font-family: var(--font-display); font-size: 2.1em; font-weight: 800; line-height: 1.1; font-variant-numeric: tabular-nums; }
.stat-label { color: var(--mut); font-size: .85em; margin-top: 4px; }
.dash-cta { margin: 0 0 28px; }
.dash-steps h2 { font-size: 1.05em; margin: 0 0 10px; }
.dash-steps ol { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; max-width: 560px; }
.dash-steps li { color: var(--mut); }
.dash-steps li strong { color: var(--ink); }

/* ---------- Create view ---------- */
/* Same ID-vs-class specificity pitfall as #app above: keep `display` out of
   the bare #view-create rule so `.view { display: none }` still wins when
   this view is not the active one. */
/* Create is a studio, not a document: the banners stack at the top and the two columns take
   whatever height is left, so the 9:16 frame is always fully visible and the (much taller)
   control column scrolls inside itself instead of pushing the preview off the bottom. */
/* height (not min-height) is load-bearing: it is what makes #create-cols' row a DEFINITE height,
   which is what `max-height: 100%` on the frame resolves against. Swap it for min-height and the
   row goes auto again and the preview grows back to its natural 1493px. The banners above are
   allowed to push the studio past the viewport — main scrolls — because #create-cols carries a
   min-height floor rather than shrinking to whatever is left over. */
#view-create.on { display: flex; flex-direction: column; height: 100%; gap: 14px; }
#create-cols {
  display: grid;
  /* The frame column is `auto`, not `1fr`: a 9:16 frame whose height is bounded by the row derives
     its own width (~325px on a 766px-tall window), so a 1fr track handed it 840px and left a 515px
     void to its right. Sized to content, the leftover goes to the controls instead — which is the
     column that actually has something to do with it (see .wiz-step's own grid below). */
  grid-template-columns: minmax(320px, 1fr) auto;
  /* The single row must be pinned to the container's height. Left implicit it is `auto`, i.e.
     sized by its tallest item — so the frame's `max-height: 100%` resolved against the CONTENT
     height it was supposed to be constraining and the preview grew to 1493px tall. */
  grid-template-rows: minmax(0, 1fr);
  gap: 20px;
  flex: 1;
  /* Shrinkable (a flex item refuses to go below its content height without this) but not to
     nothing: below ~420px the controls column stops being usable and the preview is a sliver. */
  min-height: 420px;
}
#pf-controls {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 16px;
  /* Flex column so #pf-actions' `margin-top: auto` can park it on the bottom edge. */
  display: flex;
  flex-direction: column;
  /* The controls are far taller than the preview, so they scroll inside their own column instead
     of stretching the view and pushing the frame past the bottom of the window. */
  overflow-y: auto;
}
#pf-stage {
  /* The frame is bounded by the row it sits in, not by a viewport calculation: the banners above
     it are optional and variable-height, so any `100dvh - <constant>` guess was wrong as soon as
     a project was open. max-height:100% + aspect-ratio makes the browser derive the width, which
     is why width/height are auto. */
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  aspect-ratio: 1080 / 1920;
  /* center is only affordable because #view-create is capped at 860 (see that rule): the leftover
     in this column is ~98px a side, not the ~275 that made centring look like a broken gap when
     the view was 1180 wide. Whatever is left over reads as margin instead of a hole on one side. */
  justify-self: center;
  align-self: start;
}

/* ---------- Create view form controls (A5) ---------- */
.fgroup {
  display: block;
  margin: 0 0 14px;
  color: var(--mut);
  font-size: .85em;
}
.fgroup > input,
.fgroup > select,
.fgroup > textarea {
  display: block;
  margin-top: 4px;
  color: var(--ink);
  font-size: 1em;
}
.fgroup-check {
  display: flex;
  align-items: center;
  gap: 8px;
}
.fgroup-check input { width: auto; }
#pf-controls .btn { margin: 0 8px 14px 0; }
/* That 14px bottom margin is spacing between stacked buttons; inside a flex row aligned to
   flex-end it silently lifts the button 14px above the control it sits beside. */
/* #pf-controls .btn is 1-1-0, so the reset has to carry the ID too — a plain .wl-row .btn (0-2-0)
   loses and the button stays lifted. */
#pf-controls .wl-row .btn { margin: 0; }
.tab-row { display: flex; gap: 16px; margin-top: 6px; flex-wrap: wrap; }
.tab-row label { display: flex; align-items: center; gap: 6px; color: var(--ink); font-size: 1em; }
.tab-row input { width: auto; }
/* B8b OTO2 — numbersearch/scramble tabs: 🔒 shown only when license lacks puzzlepack
   (studio.js toggles .locked; the underlying click is refused too, see studio.js click handler). */
.tab-row label .lock { display: none; }
.tab-row label.locked { opacity: .6; }
.tab-row label.locked .lock { display: inline; }
.word-errors { color: var(--danger); font-size: .82em; margin: -8px 0 14px; }
.word-errors:empty { margin: 0; }
.chip {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--panel2);
  border: 1px solid var(--line);
  color: var(--mut);
  font-size: .85em;
}
/* Every chip in the app starts life empty and is filled by JS (#proj-status, #est-chip,
   #bulk-eta, #kdp-credits). Without this the padding + border still paint, leaving a blank
   grey capsule floating next to Close / Cancel that looks like a failed render. */
.chip:empty { display: none; }

/* ---------- Generic panel (login screen already has its own #login .panel spacing) ---------- */
.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 20px;
}
.panel h2 { margin-top: 0; }
/* :not(.fgroup-check) — this rule is 0-1-1 and used to beat `.fgroup-check { display: flex }`
   (0-1-0), so every checkbox inside a .panel stacked its tick box ABOVE its own text. Only Bulk
   showed it: Create's identical markup lives in #pf-controls, which is not a .panel. */
.panel label:not(.fgroup-check) { display: block; margin: 12px 0 4px; color: var(--mut); font-size: .9em; }
.panel label.fgroup-check { margin: 12px 0 4px; color: var(--mut); font-size: .9em; }

/* ---------- Empty state ---------- */
.empty {
  padding: 48px 20px;
  text-align: center;
  color: var(--mut);
  background: var(--panel2);
  border: 1px dashed var(--line);
  border-radius: 12px;
}
.empty-ico { width: 40px; height: 40px; color: var(--mut2); }
.empty-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15em;
  color: var(--ink);
  margin: 10px 0 2px;
}
.empty p { margin: 6px 0; }
.empty .btn { margin-top: 10px; }

/* ---------- Tables (My Videos, Admin users) ---------- */
.videos-table,
.admin-users {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
}
.videos-table th, .videos-table td,
.admin-users th, .admin-users td {
  padding: 10px 14px;
  text-align: left;
  border-bottom: 1px solid var(--line);
  font-size: .9em;
}
.videos-table th, .admin-users th {
  background: var(--panel2);
  color: var(--mut2);
  text-transform: uppercase;
  font-size: .75em;
  letter-spacing: .05em;
}
.videos-table tr:last-child td, .admin-users tr:last-child td { border-bottom: none; }
.admin-users tbody tr:hover td { background: var(--panel2); }
.admin-users button.btn-expand {
  width: 28px; height: 28px; padding: 0; line-height: 1;
  border-radius: 6px; border: 1px solid var(--line);
  background: var(--panel); color: var(--mut); cursor: pointer;
}
/* Same disclosure glyph as .guide summary::before, not the raw +/- text admin.js used to swap in
   by hand — one rotating triangle language for every expand/collapse control in the app. */
.admin-users button.btn-expand::before { content: "▸"; display: inline-block; transition: transform .15s ease; }
.admin-users button.btn-expand[aria-expanded="true"]::before { transform: rotate(90deg); }
/* Row actions were bare <button>s in the UA's default chrome — the only two controls in the app
   still wearing it. Same border/hover language as .btn, sized down to the table's type scale. */
.admin-users button.btn-sm {
  padding: 5px 10px;
  font-size: .85em;
  margin-right: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}
.admin-users button.btn-sm:hover { border-color: var(--acc2); }
/* Numeric columns (quota / used / credits) keep their digits on a fixed grid across rows. */
.admin-users td { font-variant-numeric: tabular-nums; }
.admin-row-note td { text-align: center; color: var(--mut); padding: 24px 14px; }
.status-badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: capitalize;
}
.status-badge.ok { background: rgba(22, 163, 74, .12); color: #15803d; }
.status-badge.off { background: rgba(229, 72, 77, .12); color: #c2363b; }
.admin-feat-row td { background: var(--panel2); padding: 10px 14px; }
.admin-feat-row label { display: inline-block; margin-right: 16px; color: var(--ink); font-size: .9em; }

/* ---------- Guides ---------- */
.guide {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  margin-bottom: 12px;
  overflow: hidden;
}
.guide summary {
  padding: 14px 18px;
  cursor: pointer;
  font-weight: 600;
  color: var(--ink);
  list-style: none;
}
.guide summary::-webkit-details-marker { display: none; }
.guide summary::before {
  content: "▸";
  display: inline-block;
  margin-right: 8px;
  color: var(--acc);
  transition: transform .15s ease;
}
.guide[open] summary::before { transform: rotate(90deg); }
.guide-body { padding: 0 18px 18px; color: var(--mut); }

/* ---------- Modal (admin create/reset-password confirmation) ---------- */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(36, 29, 64, .5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1100;
}
.modal-box {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 24px;
  max-width: 480px;
  width: 100%;
  box-shadow: 0 10px 40px rgba(36, 29, 64, .3);
  line-height: 1.6;
}
.modal-box code {
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 2px 6px;
  font-size: .95em;
}
.modal-box .btn { margin-top: 14px; }

/* ---------- Admin ---------- */
/* Same topbar language as #app's, so the admin screen reads as part of the product instead of a
   bare form bolted on — before this it had no brand, no page title and no way to log out at all
   (#btn-logout lives inside #app's topbar, which an admin account never mounts). */
.admin-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  padding: 0 24px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
.admin-topbar .brand { border-bottom: 0; padding: 0; margin: 0; }
.admin-badge {
  margin-left: 4px;
  padding: 2px 9px;
  border-radius: 999px;
  background: rgba(109,77,242,.12);
  color: var(--acc);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}
#admin-root { padding: 24px; }
.admin-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--line);
}
.tab-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 16px;
  min-height: 44px;
  border: none;
  background: none;
  color: var(--mut);
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  /* The active bar sits ON the tablist's own border, not 2px above it — margin-bottom:-1px
     drops the button over the rule so the accent underline and the divider read as one line. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  border-radius: 8px 8px 0 0;
  transition: color .15s ease, background-color .15s ease;
}
.tab-btn:hover { color: var(--ink); background: rgba(109, 77, 242, .06); }
.tab-btn.on { color: var(--acc); border-bottom-color: var(--acc); }
.tab-ico { width: 17px; height: 17px; flex: none; opacity: .7; }
.tab-btn.on .tab-ico { opacity: 1; }
.admin-pane { max-width: 1180px; }
.admin-pane .panel { max-width: 640px; }
.admin-pane .table-wrap { max-width: 1180px; }
#admin-new-features {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 14px;
  margin: 12px 0;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
#admin-new-features label { display: flex; align-items: center; gap: 6px; font-size: .9em; color: var(--ink); }
/* Admin form conclusions: submit + siblings on one line with breathing room above, instead of a
   button glued straight under the last field's 4px label margin. */
.btn-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
.btn-row .btn { margin: 0; }
/* Host + port belong on one line — port is 5 digits, not a full-width field. */
.form-row { display: grid; grid-template-columns: 1fr 130px; gap: 0 12px; align-items: start; }
/* Write-only secrets: input + show/hide eye. The toggle overlays the field's right edge, so the
   input reserves that width — otherwise long values disappear under the button. */
.pw-wrap { position: relative; display: block; }
.pw-wrap > input { padding-right: 44px; }
.pw-toggle {
  position: absolute;
  top: 50%;
  right: 5px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--mut);
  cursor: pointer;
}
.pw-toggle:hover { color: var(--ink); background: var(--panel2); }
.pw-toggle svg { width: 17px; height: 17px; display: block; }
/* Both eye glyphs ship in the button; aria-pressed state (.showing) picks which one paints. */
.pw-toggle .ico-eye-off { display: none; }
.pw-toggle.showing .ico-eye { display: none; }
.pw-toggle.showing .ico-eye-off { display: block; }
/* .fset was written for the Create wizard's grid; inside an admin .panel it just needs its own
   vertical rhythm — first heading flush with the h2's margin, later ones separating groups. */
.admin-pane .fset { margin: 20px 0 4px; }
.admin-pane .fset:first-of-type { margin-top: 14px; }

/* ---------- Bulk (B7) ---------- */
.hide { display: none !important; }
.mut { color: var(--mut); }
/* Bulk render and Book Builder are two panels stacked in one view, so they share a width —
   640 vs full-bleed made the view look like two unrelated pages glued together. */
/* Width now comes from #view-bulk itself (see .view above) so the "Bulk render" heading and the
   panels below it share one right edge. */
#bulk-lists { min-height: 180px; }
.preset-list {
  display: flex; flex-wrap: wrap; gap: 8px 16px;
  border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px;
}
.bulk-preset-item { display: flex; align-items: center; gap: 6px; font-size: .9em; color: var(--ink); }
.bulk-preset-item input { width: auto; }
.bulk-rows { display: flex; flex-direction: column; gap: 6px; margin-bottom: 20px; }
.bulk-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); font-size: .9em;
}
.bulk-row-label { flex: 1 1 auto; font-weight: 600; }
.bulk-row-status { color: var(--mut); }
.bulk-row-running .bulk-row-status { color: var(--acc); font-weight: 600; }
.bulk-row-done .bulk-row-status { color: var(--ok); font-weight: 600; }
.bulk-row-failed .bulk-row-status, .bulk-row-skipped .bulk-row-status { color: var(--danger); font-weight: 600; }
.bulk-row-err { color: var(--danger); font-size: .85em; }
#admin-new-features input { width: auto; }

/* ---------- Responsive (B9) — mobile viewport, e.g. 390px wide phones ---------- */
/* No @media rules existed before B9 (checked: app.css was 433 lines, none). Sidebar collapses
   from a fixed 240px nav column into a horizontal scrollable strip above the content; the Create
   view's 2-column grid (controls | canvas) stacks to 1 column with the canvas capped to a sane
   width so it visibly shrinks instead of overflowing (aspect-ratio + max-width:100% on #pf-stage
   already keep it from breaking layout — this just gives it a tighter cap on narrow screens). */
@media (max-width: 640px) {
  #app {
    grid-template-columns: 1fr;
    grid-template-rows: auto 56px minmax(0, 1fr); /* same min-content trap as the desktop grid */
    grid-template-areas:
      "sidebar"
      "topbar"
      "main";
  }
  #app > .sidebar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 8px 12px;
    border-right: none;
    border-bottom: 1px solid var(--line);
    overflow-x: auto;
  }
  #app > .sidebar .grp, #app > .sidebar .brand { display: none; } /* labels/brand take too much space on a nav strip */
  #app > .sidebar [data-view],
  #app > .sidebar .upsell {
    padding: 8px 10px;
    font-size: .85em;
    white-space: nowrap;
  }
  #app > main { padding: 14px; }
  #create-cols { grid-template-columns: 1fr; }
  /* On one column the preview leads and the controls scroll with the page, not inside a box. */
  #pf-controls { order: 2; overflow-y: visible; }
  #pf-stage { order: 1; max-width: 320px; margin: 0 auto; }
  /* Admin on a phone: tabs scroll sideways as a strip instead of wrapping into two half-rows,
     and the host/port pair stacks — a 130px port column beside a shrunken host is unusable. */
  #admin-root { padding: 14px; }
  .admin-tabs { overflow-x: auto; }
  .tab-btn { white-space: nowrap; }
  .form-row { grid-template-columns: 1fr; }
}

/* ---- KDP Toolkit (OTO2) ---- */
/* A locked card used to fade wholesale, heading included, which reads as a broken render rather
   than a gate. Now only the controls dim, and they do it via :disabled above — the paragraph that
   explains what the upgrade buys stays fully readable, which is the whole point of showing it. */
.kdp-card.locked-card h2::after {
  content: "🔒 Puzzle Pack";
  margin-left: 10px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--panel2);
  border: 1px solid var(--line);
  color: var(--mut);
  font-size: .6em;
  font-weight: 600;
  vertical-align: middle;
  white-space: nowrap;
}
.kdp-field { margin-top: 14px; }
.kdp-field h4 { margin: 0 0 4px; display: flex; align-items: center; gap: 10px; font-size: .95em; }
.kdp-field p { margin: 0; white-space: pre-wrap; }
.kdp-copy { padding: 2px 8px; font-size: .78em; }
.kdp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.kdp-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
.kdp-table th, .kdp-table td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.kdp-table th { font-size: .8em; text-transform: uppercase; letter-spacing: .04em; color: var(--mut); }
.vid-thumb { width: 36px; height: 64px; border-radius: 6px; object-fit: cover; display: block; }

/* --- Wave 3: Projects grid + project bar ------------------------------------------------ */
/* Actions sit beside the title instead of being pinned to the far right by space-between. A grid
   holding two or three cards only fills the left of the row, so a "+ New list" button parked
   700px away from the last card read as a stray element with nothing to do with the page. */
.view-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.view-head-actions { display: flex; align-items: center; gap: 12px; }
/* The filter's label sits inline with its select — stacked, it made the toolbar two rows tall and
   left the page title floating against the middle of a control it has nothing to do with. */
.view-head-actions .fgroup { display: flex; align-items: center; gap: 8px; margin: 0; }
.view-head-actions .fgroup > select { margin-top: 0; width: auto; }
.proj-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 16px; margin-top: 18px; }
.proj-card {
  background: #fff; border: 1px solid rgba(0,0,0,.08); border-radius: 12px; padding: 12px;
  display: flex; flex-direction: column; gap: 6px;
  box-shadow: 0 2px 10px rgba(36, 29, 64, .05);
  transition: box-shadow .15s ease, transform .15s ease;
}
.proj-card:hover { box-shadow: 0 8px 24px rgba(36, 29, 64, .12); transform: translateY(-2px); cursor: pointer; }
.proj-card h3 { font-size: 15px; margin: 0; line-height: 1.3; overflow-wrap: anywhere; }
.proj-thumb { aspect-ratio: 9 / 16; max-height: 150px; display: grid; place-items: center; background: rgba(0,0,0,.05); border-radius: 8px; overflow: hidden; }
.proj-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.proj-thumb-ico { width: 34px; height: 34px; color: var(--mut2); }
.proj-meta { margin: 0; font-size: 12px; opacity: .75; }
.proj-badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; }
.proj-badge.draft { background: rgba(0,0,0,.08); }
.proj-badge.exported { background: #d1fae5; color: #065f46; }
.proj-actions { display: flex; gap: 8px; margin-top: auto; }
.proj-actions .btn { flex: 1; font-size: 12px; padding: 6px 8px; }
#project-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; padding: 10px 12px; border-radius: 10px; background: rgba(0,0,0,.04); }
#project-bar #proj-name { flex: 1 1 200px; font-weight: 600; }
#proj-exports { flex-basis: 100%; font-size: 12px; }
#proj-exports ul { margin: 6px 0 0; padding-left: 18px; }

/* --- Wave 3: Create wizard --------------------------------------------------------------- */
#wiz-steps { display: flex; gap: 8px; list-style: none; margin: 0 0 16px; padding: 0; }
#wiz-steps li { display: flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 999px; font-size: 13px; cursor: pointer; background: rgba(0,0,0,.05); opacity: .6; }
#wiz-steps li.on { opacity: 1; background: rgba(90,75,199,.14); font-weight: 600; }
#wiz-steps li span { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: rgba(0,0,0,.12); font-size: 12px; }
#wiz-steps li.on span { background: #5a4bc7; color: #fff; }
/* The action bar sticks to the bottom of the control column's own scrollport, so Preview /
   Render / Export are reachable from either wizard step without scrolling to find them — and it
   gives the bottom of the card something to be instead of empty space. Negative margins bleed it
   to the card's edges; `bottom: -16px` cancels the container's bottom padding so it parks flush. */
/* margin-top:auto AND sticky, because they cover different cases: with short content (step 1) the
   auto margin pushes the bar to the bottom of the card — sticky alone would leave it floating
   mid-card above dead space, which is what this bar exists to remove. With Advanced open the
   content overflows, the auto margin stops doing anything, and sticky pins the bar while the
   fields scroll behind it. */
#pf-actions {
  position: sticky;
  bottom: -16px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px 16px;
  flex-wrap: wrap;
  /* Order matters: the shorthand must not come after margin-top, or it resets auto to a length.
     The breathing room above the bar is padding-top, which auto cannot eat. */
  margin: 0 -16px -16px;
  margin-top: auto;
  padding: 16px 16px 12px;
  background: var(--panel);
  border-top: 1px solid var(--line);
  border-radius: 0 0 12px 12px;
}
#wiz-nav { display: flex; gap: 10px; margin: 0; }
#wiz-nav .btn { min-width: 120px; }
#pf-actions .btn { margin: 0; }
details.adv { margin: 10px 0; border-top: 1px solid rgba(0,0,0,.08); padding-top: 10px; }
details.adv summary { cursor: pointer; font-size: 13px; opacity: .8; }
details.adv[open] summary { margin-bottom: 10px; }

/* A wizard step flows into as many columns as its column has room for. One field per line was
   right when the controls were a fixed 320px, but the controls now take whatever the 9:16 frame
   does not (see #create-cols): at 835px that meant a single 835px-wide column of inputs and a
   scrollbar, with the Render button below the fold. Both steps now fit without scrolling. */
.wiz-step, .adv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 0 18px;
  align-content: start;
}
/* Full-bleed rows: the format tabs and the word list read as one thing, not as a field among
   fields, and the action row is the step's conclusion. */
#opt-format-tab, .wl-row, #word-errors, details.adv, .span-all { grid-column: 1 / -1; }
/* The list is five short words — the box does not need the whole 835px to say so. */
#opt-words { max-width: 560px; }
/* #pf-controls .btn carries margin-bottom:14px for stacked buttons; in this row the gap does that
   job and the margin would only push the chip off the buttons' centre line. */
.act-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
#pf-controls .act-row .btn { margin: 0; }
/* Same margin-box-bottom alignment as every other button-beside-a-field in this app. justify-self
   because a grid item stretches by default, and a 🎲 stretched across a 255px cell reads as an
   empty input, not a button. */
.adv-grid > .btn { align-self: end; justify-self: start; margin: 0 0 14px; }

/* --- Wave 3: upgrade modal --------------------------------------------------------------- */
#upgrade-modal { max-width: 760px; width: calc(100% - 32px); border: 0; border-radius: 16px; padding: 28px; position: relative; }
#upgrade-modal::backdrop { background: rgba(15,12,40,.55); }
#upgrade-modal h2 { margin: 0 0 6px; }
#upgrade-why { margin: 0 0 18px; opacity: .8; }
.up-close { position: absolute; top: 12px; right: 14px; border: 0; background: none; font-size: 18px; cursor: pointer; }
#upgrade-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; }
.up-card { border: 1px solid rgba(0,0,0,.1); border-radius: 12px; padding: 16px; display: flex; flex-direction: column; gap: 8px; }
.up-card h3 { margin: 0; font-size: 16px; }
.up-card p { margin: 0; font-size: 13px; }
.up-card .btn, .up-card .chip { margin-top: auto; text-align: center; }
.up-price { font-size: 26px; font-weight: 700; }
.up-hot { border-color: #5a4bc7; box-shadow: 0 0 0 3px rgba(90,75,199,.15); }
.up-owned { opacity: .6; }
#f-upgrade-key { margin-top: 20px; display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
/* margin:0 — .fgroup ships margin-bottom:14px, and align-items:flex-end lines up MARGIN box
   bottoms, so the label block ended 14px above the button it sits beside. */
#f-upgrade-key .fgroup { flex: 1 1 240px; margin: 0; }

/* --- Wave 4: word list library ----------------------------------------------------------- */
.view-note { margin: 8px 0 0; font-size: 13px; opacity: .75; max-width: 62ch; }
.wl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 16px; margin-top: 18px; }
.wl-card { background: #fff; border: 1px solid rgba(0,0,0,.08); border-radius: 12px; padding: 14px; display: flex; flex-direction: column; gap: 8px; }
.wl-card h3 { font-size: 15px; margin: 0; line-height: 1.3; overflow-wrap: anywhere; }
.wl-preview { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 auto; }
.wl-preview span { padding: 2px 8px; border-radius: 999px; background: rgba(0,0,0,.06); font-size: 11px; letter-spacing: .03em; }
#wl-modal { max-width: 520px; width: calc(100% - 32px); border: 0; border-radius: 16px; padding: 28px; position: relative; }
#wl-modal::backdrop { background: rgba(15,12,40,.55); }
#wl-modal h2 { margin: 0 0 14px; }
.wl-modal-actions { display: flex; justify-content: flex-end; margin-top: 16px; }
.wl-row { display: flex; align-items: flex-end; gap: 10px; }
.wl-row label { flex: 1; }

/* --- Wave 4: onboarding ------------------------------------------------------------------ */
#onboard-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 12px; }
#onboard-list li { display: flex; gap: 12px; align-items: flex-start; }
#onboard-list li p { margin: 2px 0 8px; font-size: 13px; opacity: .8; }
/* No line-through on a completed step: struck-out text reads as cancelled or deleted, which is
   the opposite of what a green ✓ means. The tick plus the dimmed row already carry "done". */
#onboard-list li.done { opacity: .6; }
#onboard-list li > div { max-width: 62ch; }
.ob-tick { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: rgba(0,0,0,.08); font-size: 13px; flex: 0 0 auto; }
#onboard-list li.done .ob-tick { background: #d1fae5; color: #065f46; }
.onboard-sample { margin: 18px 0 0; font-size: 13px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* --- In-view guide cards (.pfg) ------------------------------------------------------------
   One collapsible step card per feature view. Closed it is a single tinted bar (~52px) — the
   Create view is height-bound, so a card that cost more than that when folded would come
   straight out of the preview frame's row. */
.pfg {
  margin: 0 0 14px;
  border: 1px solid rgba(109,77,242,.22);
  border-left: 3px solid var(--acc);
  border-radius: 12px;
  background: var(--panel);
  overflow: hidden;
}
.pfg > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  cursor: pointer;
  list-style: none;
  background: rgba(109,77,242,.06);
}
.pfg > summary::-webkit-details-marker { display: none; }
.pfg-ic { display: flex; flex: 0 0 auto; }
.pfg-ic svg { width: 20px; height: 20px; color: var(--acc); }
.pfg-ttl { flex: 1 1 auto; min-width: 0; }
.pfg-ttl > b { display: block; font-size: 14px; }
.pfg-ttl > small { display: block; margin-top: 2px; color: var(--mut); font-size: 12px; }
/* The affordance carries its own state so a folded card still says what opening it gets you. */
.pfg-tog {
  flex: 0 0 auto;
  color: var(--acc);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.pfg-tog::after { content: " ▾"; display: inline-block; transition: transform .15s ease; }
.pfg[open] > summary .pfg-tog::after { transform: rotate(180deg); }
.pfg[open] > summary .pfg-tog { color: var(--mut); }
.pfg-body { padding: 4px 16px 14px; }
.pfg-step { display: flex; gap: 12px; align-items: flex-start; padding: 10px 0; border-top: 1px solid var(--line); }
.pfg-n {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(109,77,242,.12);
  color: var(--acc);
  font-size: 12px;
  font-weight: 700;
}
.pfg-tx { min-width: 0; max-width: 78ch; font-size: 13px; }
/* Direct-child selectors only: the step copy itself is full of inline <b> (control names), and a
   blanket `.pfg-tx b { display: block }` broke every one of them onto its own line. */
.pfg-tx > b { display: block; margin-bottom: 2px; }
.pfg-tx > span { color: var(--mut); }
.pfg-foot { margin: 12px 0 0; padding: 10px 12px; border-radius: 8px; background: var(--panel2); color: var(--mut); font-size: 12.5px; }

/* --- Wave 5: export dialog + history ------------------------------------------------------ */
#export-modal { max-width: 460px; width: calc(100% - 32px); border: 0; border-radius: 16px; padding: 28px; position: relative; }
#export-modal::backdrop { background: rgba(15,12,40,.55); }
#export-modal h2 { margin: 0 0 4px; }
#export-modal .mut { margin: 0 0 16px; font-size: 13px; }
.videos-table .proj-badge { text-transform: uppercase; letter-spacing: .04em; font-size: 10px; }

/* --- Formats gallery ---------------------------------------------------------------------- */
/* Wider min than .wl-grid (230px): these cards carry two paragraphs, and at 230px the blurb broke
   into a ten-line column that read as filler instead of as a reason to buy. */
.fmt-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; margin-top: 18px; }
.fmt-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 16px; border: 1px solid rgba(0,0,0,.08); border-radius: 12px; background: #fff;
  box-shadow: 0 2px 10px rgba(36, 29, 64, .05);
  transition: box-shadow .15s ease, transform .15s ease;
}
.fmt-card:not(.locked):hover { box-shadow: 0 8px 24px rgba(36, 29, 64, .12); transform: translateY(-2px); }
.fmt-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.fmt-ico { width: 24px; height: 24px; flex: none; color: var(--acc); }
.fmt-card h3 { margin: 0; font-size: 16px; }
.fmt-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 600; letter-spacing: .02em; }
.fmt-badge.ready { background: rgba(37,148,94,.12); color: #1f7a4d; }
.fmt-badge.locked { background: rgba(109,77,242,.12); color: var(--acc); }
.fmt-badge-ico { width: 11px; height: 11px; flex: none; }
.fmt-blurb { margin: 0; font-size: 13.5px; line-height: 1.5; }
/* margin-bottom:auto, not on .fmt-blurb: "Best for" is the last text in the card, so it is what
   pushes the button to a shared baseline across cards of unequal blurb length. */
.fmt-best { margin: 0 0 auto; color: var(--mut); font-size: 12.5px; }
.fmt-card .btn { margin: 0; align-self: start; }
/* A locked card is marked, never disabled: its button IS the offer, so it stays clickable. The
   border borrows the badge accent — dashed at the base 8%-black border colour was invisible. */
.fmt-card.locked { background: var(--panel2); border: 1px dashed rgba(109,77,242,.38); }

/* --- Create step 2: grouped settings ------------------------------------------------------ */
/* A section heading is a full-bleed grid row (.span-all supplies the column span). The rule runs
   to the right edge so the eye reads it as a divider, not as a label belonging to column 1. */
.fset {
  display: flex; align-items: center; gap: 10px;
  margin: 4px 0 10px;
  color: var(--mut2);
  font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
}
.fset::after { content: ""; flex: 1; height: 1px; background: var(--line); }
/* The first heading in the step must not push the whole column down. */
.wiz-step > .fset:first-child { margin-top: 0; }
/* Hints sit under their own control, inside the same .fgroup, so they stay in that grid cell and
   cannot line up under a neighbouring field. */
.hint { display: block; margin-top: 5px; color: var(--mut2); font-size: 11.5px; line-height: 1.45; }
/* A slider that belongs to the checkbox above it, not to a label of its own. */
.fgroup > .sub { display: block; margin-top: 8px; color: var(--mut); font-size: .92em; }
.fgroup > .sub > input[type="range"] { display: block; margin-top: 4px; width: 100%; }
/* A control and the button that acts on it, on one line. */
.inline-row { display: flex; align-items: flex-end; gap: 10px; }
.inline-row > label { flex: 1; min-width: 0; }
.inline-row > label > input { display: block; margin-top: 4px; width: 100%; color: var(--ink); font-size: 1em; }
/* Same id-specificity trap as .act-row above: `#pf-controls .btn` sets a 14px bottom margin, so
   in a flex-end row the button parks 14px ABOVE the input it belongs to. Match that specificity. */
#pf-controls .inline-row > .btn { margin: 0; flex: none; }
/* Preview transport: visible only while a play is running, so the bar does not sit there empty. */
.prev-prog { width: 120px; height: 6px; border: 0; border-radius: 999px; background: var(--panel2); }
.prev-prog::-webkit-progress-bar { background: var(--panel2); border-radius: 999px; }
.prev-prog::-webkit-progress-value { background: var(--acc); border-radius: 999px; }
.prev-prog::-moz-progress-bar { background: var(--acc); border-radius: 999px; }
/* Wide content scrolls inside its own box, never the page. Eight columns of user data is already
   the widest thing in the app; the next column added is the one that would have pushed it out. */
.table-wrap { overflow-x: auto; }

/* --- Bonuses view (FE fast-action bonuses, bonuses.js) --------------------------------- */
.bonus-sec { background: var(--panel, #fff); border: 1px solid var(--line); border-radius: 12px; padding: 4px 18px 14px; margin-bottom: 14px; }
.bonus-sec summary { cursor: pointer; list-style: none; padding: 12px 0; }
.bonus-sec summary::-webkit-details-marker { display: none; }
.bonus-sec summary h2 { display: inline; font-size: 1.05em; }
.bonus-sec summary::before { content: "▸"; margin-right: 8px; color: var(--mut); }
.bonus-sec[open] summary::before { content: "▾"; }
.bonus-week { margin: 18px 0 4px; font-size: .95em; }
.bonus-rules { margin: 12px 0 0 18px; line-height: 1.6; }
.bonus-rules li { margin-bottom: 6px; }
.bonus-niche { margin-top: 12px; }
.bonus-niche h3 { margin-bottom: 6px; }
.bonus-niche p { margin: 4px 0; line-height: 1.55; }

/* AI word list: the niche field that replaced window.prompt() (see studio.js mountAiWidgets).
   Inline with the button so the two-step reveal reads as one control, not a new section. */
#ai-niche-row { display: inline-flex; gap: 8px; align-items: center; margin-left: 8px; flex-wrap: wrap; }
#ai-niche { min-width: 220px; flex: 1 1 220px; }
