/* ============================================================
   SCHOOL THEME
   ------------------------------------------------------------
   The palette is derived from the school crest. Colours sampled
   from the logo artwork:
       navy #00047b · red #fe0005 · gold #febd01
   Those are saturated print colours, so the UI uses a slightly
   deepened, softened scale from the same families — same identity,
   comfortable for long screen use.

   Applied in a 60 / 30 / 10 balance:
     60%  neutral surfaces (page background + white cards)
     30%  navy (sidebar, headers, primary actions)
     10%  gold accent (active/selected states, highlights)
   Red is reserved exclusively for destructive actions, so it keeps
   its "stop and think" meaning.
   ============================================================ */
:root {
  /* Brand — navy */
  --navy-900: #0a1240;   /* sidebar base */
  --navy-800: #101a5c;
  --navy-700: #16227a;   /* primary brand */
  --navy-600: #22308f;   /* hover on primary */
  --navy-500: #3346a6;   /* focus ring */
  --navy-100: #e7ebf8;   /* selected tint on light surfaces */

  /* Brand — gold accent */
  --gold-500: #f5b301;
  --gold-600: #d99a00;
  --gold-100: #fff4d6;

  /* Output-type badges. Named here rather than at the one place they are
     drawn, because the generate button borrows them for the badge it throws
     to the sidebar — the two must never drift apart. */
  --type-html: #0f7a63;  /* teal  — webpages */
  --type-docx: #9e5c00;  /* amber — documents (5.3:1 on white) */
  --type-graphic: #7a2f8f; /* violet — infographics (5.9:1 on white) */

  /* Destructive only */
  --red-600: #c8102e;
  --red-700: #a20d25;
  --red-50:  #fdecef;

  /* Neutrals, cool-tinted to sit naturally with the navy */
  --bg:        #f4f6fb;
  --surface:   #ffffff;
  --border:    #dde3ee;
  --text:      #141b2d;
  --muted:     #5b6880;

  /* Semantic aliases (keep older rules working) */
  --navy: var(--navy-700);
  --navy-dark: var(--navy-800);
  --navy-light: var(--navy-600);
  --accent: var(--gold-500);
  --error: var(--red-600);

  --sidebar-w: 270px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
}

/* ============================================================
   SCROLLBARS
   ------------------------------------------------------------
   macOS hides its scrollbars until you scroll, so this went unnoticed there.
   Windows does not: it paints a wide light-grey track with an arrow button at
   each end, permanently, on every scrolling box. On the navy rail that is a
   pale stripe down the side of the sidebar, and it looks like a mistake.

   So: no buttons, no track, a thin rounded thumb that is INVISIBLE until the
   box is scrolled or hovered. The width is reserved whether the thumb shows or
   not, which is what stops the content shifting sideways as it fades in.

   Colour comes from two variables rather than two sets of rules. A pseudo
   element inherits custom properties from the element it belongs to, so the
   sidebar redefining them is enough to flip the whole rail to a light thumb.
   ============================================================ */
:root {
  --sb-size: 12px;
  --sb-thumb: rgb(34 48 143 / .3);        /* navy, for light surfaces */
  --sb-thumb-hover: rgb(34 48 143 / .55);
}
.sidebar {
  --sb-thumb: rgb(255 255 255 / .24);     /* light, for the navy rail */
  --sb-thumb-hover: rgb(255 255 255 / .42);
}

*::-webkit-scrollbar { width: var(--sb-size); height: var(--sb-size); }
*::-webkit-scrollbar-track { background: transparent; }
/* Windows draws stepper arrows at both ends; nothing else here has them. */
*::-webkit-scrollbar-button { display: none; }
*::-webkit-scrollbar-corner { background: transparent; }

*::-webkit-scrollbar-thumb {
  background-color: transparent;
  border-radius: 999px;
  /* A transparent border with padding-box clipping is what makes the thumb
     look narrower than the track it lives in, without shrinking the target a
     mouse has to hit. */
  border: 3px solid transparent;
  background-clip: padding-box;
  transition: background-color .25s ease;
}

/* Visible while the box is being scrolled (the class is added by app.html) or
   while the pointer is over it, so it can still be grabbed and dragged. */
*.is-scrolling::-webkit-scrollbar-thumb,
*:hover::-webkit-scrollbar-thumb {
  background-color: var(--sb-thumb);
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--sb-thumb-hover); }

/* Firefox has no ::-webkit-scrollbar and takes the standard properties
   instead. Guarded, because Chromium 121+ understands them too and lets them
   OVERRIDE everything above — applying them unconditionally would trade all
   of this for a plain thin bar on the browsers most of the school uses. */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--sb-thumb) transparent;
  }
}

.hidden { display: none !important; }

/* ============ LOGIN PAGE ============ */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.login-card {
  width: 380px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 30px;
}
.login-card h2 { margin-top: 0; color: var(--navy); }

/* Login brand block — crest on the card's own white surface */
.login-brand { text-align: center; margin-bottom: 24px; }
.login-brand img { width: 92px; height: auto; display: block; margin: 0 auto 14px; }
.login-brand h2 { margin: 0; font-size: 19px; color: var(--navy-700); }
.login-sub {
  margin: 4px 0 0; font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .9px; font-weight: 600;
}
/* Gold hairline separating the brand from the form */
.login-brand::after {
  content: ""; display: block; width: 46px; height: 3px; border-radius: 2px;
  background: var(--gold-500); margin: 16px auto 0;
}
.login-card .primary { width: 100%; margin-top: 20px; }

/* ============ APP LAYOUT ============ */
.layout { display: flex; min-height: 100vh; }

/* ---- Sidebar ---- */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: linear-gradient(180deg, var(--navy-800) 0%, var(--navy-900) 100%);
  color: #fff;
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0; bottom: 0; left: 0;
}

/* Brand block: the crest sits on its own white plate so the artwork
   always has the light background it was drawn for, instead of being
   dropped straight onto navy. */
.brand {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 18px;
  border-bottom: 1px solid rgba(255,255,255,.12);
}
.brand-logo {
  background: #fff;
  border-radius: 10px;
  padding: 6px;
  flex-shrink: 0;
  line-height: 0;
  box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
.brand-logo img { width: 34px; height: auto; display: block; }
/* Carries the brand row on its own now that the strapline is gone, so it is
   sized to hold its side against the crest rather than to head a second line. */
.brand-text { display: block; font-size: 17px; font-weight: 700; letter-spacing: .3px; line-height: 1.2; }

/* Profile block */
.profile {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid rgba(255,255,255,.12);
}
.avatar {
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--gold-500); color: var(--navy-900);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 15px; flex-shrink: 0;
}
.profile-name { font-size: 14px; font-weight: bold; }
.profile-status { font-size: 11px; opacity: .7; }

/* Mode tabs */
.tabs { padding: 14px 12px; border-bottom: 1px solid rgba(255,255,255,.12); }
.tabs-label, .history-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: .8px;
  opacity: .55; padding: 0 8px 8px;
}
/* The stored count sits with the list it describes. */
.history-label { display: flex; align-items: baseline; justify-content: space-between; }
.history-count { font-variant-numeric: tabular-nums; letter-spacing: .3px; }
.history-count.is-full { color: var(--gold-500); opacity: 1; font-weight: 700; }
.tab {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  background: transparent; color: rgba(255,255,255,.82); border: none;
  padding: 11px 12px; margin-bottom: 4px;
  border-radius: 7px; font-size: 14px; cursor: pointer;
  font-family: inherit; transition: background .15s, color .15s;
}
.tab:hover { background: rgba(255,255,255,.09); color: #fff; }
.tab.active { background: rgba(255,255,255,.13); color: #fff; font-weight: 700; }
/* Gold marker on the active tab — the accent colour from the crest. */
.tab.active::before {
  content: ""; position: absolute; left: 0; top: 50%;
  transform: translateY(-50%);
  width: 3px; height: 20px; border-radius: 0 3px 3px 0;
  background: var(--gold-500);
}
.tab-icon { font-size: 16px; }

/* History list */
.history { flex: 1; overflow-y: auto; padding: 14px 12px; min-height: 0; }
.history-list { list-style: none; margin: 0; padding: 0; }
.history-item {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 9px 10px; margin-bottom: 3px;
  border-radius: 7px; cursor: pointer; font-size: 13px;
  position: relative;
}
.history-item:hover { background: rgba(255,255,255,.09); }
.history-item .h-type {
  font-size: 9px; font-weight: bold; padding: 2px 5px;
  border-radius: 3px; margin-top: 2px; flex-shrink: 0;
}
.h-type.html { background: var(--type-html); }
.h-type.docx { background: var(--type-docx); }
.h-type.infographic { background: var(--type-graphic); }
/* Badge column: type badge, and the globe beneath it when published. */
.history-item .h-tagcol {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  flex-shrink: 0;
}
.history-item .h-live {
  font-size: 11px; line-height: 1;
  /* Emoji arrive at full saturation and would out-shout the type badge they
     sit under, which is the thing carrying the colour coding. */
  filter: grayscale(.25);
}

.history-item .h-body { min-width: 0; flex: 1; }
/* Wraps to two lines rather than truncating: a one-line ellipsis cut every
   name at the subject and left them all looking the same. */
.history-item .h-topic {
  font-weight: 600; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}
.history-item .h-meta { font-size: 11px; opacity: .6; }
.history-empty { font-size: 12px; opacity: .55; padding: 8px 10px; }

/* History item menu (3-dots) */
.h-menu-btn {
  background: none; border: none; color: rgba(255,255,255,.5);
  font-size: 18px; padding: 2px 6px; cursor: pointer; flex-shrink: 0;
  visibility: hidden; opacity: 0; transition: opacity .2s;
}
.history-item:hover .h-menu-btn { visibility: visible; opacity: 1; }
.h-menu-btn:hover { color: rgba(255,255,255,.8); }

.h-menu {
  position: absolute; right: 12px; top: 100%; background: var(--surface);
  border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,.3);
  min-width: 140px; z-index: 100; margin-top: 4px; display: none;
}
.history-item.menu-open .h-menu { display: block; }
.h-menu button {
  display: block; width: 100%; text-align: left; padding: 10px 14px;
  border: none; background: none; color: var(--text); font-size: 13px; cursor: pointer;
  font-family: inherit;
}
.h-menu button:first-child { border-radius: 6px 6px 0 0; }
.h-menu button:last-child { border-radius: 0 0 6px 6px; }
.h-menu button:hover { background: var(--navy-100); }
.h-menu button.danger { color: var(--red-600); }
.h-menu button.danger:hover { background: var(--red-50); }

/* Inline delete confirmation (replaces the native confirm() dialog) */
.h-confirm { display: none; padding: 12px 14px; }
.h-menu.confirming > div:first-child { display: none; }
.h-menu.confirming .h-confirm { display: block; }
.h-confirm-text { font-size: 12px; color: var(--text); margin-bottom: 10px; }
.h-confirm-row { display: flex; gap: 8px; }
.h-confirm-row button {
  flex: 1; padding: 7px 10px; border-radius: 5px;
  border: 1px solid var(--border); background: #fff; font-size: 12px;
}
.h-confirm-row button.danger {
  background: var(--red-600); border-color: var(--red-600); color: #fff; font-weight: 600;
}
.h-confirm-row button.danger:hover { background: var(--red-700); }

.sidebar-footer { padding: 12px 20px; border-top: 1px solid rgba(255,255,255,.12); }
.link-btn {
  background: none; border: none; color: rgba(255,255,255,.72);
  font-size: 13px; cursor: pointer; padding: 0; font-family: inherit;
}
.link-btn:hover { text-decoration: underline; }

/* ---- Main content ----
   The sidebar is fixed, so `.main` clears it with a margin rather than by
   sitting beside it in flow. The width cap used to live here, and a fixed
   left margin cannot be balanced by an automatic right one — so the column
   stayed pinned against the sidebar and every pixel past 1000px piled up on
   the right.
   The cap moved onto the children instead, where `margin-inline: auto` has a
   free margin on both sides to split. The column now sits in the middle of
   whatever room the sidebar leaves, and follows it when the rail collapses. */
.main {
  margin-left: var(--sidebar-w);
  flex: 1;
  padding: 28px 34px;
}
.main > * {
  max-width: 1000px;
  margin-inline: auto;
}
.page-head { margin-bottom: 20px; }
.page-head h2 { margin: 0 0 4px; color: var(--navy); font-size: 22px; }
.page-head p { margin: 0; color: var(--muted); font-size: 14px; }

.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 24px;
}

/* ---- Forms ---- */
label { display: block; margin: 15px 0 5px; font-weight: 600; font-size: 13px; }
input[type="text"], input[type="number"], input[type="password"], select, textarea {
  width: 100%; padding: 9px 11px;
  border: 1px solid var(--border); border-radius: 6px;
  font-size: 14px; font-family: inherit; background: #fff;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--navy-500);
  box-shadow: 0 0 0 3px rgba(51, 70, 166, .18);
}
textarea { resize: vertical; min-height: 72px; }

/* Range slider (e.g. number of quiz questions) */
.slider-value {
  display: inline-block; background: var(--navy-100); color: var(--navy-700);
  font-weight: 700; font-size: 12px; padding: 1px 9px;
  border-radius: 10px; margin-left: 6px;
}
input[type="range"] {
  width: 100%; padding: 0; height: 26px; background: transparent;
  -webkit-appearance: none; appearance: none;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 6px; border-radius: 4px; background: var(--border);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; margin-top: -6px; border-radius: 50%;
  background: var(--navy); border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.35); cursor: pointer;
}
input[type="range"]::-moz-range-track {
  height: 6px; border-radius: 4px; background: var(--border);
}
input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--navy); border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.35); cursor: pointer;
}

/* ============================================================
   BIG SLIDER WITH A TICKED SCALE
   ------------------------------------------------------------
   A plain range gives no sense of where the stops are, so picking "7 of 5-15"
   is guesswork. This one has a thicker track, a larger thumb, and a mark under
   every stop with numbers at intervals — the teacher can see where to release.

   --thumb is shared by the control and the scale: the track's usable width is
   inset by half a thumb at each end, so the scale is padded to match or the
   marks drift away from the handle.
   ============================================================ */
.slider-wrap { --thumb: 24px; margin-top: 4px; }

input[type="range"].big-slider { height: 34px; }
input[type="range"].big-slider::-webkit-slider-runnable-track {
  height: 10px; border-radius: 6px;
  background: var(--navy-100);
  border: 1px solid var(--border);
}
input[type="range"].big-slider::-webkit-slider-thumb {
  width: var(--thumb); height: var(--thumb);
  margin-top: -8px;
  border: 3px solid #fff;
  background: var(--navy-700);
  box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
input[type="range"].big-slider::-moz-range-track {
  height: 10px; border-radius: 6px;
  background: var(--navy-100); border: 1px solid var(--border);
}
input[type="range"].big-slider::-moz-range-thumb {
  width: var(--thumb); height: var(--thumb);
  border: 3px solid #fff;
  background: var(--navy-700);
  box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
input[type="range"].big-slider:focus-visible { outline: none; }
input[type="range"].big-slider:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px var(--gold-100), 0 2px 6px rgba(0,0,0,.3);
}

.slider-scale {
  display: flex; justify-content: space-between;
  padding: 0 calc(var(--thumb) / 2);
  margin-top: -2px;
}
.slider-mark {
  position: relative;
  width: 1px; height: 7px;
  background: var(--border);
  flex: 0 0 auto;
}
.slider-mark.labelled { height: 10px; background: #b9c2d4; }
.slider-mark.labelled::after {
  content: attr(data-label);
  position: absolute; top: 11px; left: 50%;
  transform: translateX(-50%);
  font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* The stop the handle is sitting on. */
.slider-mark.current { background: var(--navy-700); height: 12px; }
.slider-mark.current.labelled::after { color: var(--navy-700); font-weight: 700; }
.slider-scale + * { margin-top: 20px; }   /* clear the number labels */

.row { display: flex; gap: 16px; flex-wrap: wrap; }
.row > .field { flex: 1; min-width: 170px; }
/* A row holding one field should not stretch it across the whole card. */
.row > .field:only-child { flex: 0 1 260px; }

.check-group { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 8px 20px; }
.check-group label {
  display: inline-flex; align-items: center; gap: 7px;
  font-weight: normal; font-size: 14px; margin: 6px 0 0;
}
.check-group input { width: auto; }

/* Question-type picker: a checkbox on the left, and the count control that
   appears beside it once ticked. The row keeps a stable height so the list
   does not jump as boxes are ticked and unticked. */
.qtype-list { margin-top: 10px; display: flex; flex-direction: column; gap: 2px; }
.qtype-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 7px 10px; border-radius: 6px; min-height: 38px;
}
.qtype-row:has(.qtype:checked) { background: var(--wash, #f4f6fa); }
.qtype-label {
  display: inline-flex; align-items: center; gap: 9px;
  font-weight: normal; font-size: 14px; margin: 0; cursor: pointer;
}
.qtype-label input { width: auto; margin: 0; }
.qtype-label .hint { margin-left: 4px; }

.qtype-count { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.qtype-slider { width: 140px; accent-color: var(--navy); cursor: pointer; }
.qtype-value {
  font-size: 13px; font-variant-numeric: tabular-nums; color: var(--navy);
  font-weight: 600; min-width: 84px; text-align: right;
}
.qtype-fixed { font-size: 13px; color: var(--muted, #667); min-width: 84px; text-align: right; }

@media (max-width: 620px) {
  .qtype-row { flex-direction: column; align-items: flex-start; gap: 6px; }
  .qtype-count { width: 100%; }
  .qtype-slider { flex: 1; width: auto; }

  /* The meter is a fixed 146px and does not shrink. Held on the picker row at
     phone widths it would squeeze the topic name down to a few characters, so
     it drops below the dropdown and the Add button instead. */
  .topic-picker { flex-wrap: wrap; }
  .topic-picker .mat-meter { order: 3; flex-basis: 100%; margin-top: 2px; }
}

.fieldset {
  border: 1px solid var(--border); border-radius: 8px;
  padding: 4px 16px 16px; margin-top: 16px;
}
.fieldset > .legend {
  font-size: 12px; font-weight: bold; color: var(--navy);
  text-transform: uppercase; letter-spacing: .5px; margin-top: 14px;
}

button.primary {
  margin-top: 22px; background: var(--navy); color: #fff;
  border: none; padding: 12px 26px; border-radius: 7px;
  font-size: 15px; font-weight: 600; cursor: pointer; font-family: inherit;
}
button.primary:hover { background: var(--navy-600); }
button.primary:disabled { opacity: .55; cursor: not-allowed; }

button.small {
  background: var(--navy); color: #fff; border: none;
  padding: 7px 15px; border-radius: 6px;
  font-size: 13px; cursor: pointer; font-family: inherit;
}
button.small.ghost { background: var(--navy-100); color: var(--navy-700); }

.hint { color: var(--muted); font-size: 12px; margin-top: 4px; }
.error { color: var(--error); font-size: 14px; margin-top: 12px; }

/* Kept: the preview overlay's type badge reuses it. The rest of the old
   inline result panel is gone — finished materials open from the History
   list now, not underneath the form. */
.badge {
  background: var(--navy-100); color: var(--navy-700);
  padding: 3px 10px; border-radius: 12px;
  font-size: 11px; font-weight: bold;
}

/* ============================================================
   SIDEBAR: collapse + resize
   ============================================================ */

/* Drag handle sitting on the sidebar's right edge. Wider than it looks so it
   is easy to grab, but only the inner line is painted. */
.sidebar-resizer {
  position: absolute; top: 0; right: -3px; bottom: 0;
  width: 7px; cursor: col-resize; z-index: 5;
  background: transparent; border: 0;
}
.sidebar-resizer::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 3px;
  width: 1px; background: transparent; transition: background .15s;
}
.sidebar-resizer:hover::after,
.sidebar-resizer:focus-visible::after,
body.resizing .sidebar-resizer::after { background: var(--gold-500); }
.sidebar-resizer:focus-visible { outline: none; }

/* While dragging, kill text selection and pointer events in the page so the
   cursor stays a resize arrow the whole way across. */
body.resizing { user-select: none; cursor: col-resize; }
body.resizing iframe { pointer-events: none; }

.sidebar, .main { transition: width .18s ease, margin-left .18s ease; }
/* No easing while dragging, or the panel lags behind the pointer. */
body.resizing .sidebar, body.resizing .main { transition: none; }
.tab-text { margin-left: 2px; }

.sidebar-toggle {
  margin-left: auto;                 /* pushed to the brand block's right edge */
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  background: rgba(255,255,255,.08); color: rgba(255,255,255,.75);
  border: 1px solid rgba(255,255,255,.14); border-radius: 6px;
  font: inherit; cursor: pointer;
  transition: background .15s, color .15s;
}
.sidebar-toggle:hover { background: rgba(255,255,255,.18); color: #fff; }
.sidebar-toggle-icon { font-size: 10px; line-height: 1; }

/* ---- Collapsed rail ---- */
body.sidebar-collapsed { --sidebar-w: 62px; }
body.sidebar-collapsed .brand-words,
body.sidebar-collapsed .profile > div:not(.avatar),
body.sidebar-collapsed .tabs-label,
body.sidebar-collapsed .tab-text,
body.sidebar-collapsed .history,
body.sidebar-collapsed .sidebar-footer,

body.sidebar-collapsed .sidebar-resizer { display: none; }
body.sidebar-collapsed .brand {
  flex-direction: column; align-items: center; gap: 9px; padding: 12px 0;
}
body.sidebar-collapsed .sidebar-toggle { margin-left: 0; }
body.sidebar-collapsed .profile { justify-content: center; padding: 12px 0; }
body.sidebar-collapsed .tab { justify-content: center; padding: 12px 0; }
body.sidebar-collapsed .tab .tab-icon { margin: 0; font-size: 17px; }

/* ============================================================
   PENDING GENERATION ITEM
   ------------------------------------------------------------
   Greyed back so it reads as "not ready yet" against the finished items
   above it, with a progress bar and a countdown.
   ============================================================ */
.history-item.pending {
  opacity: .62;
  cursor: default;
  background: rgba(255,255,255,.05);
}
.history-item.pending:hover { background: rgba(255,255,255,.05); }
.history-item.pending .h-menu-btn { display: none; }

.pending-track {
  height: 3px; margin: 7px 0 5px;
  background: rgba(255,255,255,.16); border-radius: 2px; overflow: hidden;
}
.pending-fill {
  height: 100%; width: 0;
  background: var(--gold-500);
  border-radius: 2px;
  transition: width .25s linear;
}
.pending-meta { font-variant-numeric: tabular-nums; }

.history-item.pending-done { opacity: .85; }
.history-item.pending-done .pending-fill { background: #4ade80; }
.history-item.pending-failed .pending-fill { background: var(--red-600); }
.history-item.pending-failed .pending-meta { color: #ffb4b4; }

/* ============================================================
   GENERATE BUTTON
   ------------------------------------------------------------
   The main action of the whole page, so it is centred and given real
   presence. On submit it collapses into a spinning ring, then leaves as a
   ball thrown to the history list — the shape change is unmistakable at a
   glance, and it removes the target so a second click is impossible while
   the first is still running.

   THE RING IS THE BUTTON. It is drawn by the button's own box — a conic
   gradient with the middle punched out by a mask — not by a spinner element
   placed inside it. There is nothing to centre against anything: the ring is
   concentric with the button by construction, at any size. An earlier version
   used an inset pseudo-element and spent its life half a pixel out.
   ============================================================ */
button.generate-btn {
  /* ---- THE DIALS ----------------------------------------------------
     --btn-size        diameter once it collapses to a circle
     --ring-thickness  stroke width of the loading ring
     --ring-rgb        the ring's blue, as raw channels so the gradient can
                       fade it out by alpha from one source of truth.
                       This is --navy-600 (#22308f), the lighter half of the
                       button's own gradient, so the loader is the button's
                       colour rather than a second blue from nowhere.
     --btn-shadow      resting drop shadow (dropped while it spins)
     -------------------------------------------------------------------- */
  --btn-size: 60px;
  --ring-thickness: calc(var(--btn-size) * .22);
  --ring-rgb: 34, 48, 143;
  --btn-shadow: 0 6px 18px rgba(22,34,122,.28);

  display: block;
  margin: 26px auto 0;
  min-width: 260px;
  height: var(--btn-size);
  padding: 0 40px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: .2px;
  border-radius: calc(var(--btn-size) / 2);
  background: linear-gradient(135deg, var(--navy-700) 0%, var(--navy-600) 100%);
  box-shadow: var(--btn-shadow);
  position: relative;
  overflow: hidden;
  /* Quick, and the squeeze happens OUT OF SIGHT. A pill narrowing to a circle
     in full view reads as a rubber band being pulled in — the shape only
     convinces at either end, never in between.
     The order matters as much as the speed: the fade runs first and the shape
     change is DELAYED behind it by exactly as long as the fade takes, so the
     button is already invisible before it starts to move. Shortening the fade
     without shortening that delay puts the squeeze back on screen. */
  --shape-tr: .12s cubic-bezier(.4,0,.2,1) .07s;
  --fade-tr: .2s linear;            /* the way back in; .is-loading overrides */
  transition: min-width var(--shape-tr),
              width     var(--shape-tr),
              padding   var(--shape-tr),
              opacity   var(--fade-tr),
              box-shadow .2s, transform .12s;
}
button.generate-btn:hover:not(:disabled) {
  box-shadow: 0 9px 24px rgba(22,34,122,.36);
  transform: translateY(-1px);
}
button.generate-btn:active:not(:disabled) { transform: translateY(0); }

/* Keyboard focus must stay obvious on a rounded, gradient button. */
button.generate-btn:focus-visible {
  outline: 3px solid var(--gold-500);
  outline-offset: 3px;
}

button.generate-btn .btn-label {
  display: inline-block;
  white-space: nowrap;
  transition: opacity .1s linear;
}

/* ---- Loading, phase 1: collapse to a circle, out of sight ----
   The button fades to nothing as it squeezes, so the awkward in-between
   shapes are never actually seen — only the pill it left and the ring it
   arrives as. Phase 2 brings the opacity back. */
button.generate-btn.is-loading {
  /* Width tied to the same variable as height, so it can only ever be a
     true circle — the two cannot be edited out of step. */
  min-width: var(--btn-size);
  width: var(--btn-size);
  padding: 0;
  cursor: progress;
  opacity: 0;
  --fade-tr: .07s linear;           /* out fast, ahead of the shape's delay */
  border-radius: 50%;
  /* No shadow: the mask in phase 2 would clip it anyway, and its 6px
     downward offset is what made the old circle read as sitting low. */
  box-shadow: none;
}

/* ---- Loading, phase 2: hollow it out and spin ----
   Held back by app.html until the collapse has finished. Applied any sooner
   the box is still a wide pill, so the mask is an ELLIPSE and the ring comes
   out flat and squashed before snapping round — which is what "the icon
   looks flat at the start" was. Nothing here fires until it is a circle. */
button.generate-btn.is-spinning {
  /* Back into view as the ring — the second half of the cross-fade that
     phase 1's opacity: 0 started. Slower than the fade out, so the ring
     arrives rather than snapping on. Later in the file than .is-loading, and
     the same specificity, so this --fade-tr is the one that applies. */
  opacity: 1;
  --fade-tr: .16s linear;

  /* Full-strength at the head, gone by the time it comes round the right,
     building back up the left side — so the head of the arc is the solid
     end and the tail dissolves behind it. */
  background: conic-gradient(from 0deg,
      rgb(var(--ring-rgb))           0deg,
      rgba(var(--ring-rgb), 0)       6deg,   /* arc ends here; the cap rounds it */
      rgba(var(--ring-rgb), 0)      62deg,
      rgba(var(--ring-rgb), .14)   104deg,
      rgba(var(--ring-rgb), .48)   186deg,
      rgba(var(--ring-rgb), .82)   272deg,
      rgb(var(--ring-rgb))         360deg);

  /* Punch out the middle. `circle` is explicit rather than the default
     ellipse, and closest-side measures to the nearest edge — so even if this
     ever lands on a box that is not square, the hole stays round. */
  -webkit-mask: radial-gradient(circle closest-side,
      transparent calc(100% - var(--ring-thickness) - .5px),
      #000        calc(100% - var(--ring-thickness) + .5px));
  mask: radial-gradient(circle closest-side,
      transparent calc(100% - var(--ring-thickness) - .5px),
      #000        calc(100% - var(--ring-thickness) + .5px));

  animation: spin .85s linear infinite;
}

/* The rounded head of the arc. Sat at the top of the ring band — its centre
   lands exactly on the band's midline because the offsets are all derived
   from --ring-thickness, and it turns with the button rather than on its own. */
button.generate-btn.is-spinning::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--ring-thickness);
  height: var(--ring-thickness);
  margin-left: calc(var(--ring-thickness) / -2);
  border-radius: 50%;
  background: rgb(var(--ring-rgb));
}

/* ---- Leaving: the output-type badge, thrown to the history list ----
   The ring becomes the very badge the finished item will wear — DOCX in
   amber, HTML in teal — so what lands in the sidebar is recognisably the
   thing that left the form, not an abstract dot.

   The BOX stays 60x60 and only turns transparent; the badge is drawn by
   ::after inside it. Resizing the box to badge proportions would shorten the
   card by 30-odd pixels mid-flight and jolt everything below it.

   Position and scale come from the Web Animations API in app.html, which
   measures both ends at the moment it runs — the sidebar can be resized or
   collapsed, and the button moves with the length of the form above it. */
button.generate-btn.is-flying {
  min-width: var(--btn-size);
  width: var(--btn-size);
  padding: 0;
  opacity: 1;
  background: transparent;
  box-shadow: none;
  overflow: visible;                /* the badge is wider than it is tall */
  -webkit-mask: none;
  mask: none;
  animation: none;
  transition: none;                 /* the flight owns the transform outright */
  pointer-events: none;
  z-index: 60;                      /* over the sidebar it is heading for */
}

/* The badge itself. Its own translate centres it in the box; the flight's
   transform is on the BUTTON, so the two never contend for the property. */
button.generate-btn.is-flying::after {
  content: attr(data-badge);
  position: absolute;
  top: 50%; left: 50%;
  padding: 4px 8px;
  border-radius: 4px;
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .5px;
  line-height: 1;
  white-space: nowrap;
  box-shadow: 0 4px 12px rgba(10, 18, 64, .32);
  animation: badge-pop .2s cubic-bezier(.3, 1.35, .5, 1) both;
}
button.generate-btn.is-flying.badge-html::after { background: var(--type-html); }
button.generate-btn.is-flying.badge-docx::after { background: var(--type-docx); }
button.generate-btn.is-flying.badge-infographic::after { background: var(--type-graphic); }

/* Grows out of the middle of the ring it replaces, so the swap is a change
   of form rather than one shape being deleted and another drawn. */
@keyframes badge-pop {
  from { transform: translate(-50%, -50%) scale(.45); opacity: 0; }
  to   { transform: translate(-50%, -50%) scale(1);   opacity: 1; }
}

/* One frame, held by app.html between the badge leaving and the pill coming
   back: full width again but still invisible, and with no transition to
   animate the resize. Dropping it fades the finished button in at its proper
   size, so the widening is never watched any more than the squeeze was. */
button.generate-btn.is-returning { opacity: 0; transition: none; }

/* visibility as well as opacity: a transparent label still occupies the
   button and can be clipped at the rounded edge. Hidden from phase 1, so it
   is gone before the button is narrow enough to clip it. */
button.generate-btn.is-loading .btn-label,
button.generate-btn.is-flying .btn-label { opacity: 0; visibility: hidden; }

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

/* ============================================================
   PREVIEW OVERLAY
   ------------------------------------------------------------
   A finished material opens here, over the page rather than beside it. The
   generated webpage runs LIVE in the frame — scrollable, playable — so the
   panel is given most of the viewport; a preview too small to try is not a
   preview.
   ============================================================ */
.preview-dialog {
  width: min(1120px, 94vw);
  height: min(880px, 92vh);
  max-width: none; max-height: none;
  padding: 0;
  border: none;
  border-radius: 14px;
  background: var(--surface);
  color: var(--text);
  overflow: hidden;                 /* keeps the frame inside the rounding */
  box-shadow: 0 30px 80px rgba(10, 18, 64, .42);
  flex-direction: column;
}
/* display belongs on [open] only: a <dialog> is hidden by display:none, and
   any display on the base rule would override that and pin it on screen. */
.preview-dialog[open] {
  display: flex;
  animation: preview-in .2s cubic-bezier(.2,.8,.35,1);
}
@keyframes preview-in {
  from { opacity: 0; transform: scale(.985) translateY(8px); }
  to   { opacity: 1; transform: none; }
}
/* The page behind is blurred, not just dimmed — the request was for the main
   page to fall back rather than merely darken. */
.preview-dialog::backdrop {
  background: rgba(10, 18, 64, .5);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}
/* Nothing behind should scroll while the overlay owns the screen. */
body.preview-open { overflow: hidden; }

/* ---- Share link dialog --------------------------------------------------
   The preview's small sibling. It holds one URL and the button that copies
   it, so it is sized to the sentence rather than to the screen.
   -------------------------------------------------------------------------- */
.link-dialog {
  width: min(520px, 92vw);
  max-width: none;
  padding: 0 20px 20px;
  border: none; border-radius: 14px;
  background: var(--surface); color: var(--text);
  box-shadow: 0 30px 80px rgb(10 18 64 / .42);
}
.link-dialog[open] { animation: preview-in .18s cubic-bezier(.2,.8,.35,1); }
.link-dialog::backdrop {
  background: rgb(10 18 64 / .5);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}
.link-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 0 10px;
}
.link-head h3 {
  margin: 0; flex: 1; min-width: 0; font-size: 15px; color: var(--navy);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.link-note { margin: 0 0 12px; font-size: 12px; color: var(--muted); }

.link-row { display: flex; gap: 8px; }
.link-row input {
  flex: 1; min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text);
}
.link-copy {
  flex: none; padding: 10px 18px;
  border: none; border-radius: 8px;
  background: var(--navy-700); color: #fff;
  font-family: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: background .15s ease;
}
.link-copy:hover { background: var(--navy-600); }
/* Confirmation lives in the button itself — a toast would appear away from
   where the teacher is looking. */
.link-copy.is-done { background: var(--type-html); }

.link-actions {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 14px;
}
.link-actions button {
  border: none; background: none; padding: 4px 2px;
  font-family: inherit; font-size: 13px; cursor: pointer;
}
.link-open { color: var(--navy-700); font-weight: 600; }
.link-open:hover { text-decoration: underline; }
.link-unpublish { color: var(--red-600); }
.link-unpublish:hover { text-decoration: underline; }

.preview-head {
  flex: none;
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px 14px 20px;
  border-bottom: 1px solid var(--border);
  background: #fafbfe;
}
.preview-titles { flex: 1; min-width: 0; }
.preview-titles h3 {
  margin: 0; font-size: 16px; color: var(--navy);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.preview-sub {
  margin: 2px 0 0; font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The badge here is the history list's, which is drawn for a dark sidebar. */
.preview-head .badge.h-type { color: #fff; flex: none; }

button.preview-download {
  flex: none;
  border: none; cursor: pointer; font-family: inherit;
  padding: 9px 18px; border-radius: 7px;
  background: var(--navy); color: #fff;
  font-size: 13.5px; font-weight: 700;
}
button.preview-download:hover { background: var(--navy-600); }

button.preview-close {
  flex: none;
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  padding: 0; border: none; border-radius: 8px;
  background: transparent; color: var(--muted);
  font-family: inherit; font-size: 26px; line-height: 1;
  cursor: pointer;
  transition: background .15s, color .15s;
}
button.preview-close:hover { background: var(--red-50); color: var(--red-600); }
button.preview-download:focus-visible,
button.preview-close:focus-visible {
  outline: 3px solid var(--gold-500); outline-offset: 2px;
}

/* min-height:0 is what lets the frame shrink to the panel instead of pushing
   the header off the top of it. */
.preview-body { flex: 1 1 auto; min-height: 0; background: var(--bg); }
.preview-frame { width: 100%; height: 100%; border: none; background: #fff; }

/* An infographic is shown as the picture it is. `contain` rather than `cover`:
   this is the whole poster being checked before it goes to a class, so nothing
   may be cropped, whatever aspect ratio was chosen. The panel is the neutral
   grey a photo viewer uses, so a white poster still has an edge. */
.preview-image {
  display: block; width: 100%; height: 100%;
  object-fit: contain; background: #e9edf5; padding: 18px;
}

.preview-body.is-document {
  display: flex; align-items: center; justify-content: center;
  padding: 32px;
}
.preview-notice { max-width: 440px; text-align: center; }
.preview-notice-head {
  margin: 0 0 8px; font-size: 16px; font-weight: 700; color: var(--navy);
}
/* Named rather than matched by position: a :last-child rule here beat the
   note's own class on specificity and flattened its spacing to zero. */
.preview-notice-body,
.preview-notice-note {
  margin: 0; font-size: 13.5px; color: var(--muted); line-height: 1.6;
}
.preview-notice-note { margin-top: 18px; }

/* A second download, under the sentence that asks for one. Larger than the
   header's copy because here it is the only thing on the panel. */
button.preview-download-big {
  border: none; cursor: pointer; font-family: inherit;
  padding: 14px 34px; border-radius: 9px;
  background: var(--navy); color: #fff;
  font-size: 15.5px; font-weight: 700; letter-spacing: .2px;
  box-shadow: 0 6px 16px rgba(22, 34, 122, .26);
  transition: background .15s, transform .12s, box-shadow .15s;
}
button.preview-download-big:hover {
  background: var(--navy-600);
  transform: translateY(-1px);
  box-shadow: 0 9px 20px rgba(22, 34, 122, .32);
}
button.preview-download-big:active { transform: translateY(0); }
button.preview-download-big:focus-visible {
  outline: 3px solid var(--gold-500); outline-offset: 3px;
}

@media (max-width: 820px) {
  .preview-dialog { width: 96vw; height: 92vh; }
  .preview-head { padding: 12px; gap: 8px; }
  .preview-titles h3 { font-size: 14px; }
}

/* ============================================================
   TOPIC PICKER (Quiz mode)
   ------------------------------------------------------------
   A dropdown that ADDS, and chips that remove. The dropdown only ever offers
   topics not already chosen, so the same one cannot be added twice and the
   list shortens as it is used.
   ============================================================ */
.topic-picker { display: flex; gap: 8px; align-items: stretch; }
.topic-picker select { flex: 1; min-width: 0; }

/* ---- Material depth meter ------------------------------------------------
   How much indexed material the highlighted topic holds, as five segments
   against what one generation actually consumes. Deliberately wordless: a
   native <select> cannot carry markup inside an <option>, so this sits under
   the picker instead and speaks in shape rather than in a number nobody
   outside the project would be able to interpret.
   -------------------------------------------------------------------------- */
.mat-meter {
  --seg-w: 26px;
  --seg-h: 13px;
  display: flex;
  gap: 4px;
  /* Sits in the picker row beside the dropdown. Centred rather than stretched,
     because .topic-picker aligns its children to stretch for the select and
     the Add button and would otherwise pull the segments tall. */
  align-self: center;
  flex-shrink: 0;
}
.mat-meter[hidden] { display: none; }

.mat-meter i {
  width: var(--seg-w);
  height: var(--seg-h);
  border-radius: 2px;
  /* The lean is what reads as a game HUD rather than a progress bar. */
  transform: skewX(-20deg);
  background: var(--navy-100);
  box-shadow: inset 0 1px 2px rgb(10 18 64 / .18);
  transition: background .22s ease, box-shadow .22s ease;
}

.mat-meter i.is-on {
  background: linear-gradient(180deg, var(--navy-500), var(--navy-700));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .38),
              0 0 7px rgb(34 48 143 / .45);
  /* Segments light left to right, so a change reads as filling up. */
  animation: seg-in .26s cubic-bezier(.2, .8, .3, 1) backwards;
  animation-delay: calc(var(--i) * 55ms);
}

/* A topic holding everything a generation can use maxes out in gold — the
   accent is reserved for this one state so "full" is legible at a glance. */
.mat-meter[data-level="5"] i.is-on {
  background: linear-gradient(180deg, var(--gold-500), var(--gold-600));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5),
              0 0 9px rgb(245 179 1 / .6);
}

@keyframes seg-in {
  from { opacity: 0; transform: skewX(-20deg) scaleX(.3); }
  to   { opacity: 1; transform: skewX(-20deg) scaleX(1); }
}

@media (prefers-reduced-motion: reduce) {
  .mat-meter i.is-on { animation: none; }
}

button.topic-add {
  flex: none;
  padding: 0 16px;
  border: 1px solid var(--navy-500);
  border-radius: 6px;
  background: var(--navy-100);
  color: var(--navy-700);
  font-family: inherit; font-size: 13px; font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, opacity .15s;
}
button.topic-add:hover:not(:disabled) { background: #dae1f4; }
button.topic-add:disabled {
  opacity: .5; cursor: not-allowed;
  border-color: var(--border); background: var(--bg); color: var(--muted);
}

/* Encloses the chosen topics so they read as one group. Tighter than the
   general .fieldset, which is sized for whole sections of the form rather
   than for a single row of labels. */
.topic-selected {
  margin-top: 12px;
  padding: 2px 14px 14px;
}
.topic-selected > .legend { margin-top: 11px; }

.topic-chips {
  list-style: none;
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 9px 0 0; padding: 0;
}

/* Sized to sit alongside the Add button rather than under it as an
   afterthought: these are the actual content of the paper, and the dropdown
   is only the way in. */
.topic-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 7px 7px 7px 14px;
  border: 1px solid var(--navy-100);
  border-radius: 18px;
  background: var(--navy-100);
  color: var(--navy-700);
  font-size: 13.5px; font-weight: 600;
  transition: background .15s, border-color .15s;
}
.topic-chip:hover { background: #dae1f4; border-color: var(--navy-500); }

button.topic-chip-x {
  display: flex; align-items: center; justify-content: center;
  width: 21px; height: 21px;
  padding: 0; border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--navy-700);
  font-family: inherit; font-size: 17px; line-height: 1;
  cursor: pointer;
  transition: background .15s, color .15s;
}
/* Red only on the button itself, so hovering the chip does not make the whole
   thing look like it is about to be deleted. */
button.topic-chip-x:hover { background: var(--red-600); color: #fff; }
button.topic-chip-x:focus-visible {
  outline: 2px solid var(--navy-500); outline-offset: 1px;
}

.topic-empty {
  color: var(--muted); font-size: 13px; font-style: italic;
  padding: 7px 0;
}

/* Sits under the chips, so it needs clearing room from them rather than the
   4px a hint gets when it follows a field. */
.topic-note { font-style: italic; margin-top: 9px; }
/* The "Hints:" label stays upright — italic throughout and it stops reading
   as a label and becomes part of the sentence. */
.topic-note b { font-style: normal; color: var(--navy-700); }

/* ============================================================
   CONFIRM DIALOG
   ------------------------------------------------------------
   Shown between the click and the request. It is the last point at which a
   wrong Form or a stale topic is free to fix, so it restates the request in
   full rather than asking a bare "are you sure?".
   ============================================================ */
.confirm-dialog {
  width: min(540px, calc(100vw - 32px));
  /* A paper using all six question types is taller than a laptop screen, so
     the panel is capped and the SUMMARY scrolls inside it — the heading and
     the two buttons must never be the part that goes off-screen. */
  max-height: min(88vh, 760px);
  flex-direction: column;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 24px 64px rgba(10, 18, 64, .3);
}
.confirm-dialog::backdrop { background: rgba(10, 18, 64, .45); }
/* display belongs on [open] only: a <dialog> is hidden by `display: none`,
   which any display value set on the base rule would override — leaving the
   panel on screen permanently. */
.confirm-dialog[open] {
  display: flex;
  animation: confirm-in .18s cubic-bezier(.2,.8,.35,1);
}
@keyframes confirm-in {
  from { opacity: 0; transform: translateY(-10px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

.confirm-head { flex: none; padding: 20px 22px 0; }
.confirm-head h3 { margin: 0 0 5px; color: var(--navy); font-size: 18px; }
.confirm-sub { margin: 0; color: var(--muted); font-size: 13px; }

/* Two columns so the values line up in a scannable rail. min-height:0 is what
   lets this shrink below its content and actually scroll inside the flex
   column, rather than pushing the buttons out of the panel. */
.confirm-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: grid;
  grid-template-columns: 118px 1fr;
  align-content: start;
  gap: 9px 16px;
  margin: 0;
  padding: 16px 22px;
  font-size: 13px;
}
.confirm-list dt { color: var(--muted); font-weight: 600; }
.confirm-list dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.confirm-list dd ul { margin: 0; padding-left: 17px; font-weight: 500; }
.confirm-list dd ul li { margin-bottom: 2px; }
/* Free text the teacher typed, set apart from the values the form chose. */
.confirm-list dd.quote {
  font-weight: 400; font-style: italic; color: var(--muted);
  white-space: pre-wrap;
}

/* Pinned below the scroll area, full width: the border is what tells you the
   summary above it ends there rather than simply running out. */
.confirm-note {
  flex: none;
  margin: 0;
  padding: 11px 22px;
  background: var(--gold-100);
  border-top: 1px solid var(--gold-500);
  font-size: 12.5px;
  color: #6b4e00;
}

.confirm-actions {
  flex: none;
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 15px 22px;
  border-top: 1px solid var(--border);
  background: #fafbfe;
  border-radius: 0 0 11px 11px;
}
/* Deliberately not .primary/.small: those carry their own margins meant for
   a form, and inheriting them here only invites a specificity fight. */
button.confirm-btn {
  border: none; cursor: pointer; font-family: inherit;
  padding: 10px 20px; border-radius: 7px;
  font-size: 14px; font-weight: 700;
}
button.confirm-btn.ghost { background: var(--navy-100); color: var(--navy-700); }
button.confirm-btn.ghost:hover { background: #dae1f4; }
button.confirm-btn.go { background: var(--navy); color: #fff; }
button.confirm-btn.go:hover { background: var(--navy-600); }
button.confirm-btn:focus-visible { outline: 3px solid var(--gold-500); outline-offset: 2px; }

/* The new job arriving in the sidebar: a short slide in and a gold tint that
   settles, so the eye is taken to where the request has gone. */
.history-item.pending.arriving { animation: pending-arrive .55s cubic-bezier(.2,.8,.35,1); }
@keyframes pending-arrive {
  0%   { opacity: 0;   transform: translateX(22px); background: rgba(245,179,1,.30); }
  55%  { opacity: .62; background: rgba(245,179,1,.17); }
  100% { opacity: .62; transform: none; background: rgba(255,255,255,.05); }
}

/* A plain fade for reduced motion — nothing travels across the screen. */
@keyframes pending-arrive-plain { from { opacity: 0; } to { opacity: .62; } }

/* Reduced motion: keep the shape change, slow the spin right down rather
   than removing the only sign that work is in progress. The flight is
   skipped outright — app.html checks the same query. */
@media (prefers-reduced-motion: reduce) {
  button.generate-btn { transition: none; }
  button.generate-btn.is-spinning { animation-duration: 2.6s; }
  .pending-fill { transition: none; }
  .history-item.pending.arriving { animation-name: pending-arrive-plain; }
  .confirm-dialog[open] { animation: none; }
}

/* Responsive: collapse sidebar on narrow screens */
@media (max-width: 820px) {
  .sidebar { position: static; width: 100%; height: auto; }
  .layout { flex-direction: column; }
  .main { margin-left: 0; padding: 20px; }
  .history { max-height: 220px; }

  /* The sidebar is already a stacked block here, so there is nothing to
     collapse and no vertical edge to drag. Both controls would be dead
     weight, and the collapse button would stretch the full width. */
  .sidebar-resizer { display: none; }

  /* A width saved on a desktop must not leak into the stacked layout. */
  body, body.sidebar-collapsed { --sidebar-w: 100%; }
  body.sidebar-collapsed .brand-words,
  body.sidebar-collapsed .profile > div:not(.avatar),
  body.sidebar-collapsed .tabs-label,
  body.sidebar-collapsed .tab-text,
  body.sidebar-collapsed .history,
  body.sidebar-collapsed .sidebar-footer { display: revert; }
}

/* Daily allowance indicator in the sidebar profile block */
.profile-status.quota-out { color: #ffb4b4; font-weight: 700; opacity: 1; }


/* ---- Page editor --------------------------------------------------------
   The preview's working sibling: same overlay shape, but its body is a form
   rather than an iframe. A teacher edits the CONTENT and the school re-renders
   the PAGE, so nothing in here can reach the layout, the branding or the quiz
   engine — the form only ever collects words.
   -------------------------------------------------------------------------- */
.edit-dialog {
  width: min(880px, 94vw);
  height: min(880px, 92vh);
  max-width: none; max-height: none;
  padding: 0; border: none; border-radius: 14px;
  background: var(--surface); color: var(--text);
  overflow: hidden;
  box-shadow: 0 30px 80px rgba(10, 18, 64, .42);
  flex-direction: column;
}
.edit-dialog[open] { display: flex; animation: preview-in .2s cubic-bezier(.2,.8,.35,1); }
.edit-dialog::backdrop {
  background: rgba(10, 18, 64, .5);
  backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px);
}
.edit-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 18px 22px 28px; background: var(--bg);
}
/* Says what happened to the last save, next to the button that caused it. */
.edit-status { flex: none; font-size: 12.5px; font-weight: 700; color: var(--muted); }
.edit-status.ok { color: var(--green-700, #0f7a63); }
.edit-status.bad { color: var(--red-600); }

/* One block per part of the page — Cards, Quiz, and so on. */
.ed-sec { margin-bottom: 22px; }
.ed-sec > h4 {
  margin: 0 0 4px; font-size: 12px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--muted);
}
.ed-sec > .ed-note { margin: 0 0 10px; font-size: 12.5px; color: var(--muted); }

.ed-field { margin-bottom: 10px; }
.ed-field > label {
  display: block; margin-bottom: 3px;
  font-size: 12px; font-weight: 700; color: var(--navy);
}
.ed-field input, .ed-field textarea {
  width: 100%; box-sizing: border-box;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 7px;
  font-family: inherit; font-size: 13.5px; color: var(--text); background: #fff;
}
.ed-field textarea { resize: vertical; min-height: 62px; }
.ed-field input:focus, .ed-field textarea:focus {
  outline: none; border-color: var(--gold-500);
  box-shadow: 0 0 0 3px rgba(245, 179, 1, .18);
}
/* A row of short fields — word, part of speech, Chinese — side by side. */
.ed-row { display: flex; gap: 10px; flex-wrap: wrap; }
.ed-row > .ed-field { flex: 1 1 150px; margin-bottom: 10px; }

/* A repeated thing: one card, one quiz question, one game item. Collapsed by
   default because a vocabulary page carries twenty of them and an open form
   twenty cards long is not a form anyone can find their way around. */
.ed-item {
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); margin-bottom: 8px;
}
.ed-item > summary {
  cursor: pointer; padding: 10px 12px; list-style: none;
  display: flex; align-items: center; gap: 8px;
  font-size: 13.5px; font-weight: 700; color: var(--navy);
}
.ed-item > summary::-webkit-details-marker { display: none; }
.ed-item > summary::before { content: "▸"; color: var(--muted); font-weight: 400; }
.ed-item[open] > summary::before { content: "▾"; }
.ed-item > summary .ed-sum-sub {
  margin-left: auto; font-weight: 500; font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%;
}
.ed-item-body { padding: 0 12px 12px; border-top: 1px solid var(--border); padding-top: 12px; }

.ed-btn {
  border: 1px solid var(--border); background: #fff; color: var(--navy);
  font-family: inherit; font-size: 12.5px; font-weight: 700;
  padding: 7px 12px; border-radius: 7px; cursor: pointer;
}
.ed-btn:hover { border-color: var(--navy); background: var(--navy-100); }
.ed-btn.danger { color: var(--red-600); }
.ed-btn.danger:hover { border-color: var(--red-600); background: var(--red-50); }

/* One line of a list — an objective, a bullet, a quiz option. */
.ed-line { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }
.ed-line input { flex: 1 1 auto; min-width: 0; }
.ed-line .ed-x {
  flex: none; width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--border); border-radius: 7px; background: #fff;
  color: var(--muted); cursor: pointer; font-size: 15px; line-height: 1;
}
.ed-line .ed-x:hover { border-color: var(--red-600); color: var(--red-600); background: var(--red-50); }
/* The radio that marks the right answer. Sized as a target, not a dot. */
.ed-line .ed-correct {
  flex: none; display: flex; align-items: center; gap: 5px;
  font-size: 11.5px; font-weight: 700; color: var(--muted); cursor: pointer;
  white-space: nowrap;
}
.ed-line input[type="radio"] { width: 17px; height: 17px; flex: none; accent-color: var(--green-700, #0f7a63); }
.ed-line.is-correct input[type="text"] { border-color: var(--green-700, #0f7a63); background: #f2fbf8; }
.ed-line.is-correct .ed-correct { color: var(--green-700, #0f7a63); }

/* Shown instead of the form when a page predates the editor. */
.ed-empty { max-width: 460px; margin: 40px auto; text-align: center; color: var(--muted); font-size: 14px; }
.ed-empty b { display: block; margin-bottom: 6px; color: var(--navy); font-size: 16px; }

/* A menu inside the editor form, styled to match its text inputs. */
.ed-select {
  width: 100%; box-sizing: border-box;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 7px;
  font-family: inherit; font-size: 13.5px; color: var(--text); background: #fff;
}
.ed-select:focus {
  outline: none; border-color: var(--gold-500);
  box-shadow: 0 0 0 3px rgba(245, 179, 1, .18);
}
.ed-btn:disabled { opacity: .5; cursor: not-allowed; }
.ed-btn:disabled:hover { border-color: var(--border); background: #fff; }

/* ============================================================
   VISUAL OPTION PICKERS  (infographic form)
   ------------------------------------------------------------
   A dropdown reading "3:4" or "Concept explainer" tells a teacher the name of
   a thing they have never seen. These grids put a small drawing against every
   option, so the shape of the paper and the shape of the layout are decided by
   looking rather than by guessing.

   THE <select> IS STILL THE SOURCE OF TRUTH. Each grid is generated from its
   select's own options and writes back to it, so the form submission, the
   confirm dialog, the orientation/ratio dependency and resetForm() all keep
   working through the controls they already knew about — there is no second
   copy of the state to fall out of step.
   ============================================================ */
.choice-hidden { display: none; }

.choice-grid {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 2px 0 4px;
}

.choice {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  min-width: 78px; padding: 9px 9px 7px;
  border: 1.5px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--muted);
  font: inherit; font-size: 11.5px; line-height: 1.25; text-align: center;
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s, box-shadow .12s;
}
.choice:hover { border-color: var(--navy-500); color: var(--navy-700); }
.choice:focus-visible {
  outline: 2px solid var(--navy-500); outline-offset: 2px;
}
.choice.is-on {
  border-color: var(--navy-700); color: var(--navy-700);
  background: var(--navy-100);
  box-shadow: inset 0 0 0 1px var(--navy-700);
  font-weight: 600;
}

/* currentColor throughout the artwork, so a tile's drawing picks up the
   selected state for free. */
.choice svg { display: block; width: 46px; height: 34px; }
.choice-label { display: block; max-width: 92px; }

/* The ratio tiles are read as shapes, so they are given room to BE shapes and
   the label sits under a constant-height box — otherwise a 21:9 tile is half
   the height of a 3:4 one and the row looks broken. */
.choice-grid.ratios .choice { min-width: 68px; }
.choice-grid.ratios svg { height: 40px; }

/* ============================================================
   "YOU MAY INCLUDE…"  — content blocks with drawn samples
   ------------------------------------------------------------
   "Worked example" and "Memory hook" are names for things a teacher cannot
   picture, so every card carries a small drawing of what that block looks like
   once it is on the poster, and hovering (or tabbing to) a card enlarges it.

   The small sample is ALWAYS visible rather than hover-only: these forms are
   used on iPads, where hover does not exist and a hover-only preview would
   simply never appear.
   ============================================================ */
.block-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 8px; margin-top: 8px;
}

.block-card {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 9px 8px 8px;
  border: 1.5px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--muted);
  font: inherit; font-size: 11.5px; line-height: 1.25; text-align: center;
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
.block-card:hover { border-color: var(--navy-500); color: var(--navy-700); }
.block-card:focus-visible { outline: 2px solid var(--navy-500); outline-offset: 2px; }
.block-card.is-on {
  border-color: var(--navy-700); color: var(--navy-700);
  background: var(--navy-100); box-shadow: inset 0 0 0 1px var(--navy-700);
  font-weight: 600;
}
/* At the cap: still readable, visibly not available.
   The fade is applied to the card's CONTENTS, never to the card. `opacity` on
   the card creates a stacking context that its hover panel is inside, so a
   muted card showed a 42%-transparent preview with the grid visible through
   it. */
.block-card.is-muted { cursor: not-allowed; }
.block-card.is-muted .block-art,
.block-card.is-muted .block-label { opacity: .40; }
.block-card.is-muted:hover { border-color: var(--border); color: var(--muted); }

.block-art { display: block; width: 100%; }
.block-art svg { display: block; width: 100%; height: 40px; }
.block-label { display: block; }

/* A tick in the corner of a chosen card, so the state survives being read at
   a glance rather than depending on the tint alone. */
.block-card.is-on::after {
  content: "✓";
  position: absolute; top: 5px; right: 7px;
  font-size: 11px; font-weight: 700; color: var(--navy-700);
}

/* The enlarged sample. Sits above the card, centred, and never intercepts the
   pointer — otherwise it would cover the card that summoned it and the click
   would land on the panel instead. */
.block-pop {
  position: absolute; bottom: calc(100% + 8px); left: 50%;
  transform: translateX(calc(-50% + var(--pop-shift, 0px))) translateY(4px);
  width: 208px; padding: 10px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 10px 28px rgba(10,18,64,.20);
  opacity: 0; visibility: hidden; pointer-events: none; z-index: 40;
  transition: opacity .13s, transform .13s, visibility .13s;
}
.block-card:hover .block-pop,
.block-card:focus-visible .block-pop {
  opacity: 1; visibility: visible;
  transform: translateX(calc(-50% + var(--pop-shift, 0px))) translateY(0);
}
.block-pop-art { display: block; color: var(--navy-700); }
.block-pop-art svg { display: block; width: 100%; height: 92px; }
.block-pop-text {
  display: block; margin-top: 8px;
  font-size: 12px; line-height: 1.45; color: var(--muted); text-align: left;
}
/* Cards with no room above open downwards instead. Which those are depends on
   how the grid wrapped and where the page is scrolled, so it is decided in JS
   (placePop) rather than guessed from the child index. */
.block-card.pop-below .block-pop {
  bottom: auto; top: calc(100% + 8px);
  transform: translateX(calc(-50% + var(--pop-shift, 0px))) translateY(-4px);
}
.block-card.pop-below:hover .block-pop,
.block-card.pop-below:focus-visible .block-pop {
  transform: translateX(calc(-50% + var(--pop-shift, 0px))) translateY(0);
}

/* A required field says so, rather than letting a teacher discover it after
   the confirm dialog and the hand-off animation have already played. */
.req {
  color: var(--red-600); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; margin-left: 4px;
}

/* The artistic-style samples are pictures rather than diagrams, so they get
   room to read as pictures and lose the padding that suits a line drawing. */
.block-grid.art-styles { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }
.block-grid.art-styles .block-art svg { height: 52px; border-radius: 5px; }
.block-grid.art-styles .block-pop-art svg { height: 104px; border-radius: 6px; }
