/* MCQ — shared styles. MC-branded navy header + energetic quiz accents. */
:root {
  /* ---------- App chrome (2026-08 re-skin) ----------
     Derived from the brand, but deliberately NOT the four answer colours.
     An answer colour used as a button would compete with the same colour
     used as a 1-of-4 tile, and the whole scheme rests on "this colour means
     this answer". Navy is the single overlap and it is safe: here it is
     only ever a surface (header, primary button), never a choice. */
  --navy:        #133061;
  --navy-2:      #1D4585;
  --accent:      #133061;   /* brand navy - links, focus, primary action */
  --accent-ink:  #FFFFFF;   /* 13.0:1 on --accent */
  --accent-soft: #E7EDF7;   /* tinted surface: active nav item, admin pill */
  --accent-2:    #0B7A52;   /* success only - stays green, it is semantic */
  --gold:        #FDCC42;
  --gold-ink:    #542715;   /* 8.6:1. White on this yellow is 1.6:1 */
  --bg:          #FDF8EC;   /* same cream ground the game uses */
  --card:        #ffffff;
  --border:      #EDE4D3;
  --border-strong:#DCD0B9;
  --ink:         #2B2119;
  --muted:       #7A6E5F;   /* 4.7:1 on --bg */
  --surface:     #F6EFE0;   /* hover rows, inset panels */
  --surface-2:   #F1E9DA;
  --danger:      #d64545;
  --r:           12px;
  --shadow:      0 2px 14px rgba(84,39,21,.10);
  --ff: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* ---------- MCQ brand (2026-08) ----------
     The four answer colours and their shapes. Colour and shape both identify
     an answer, so a player matching the projector to their phone has two
     independent cues. Label colour is per tile, never global: white on
     orange is 2.1:1 and on yellow 1.6:1, both unreadable.
     Shapes live in public/brand/ — see BRAND.md. */
  --a1:        #FD8B09;  --a1-ink: #542715;  /* flame  */
  --a2:        #133061;  --a2-ink: #FFFFFF;  /* eye    */
  --a3:        #ED2023;  --a3-ink: #FFFFFF;  /* wink   */
  --a4:        #FDCC42;  --a4-ink: #542715;  /* smile  */
  --maco-face: #FAC444;
  --maco-ink:  #542715;
  --quiz-bg:   #FDF8EC;
}

/* --a2 on a dark ground.
   Navy #133061 is 1.45:1 against a dark ground, so the tile stops reading as
   a tile at all; #3270BE is 3.76:1 on the ground and still holds white label
   text at 5.0:1. Orange, red and yellow all clear 4:1 on dark unchanged, so
   they must NOT be adjusted - changing them would break the match between the
   projector and a player's phone if the two devices are in different themes.

   Keyed to the SURFACE, not to prefers-color-scheme. These pages are the
   light app shell and stay light whatever the OS says; keying the swap to the
   OS put the pale blue tile on a cream page for anyone browsing in dark mode.
   The landing page is the one dark surface here, so it opts in by name. The
   game and /demo carry their own copy of this rule, where the swap really is
   theme-driven because those surfaces follow the OS. */
/* `--a2` 唔再覆蓋。嗰個淺藍(#3270BE)係因為張示範卡以前係深色底、真
   navy 喺上面睇唔見先加嘅;而家張卡係真投影幕嗰塊米白,navy 本來就係
   對嘅色 —— 覆蓋咗反而變咗「同真遊戲唔同色」。 */
body[data-page="home"] { --quiz-bg: #1B1713; }
* { box-sizing: border-box; }
input[type=checkbox], input[type=radio] { accent-color: var(--accent); }
html, body { margin: 0; }
body {
  font-family: var(--ff); color: var(--ink); background: var(--bg);
  min-height: 100vh; display: flex; flex-direction: column; line-height: 1.5;
}
a { color: var(--accent); }

/* ---------- Header ---------- */
.nav {
  display: flex; align-items: center; gap: 1rem;
  height: 60px; padding: 0 1.25rem;
  background: var(--navy); color: #fff;
  box-shadow: 0 2px 14px rgba(19,48,97,.28); position: sticky; top: 0; z-index: 50;
}
.nav-logo { display: flex; align-items: center; gap: .6rem; text-decoration: none; color: #fff; }
/* 冇 padding、冇底色 —— 個 logo 塊瓦本身鋪滿成格。之前有 2px 米白
   padding 造出一圈白邊,用戶唔要。冇咗 padding,`background` 亦都永遠遮
   住咗、留住冇意思,一齊拆。 */
.nav-mark {
  display: grid; place-items: center; flex: none;
  width: 36px; height: 36px; border-radius: 10px;
}
/* 加 `.nav-logo` 前綴唔係手多,係要贏返下面嗰條:`.nav-logo img` 同呢條同
   權重、又排喺後面,所以一直係佢話事,張圖被逼做 34px 而唔係鋪滿成格。
   圓角同 `.nav-mark` 一樣 10px —— 張圖就係最外層,細一級就會喺角位露方角。 */
.nav-logo .nav-mark img { width: 100%; height: 100%; display: block; border-radius: 10px; }
.nav-logo img { height: 34px; width: auto; display: block; }
.nav-logo .brand { font-weight: 800; font-size: 1.15rem; letter-spacing: .02em; }
.nav-logo .brand em { color: var(--gold); font-style: normal; }
.nav-spacer { flex: 1; }
#navLeft { display: flex; align-items: center; gap: .9rem; margin-left: 1.4rem; }
#navRight { display: flex; align-items: center; gap: .9rem; }
.nav-link { display: inline-flex; align-items: center; gap: .4rem; color: rgba(255,255,255,.85); text-decoration: none; font-size: .9rem; font-weight: 600; padding: .35rem .5rem; }
.nav-link i, .nav-link svg { width: 16px; height: 16px; }
.nav-link:hover { color: #fff; }
/* User badge → click-to-open dropdown (native <details>) */
.nav-avatar { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; border: 1.5px solid rgba(255,255,255,.3); flex-shrink: 0; }
.nav-avatar.fallback { display: grid; place-items: center; background: var(--accent); color: var(--accent-ink); font-weight: 700; font-size: .85rem; border: 0; }
.nav-user { position: relative; }
.nav-user > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: .5rem;
  padding: .25rem .55rem .25rem .3rem; border-radius: 999px; background: rgba(255,255,255,.08);
  transition: background .15s;
}
.nav-user > summary::-webkit-details-marker { display: none; }
.nav-user > summary:hover { background: rgba(255,255,255,.16); }
.nav-user .who { font-size: .85rem; color: rgba(255,255,255,.92); font-weight: 600; }
.nav-user .chev { color: rgba(255,255,255,.6); width: 15px; height: 15px; transition: transform .15s; }
.nav-user[open] .chev { transform: rotate(180deg); }
.nav-user-panel {
  position: absolute; right: 0; top: calc(100% + 8px); min-width: 220px;
  background: #fff; border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 14px 34px rgba(84,39,21,.24); padding: .4rem; z-index: 60; overflow: hidden;
}
.nav-user-head { display: flex; align-items: center; gap: .6rem; padding: .55rem .6rem .65rem; border-bottom: 1px solid var(--border); margin-bottom: .35rem; }
.nav-user-head .nm { font-weight: 700; color: var(--navy); font-size: .9rem; }
.nav-user-head .em { color: var(--muted); font-size: .78rem; }
.nav-user-head .nav-avatar { border: 0; }
.nav-user-panel a {
  display: flex; align-items: center; gap: .6rem; padding: .55rem .65rem; border-radius: 8px;
  text-decoration: none; color: var(--ink); font-size: .9rem; font-weight: 500;
}
.nav-user-panel a i, .nav-user-panel a svg { width: 16px; height: 16px; color: var(--muted); flex-shrink: 0; }
.nav-user-panel a:hover { background: var(--surface); }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .6rem 1.1rem; border-radius: 999px; border: 0; cursor: pointer;
  font-family: var(--ff); font-weight: 700; font-size: .9rem; text-decoration: none;
  transition: transform .06s ease, filter .15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { filter: brightness(1.08); }
.btn-gold { background: var(--gold); color: var(--gold-ink); }
.btn-ghost { background: rgba(255,255,255,.12); color: #fff; }
.btn-ghost:hover { background: rgba(255,255,255,.2); }
.btn-outline { background: #fff; color: var(--ink); border: 1px solid var(--border-strong); }
.btn-outline:hover { background: var(--surface); }
.btn-danger { background: #fdecec; color: var(--danger); }
.btn[disabled] { opacity: .55; cursor: default; }

/* ---------- Landing ---------- */
.hero {
  background: radial-gradient(1200px 500px at 15% -10%, rgba(108,92,231,.25), transparent),
              linear-gradient(160deg, var(--navy), var(--navy-2));
  color: #fff; padding: 4.5rem 1.25rem 5rem; text-align: center;
}
.hero h1 { font-size: clamp(2.1rem, 5vw, 3.4rem); margin: 0 0 1rem; font-weight: 900; letter-spacing: -.02em; text-wrap: balance; }
.hero h1 .pop { color: var(--gold); }
.hero p { font-size: clamp(1rem, 2.2vw, 1.25rem); color: rgba(255,255,255,.85); max-width: 620px; margin: 0 auto 2rem; }
.hero .cta-row { display: flex; gap: .75rem; justify-content: center; flex-wrap: wrap; }
.hero .btn { padding: .85rem 1.6rem; font-size: 1rem; }

.section { max-width: 1000px; margin: 0 auto; padding: 3.5rem 1.25rem; }
.section h2 { font-size: 1.7rem; text-align: center; margin: 0 0 2rem; color: var(--navy); }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.25rem; }
.card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r);
  padding: 1.5rem; box-shadow: var(--shadow);
}
.card-ico { width: 28px; height: 28px; color: var(--accent); }
.card h3 { margin: .6rem 0 .4rem; font-size: 1.1rem; color: var(--navy); }
.card p { margin: 0; color: var(--muted); font-size: .92rem; }

/* ---------- Page shell (account / admin) ---------- */
/* --page-pad-b is named because .editor-bar has to cancel it exactly; see
   there. Two literals that must stay equal would drift the first time someone
   adjusts the page's breathing room. */
.page { --page-pad-b: 2rem; flex: 1; max-width: 960px; width: 100%; margin: 0 auto; padding: 2rem 1.25rem var(--page-pad-b); }
.page-title { font-size: 1.6rem; color: var(--navy); margin: 0 0 .35rem; font-weight: 800; }
.page-sub { color: var(--muted); margin: 0 0 1.75rem; }
.panel { background: var(--card); border: 1px solid var(--border); border-radius: var(--r); box-shadow: var(--shadow); padding: 1.75rem; }

/* ---------- Forms ---------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem 1.25rem; }
.field { display: flex; flex-direction: column; gap: .35rem; }
.field.full { grid-column: 1 / -1; }
.field label { font-size: .8rem; font-weight: 700; color: var(--navy); }
.field label .req { color: var(--danger); }
.field input, .field select {
  padding: .6rem .7rem; border: 1px solid var(--border-strong); border-radius: 9px;
  font-family: var(--ff); font-size: .95rem; background: #fff; color: var(--ink);
}
.field input:focus, .field select:focus { outline: 2px solid var(--accent); border-color: var(--accent); }
/* Wraps. Three "Add a question" buttons in one unwrapping row pushed True /
   False 61px off the right of a 375px screen — the row shrank the buttons
   until their labels stacked three words deep and then simply overflowed. */
.form-actions { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; margin-top: 1.5rem; }
.msg { font-size: .9rem; }
.msg.err { color: var(--danger); }
.msg.ok { color: var(--accent-2); }

/* ---------- Admin shell (Album-style left sidebar + main) ---------- */
.admin-shell { flex: 1; display: flex; align-items: stretch; width: 100%; }
.sidebar {
  width: 232px; flex-shrink: 0; background: var(--card); border-right: 1px solid var(--border);
  padding: 1.1rem .75rem; display: flex; flex-direction: column;
}
.sidebar-nav { display: flex; flex-direction: column; gap: .15rem; }
.sidebar-link {
  display: flex; align-items: center; gap: .7rem; padding: .62rem .75rem; border-radius: 9px;
  text-decoration: none; color: var(--ink); font-size: .92rem; font-weight: 600; cursor: pointer;
}
.sidebar-link i, .sidebar-link svg { width: 18px; height: 18px; stroke-width: 2; color: var(--muted); flex-shrink: 0; }
.sidebar-link:hover { background: var(--surface); }
.sidebar-link.is-active { background: var(--accent-soft); color: var(--accent); }
.sidebar-link.is-active i, .sidebar-link.is-active svg { color: var(--accent); }
.sidebar-link .soon { margin-left: auto; font-size: .6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); background: var(--surface-2); padding: .12rem .42rem; border-radius: 999px; }
.sidebar-footer { margin-top: auto; padding: .9rem .75rem 0; color: var(--muted); font-size: .72rem; }
.admin-main { flex: 1; min-width: 0; padding: 2rem; max-width: 1100px; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
.stat-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--r); padding: 1.1rem 1.2rem; box-shadow: var(--shadow); display: flex; align-items: center; gap: .9rem; }
.stat-card i, .stat-card svg { width: 26px; height: 26px; color: var(--accent); flex-shrink: 0; }
.stat-card .value { color: var(--navy); font-size: 1.9rem; font-weight: 800; line-height: 1; }
.stat-card .label { color: var(--muted); font-size: .74rem; font-weight: 600; margin-top: .35rem; text-transform: uppercase; letter-spacing: .03em; }

@media (max-width: 760px) {
  .admin-shell { flex-direction: column; }
  .sidebar { width: 100%; border-right: 0; border-bottom: 1px solid var(--border); padding: .6rem; }
  .sidebar-nav { flex-direction: row; flex-wrap: wrap; }
  .sidebar-footer { display: none; }
  .admin-main { padding: 1.25rem 1rem; }
}

/* ---------- Table (admin) ---------- */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: .9rem; }
th, td { text-align: left; padding: .7rem .8rem; border-bottom: 1px solid var(--border); white-space: nowrap; }
th { font-size: .72rem; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); }
tbody tr:hover { background: var(--surface); }
.u-cell { display: flex; align-items: center; gap: .6rem; }
.u-cell img { width: 30px; height: 30px; border-radius: 50%; }
.u-cell .avatar-fallback { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color:var(--accent-ink); display: grid; place-items: center; font-weight: 700; font-size: .85rem; }
.pill { display: inline-block; padding: .2rem .6rem; border-radius: 999px; font-size: .72rem; font-weight: 700; }
.pill.admin { background: var(--accent-soft); color: var(--accent); }
.pill.player { background: var(--surface-2); color: var(--muted); }
.role-select { padding: .3rem .5rem; border: 1px solid var(--border-strong); border-radius: 7px; font-family: var(--ff); }

/* ---------- Auth modal ---------- */
.modal-backdrop { position: fixed; inset: 0; background: rgba(43,33,25,.55); display: none; place-items: center; z-index: 100; padding: 1rem; }
.modal-backdrop.open { display: grid; }
.modal { background: #fff; border-radius: 16px; width: 100%; max-width: 400px; padding: 2rem; box-shadow: 0 24px 60px rgba(0,0,0,.3); }
.modal-logo { display: grid; place-items: center; margin: 0 0 .9rem; }

/* 共用對話框(見 quizzes.html #appDialog)。`.modal` 已經畀咗底、圓角同陰影,
   呢度淨係補標題/內文嘅間距同兩粒掣嘅排位。 */
.modal.dialog { max-width: 440px; }
.modal.dialog h3 { margin: 0 0 .5rem; }
.modal.dialog .sub { margin: 0 0 1.25rem; color: var(--muted); }
.dialog-actions { display: flex; justify-content: flex-end; gap: .6rem; }

/* ---------- 摺埋 + 拖動排序 ---------- */

/* 一份 quiz 最多 50 題,而每張卡全展開嘅話,將第 40 題搬去第 2 位要撳 38 次
   ↑。摺埋之後成個列表望得晒,拖一次就搞掂。 */
.questions-bar { display: flex; align-items: center; justify-content: flex-end; margin: 0 0 .75rem; }
.questions-count { color: var(--muted); font-size: .85rem; }
/* In the sticky bar the count is the only thing holding the left-hand side, so
   it is stated rather than murmured — but still under the buttons and under a
   red error, which are what you act on. */
.editor-bar .questions-count { color: var(--ink); font-size: .95rem; font-weight: 700; white-space: nowrap; }

.question-card.is-collapsed { padding-top: .75rem; padding-bottom: .75rem; }
.question-card.is-collapsed .question-card-head { margin: 0 0 .55rem; }
/* 摺埋之後嘅第二行:題目文字靠左食晒剩低嘅闊度,工具靠右。因為佢自己
   一行,每張卡都一樣高、文字都由同一個位開始 —— 唔會再由類型 pill 有
   幾長決定。 */
.question-card-foot { display: flex; align-items: center; gap: .6rem; }
/* The "· 4 options" half of the type pill. First thing to go when the head row
   cannot hold it: which of the two kinds this is stays, the option count is
   recoverable by expanding the card. Threshold is where the full label stops
   fitting beside the grip, chevron and number — see questionCardHtml. */
@media (max-width: 430px) { .q-type-detail { display: none; } }
/* 摺埋之後用題目文字認返係邊條 —— 一行,長就切。 */
.q-summary { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
             color: var(--muted); font-size: .9rem; }

/* 拖動只由呢個 grip 起動(見 app.js wireQuestionDrag):成張卡拖得嘅話,
   喺題目框入面揀字就會變成拖卡。 */
.q-grip { display: inline-flex; align-items: center; color: var(--muted); cursor: grab; flex: none; }
.q-grip:active { cursor: grabbing; }
.question-card.dragging { opacity: .5; }
/* Below this the head row cannot hold the grip as well, and the grip is the
   right thing to drop: dragging is armed on mousedown (see wireQuestionDrag),
   which no touch device fires, and HTML5 drag-and-drop does not work by touch
   anyway — so on a phone it is 34px of dead control on the most crowded row on
   the page. Reordering by the up and down buttons is unaffected. */
@media (max-width: 360px) { .q-grip { display: none; } }
.modal h3 { margin: 0 0 .3rem; color: var(--navy); font-size: 1.35rem; text-align: center; }
.modal .sub { text-align: center; color: var(--muted); font-size: .9rem; margin: 0 0 1.5rem; }
.modal .btn { width: 100%; margin-bottom: .6rem; }
.btn-google { background: #fff; color: #3c4043; border: 1px solid var(--border-strong); }
.btn-google:hover { background: #f8f9fa; }
.btn-google img { width: 18px; height: 18px; }
.divider { display: flex; align-items: center; gap: .75rem; color: var(--muted); font-size: .8rem; margin: 1rem 0; }
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.modal input { width: 100%; padding: .65rem .7rem; border: 1px solid var(--border-strong); border-radius: 9px; margin-bottom: .6rem; font-family: var(--ff); font-size: .95rem; }
.modal .toggle { text-align: center; font-size: .85rem; margin-top: .75rem; color: var(--muted); }
.modal .toggle a { cursor: pointer; }
.modal .close { float: right; cursor: pointer; color: var(--muted); font-size: 1.3rem; line-height: 1; margin: -.5rem -.5rem 0 0; }

/* ---------- Footer ---------- */
.footer { text-align: center; padding: 2rem 1rem; color: var(--muted); font-size: .82rem; }

.hidden { display: none !important; }
.spinner { text-align: center; padding: 3rem; color: var(--muted); }

/* page header with an action button (My Quizzes etc.) */
.page-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.page-header .page-title, .page-header .page-sub { margin: 0; }
.page-header .page-sub { margin-top: .25rem; }

/* The editor's Cancel / Save bar, stuck to the bottom of the viewport for as
   long as the editor is on screen, and settling into place at the end of the
   page like any other block — which is what sticky gives us for free, and why
   there is no scroll handler here.

   The whole page header could not be stuck instead: it wraps on a phone, where
   it stands 145px tall, and the nav above it is already sticky at 60px. That
   pair would have spent a quarter of an 820px screen on chrome that never
   changes. This bar is one row high and does not stack with the nav.

   Flush to the bottom edge rather than floating a little above it. Floating
   leaves a strip of the question card visible underneath the bar, and a row of
   controls sliced in half by something hovering over it reads as a bug, not as
   depth. z-index stays under the nav's 50. */
.editor-bar {
  position: sticky;
  bottom: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .6rem .75rem;
  margin-top: auto;
  /* Eat the page's bottom padding. sticky pins the bar to the viewport bottom
     only until its own place in the flow arrives; with 2rem of page padding
     below it, that place was 2rem up, so scrolling to the end lifted the bar
     off the edge it had been sitting on the whole way down. Nothing follows
     the bar, so the padding has nothing left to protect. */
  margin-bottom: calc(-1 * var(--page-pad-b));
  padding: .8rem 1rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: var(--r) var(--r) 0 0;
  box-shadow: 0 -4px 18px rgba(84,39,21,.14);
}

/* The bar is full width because it has to reach the right-hand edge, which
   left the other half of it empty. It holds the two things you want in front
   of you while deciding whether to save: how many questions there are, and
   whatever Save last had to say. The count moved here out of the header above
   the questions, so it is stated once, in the half of the pair that stays on
   screen. */
.editor-bar-status { display: flex; align-items: center; gap: .6rem; min-width: 0; flex: 1 1 auto; }
.editor-bar-status:empty { display: none; }
.editor-bar-actions { display: flex; gap: .75rem; flex-shrink: 0; margin-left: auto; }

/* The editor stretches to fill the page so the bar has somewhere to be pushed
   to. sticky only pins an element that would otherwise scroll away, so on a
   quiz short enough not to scroll there was nothing to pin and the bar sat
   wherever the content happened to end — 92px up the screen on an empty
   editor, which is not "the bottom" by any reading.

   Safe to make this a flex column: none of the direct children carry both a
   top and a bottom margin, so nothing here was relying on margin collapsing.
   The gap above the bar moves onto the panel before it, because margin-top on
   the bar is spent on auto. */
body[data-page="quizzes"] main.page { display: flex; flex-direction: column; }
#quizEditorView { display: flex; flex-direction: column; flex: 1; }
.add-question-panel { margin-bottom: 1.25rem; }

/* empty states */
/* The logo, at sizes where it actually reads.
   Its four quadrants swamp the mascot below about 48px - that is why the nav
   uses the bare mascot instead - so this is for 56px and up. On a dark ground
   it needs the plate: the logo's own top-right quadrant is navy, so without
   one the mark loses a corner and reads as an L. */
.logo-mark { display: block; border-radius: 18%; }
.logo-plate {
  /* Everything derives from --size. Percentage padding would resolve against
     the PARENT's width, not this box - 8% of a wide column put 60px of
     padding inside a 76px plate and collapsed the image to nothing. The
     background is a literal, not var(--quiz-bg): a plate that follows a dark
     surface token is not a plate. */
  --size: 76px;
  width: var(--size); height: var(--size); flex: none;
  display: grid; place-items: center;
  background: #FDF8EC;
  padding: calc(var(--size) * .085);
  border-radius: calc(var(--size) * .25);
  box-shadow: 0 10px 28px rgba(0,0,0,.22);
}
.logo-plate .logo-mark { width: 100%; height: 100%; border-radius: calc(var(--size) * .16); }

.empty-state { text-align: center; padding: 3rem 1.5rem; color: var(--muted); }
.empty-state i, .empty-state svg { width: 44px; height: 44px; margin: 0 auto .75rem; display: block; opacity: .4; }
.empty-state h3 { color: var(--navy); margin: .25rem 0; }
.empty-state .avatar-fallback { margin: 0 auto; }

/* handle (slug) cell in admin table */
.handle-cell { display: inline-flex; align-items: center; gap: .35rem; font-size: .85rem; }
.handle-edit { border: 0; background: none; cursor: pointer; color: var(--muted); padding: 2px; display: inline-flex; }
.handle-edit i, .handle-edit svg { width: 14px; height: 14px; }
.handle-edit:hover { color: var(--accent); }

/* sortable table headers */
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable > span { margin-right: 4px; }
th.sortable:hover { color: var(--navy); }
th.sortable.sorted { color: var(--navy); }
.sort-ico { width: 13px; height: 13px; vertical-align: -2px; color: #C6B9A4; }
.sort-ico.active { color: var(--accent); }

/* date/time (Last active) cell */
.dt-cell { line-height: 1.25; }
.dt-cell .cell-time { color: var(--muted); font-size: .78rem; }

/* Permission dropdown (replaces the role pill + change-role column) */
.perm-select {
  padding: .3rem 1.6rem .3rem .6rem; border: 1px solid var(--border-strong); border-radius: 999px;
  font-family: var(--ff); font-size: .8rem; font-weight: 600; color: var(--navy); background: #fff;
  cursor: pointer; appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .5rem center;
}
.perm-select:hover { border-color: var(--accent); }
.perm-select:focus { outline: 2px solid var(--accent); border-color: var(--accent); }

/* icon-only action buttons (quiz table Start / Delete) */
.btn.icon-only { padding: .45rem .6rem; gap: 0; }
.btn.icon-only i, .btn.icon-only svg { width: 15px; height: 15px; }

@media (max-width: 640px) {
  .form-grid { grid-template-columns: 1fr; }
  .nav-user .who { display: none; }
  .nav-logo .brand { font-size: 1rem; }
}

/* ---------- Quiz editor ---------- */
.btn-sm { padding: .4rem .75rem; font-size: .82rem; }
.btn-icon { padding: .4rem; width: 34px; height: 34px; }
.btn-icon i, .btn-icon svg { margin: 0; }
.btn-outline.is-active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* The flex lives on a div INSIDE the cell, never on the <td> itself. A table
   cell with display:flex leaves table layout: it stops stretching to the row
   height, so its bottom border is drawn short and the row's rule visibly
   breaks under the buttons. Measured: the actions cell ended at y=554 while
   its three siblings ended at 559.

   Pinned to the right edge of the row. The cell gets whatever width is left
   over, so left-aligned buttons float in the middle with a gap after them;
   against the right edge they line up down the table however wide the title
   column happens to be. */
.row-actions { display: flex; gap: .5rem; justify-content: flex-end; }

.field textarea {
  padding: .6rem .7rem; border: 1px solid var(--border-strong); border-radius: 9px;
  font-family: var(--ff); font-size: .95rem; background: #fff; color: var(--ink); resize: vertical;
}
.field textarea:focus { outline: 2px solid var(--accent); border-color: var(--accent); }
.char-count { color: var(--muted); font-size: .78rem; align-self: flex-end; }
/* The length error and the counter it contradicts belong on one line, under
   the box: "270/200" and "must be 1-200 characters (got 270)" are the same
   fact, and reading them a line apart makes you check whether they agree.
   margin-left on the counter keeps it hard right when there is no error. */
.field-foot { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.field-foot .char-count { align-self: auto; margin-left: auto; flex-shrink: 0; }
.field-foot .field-error { margin-top: 0; }

.question-card { margin-bottom: 1rem; }
.question-card-head { display: flex; align-items: center; gap: .6rem; margin-bottom: 1.1rem; flex-wrap: wrap; }
/* margin-left: auto, at every width. A narrow-screen override used to drop
   this, which put the buttons hard left the moment the row wrapped — so where
   they sat depended on how long the question-type label happened to be. */
.question-card-tools { display: flex; gap: .4rem; margin-left: auto; }
.question-num { background: var(--navy); color: var(--accent-ink); }
.question-type { background: var(--surface-2); color: var(--muted); }

.option-count-toggle { display: flex; gap: .5rem; margin-bottom: .75rem; }
.options-grid { display: grid; gap: .6rem; }
.option-row { display: flex; align-items: center; gap: .6rem; }
.option-shape {
  width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;
  display: grid; place-items: center;
}
.option-shape i {
  width: 19px; height: 19px; background: currentColor;
  -webkit-mask: var(--shape) center / contain no-repeat;
          mask: var(--shape) center / contain no-repeat;
}
.option-row[data-i="0"] .option-shape { background: var(--a1); color: #FFFFFF;  --shape: url(/brand/shapes/flame.svg); }
.option-row[data-i="1"] .option-shape { background: var(--a2); color: #FDCC42;  --shape: url(/brand/shapes/eye.svg); }
.option-row[data-i="2"] .option-shape { background: var(--a3); color: #FFFFFF;  --shape: url(/brand/shapes/wink.svg); }
.option-row[data-i="3"] .option-shape { background: var(--a4); color: #542715;  --shape: url(/brand/shapes/smile.svg); }
.option-text { flex: 1; min-width: 0; padding: .6rem .7rem; border: 1px solid var(--border-strong); border-radius: 9px; font-family: var(--ff); font-size: .95rem; }
.option-text:focus { outline: 2px solid var(--accent); border-color: var(--accent); }
/* 選項**字數**計數器。名要叫 `option-char-count` 唔可以叫 `option-count`
   —— 呢個檔已經有一個 `.option-count-toggle`,佢講嘅係「選項**數量**」
   (2 options / 4 options 嗰組掣)。兩個名太似,意思完全唔同。
   題目一路都有計數器(`.char-count`),選項就一直只有一個
   `maxlength` 靜靜雞截住 —— 打到上限就唔畀再打,零提示。上限由 100 升到
   200 之後人會真係寫長句,更加需要知道自己去到邊。
   `tabular-nums` + `min-width`:數字由 1 位變 3 位嗰陣成行唔會跳。 */
.option-char-count { color: var(--muted); font-size: .72rem; min-width: 6.5em; text-align: right; font-variant-numeric: tabular-nums; }
/* 超額。呢兩個計數器唔再靠 `maxlength` 攔住 —— 拆走咗佢,係因為 maxlength
   喺**貼上**嗰陣會靜靜雞剪走多出嚟嘅字:貼 250 字落去,框入面剩返 200,
   而個框睇落一切正常,冇任何嘢話你知有 50 個字消失咗。打字你感覺得到
   (撳鍵冇反應),貼上你完全唔知。
   拆走係安全嘅:超出上限本來就存唔到 —— client 有 validateQuizState()、
   server 有 firestore_quizzes.validate_quiz(),兩重都會 reject 兼講出實際
   字數。maxlength 冇守住任何嘢,佢淨係喺輸入嗰刻剪走文字。 */
.char-count.is-over,
.option-char-count.is-over { color: var(--danger); font-weight: 700; }
/* 三段都唔係錯,所以一段都唔用紅色 —— 紅色喺呢一頁一路都係「唔改就 save
   唔到」。Best 用 success 綠、OK 留返灰(即係唔出聲)、Small 用咖啡色:
   同 danger 分得開,而喺 cream 底上面對比度 8.6:1。 */
.char-count.is-best,
.option-char-count.is-best { color: var(--accent-2); font-weight: 700; }
.char-count.is-small,
.option-char-count.is-small { color: var(--gold-ink); font-weight: 700; }
.char-note { color: var(--gold-ink); font-size: .82rem; margin-top: .4rem; }
.field-foot .char-note { margin-top: 0; }

/* 出錯嗰張卡 / 嗰一行自己講,唔使人由頁頂一串「Question 7, option 3: …」
   碌落去揾。頂部淨係留「N things need fixing」一句摘要 + 捲到第一個。 */
.question-card.has-error { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger) inset; }
.option-row.has-error .option-text { border-color: var(--danger); }
.field.has-error > input { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger) inset; }
.field-error { color: var(--danger); font-size: .82rem; margin-top: .4rem; }
/* 每個選項連住佢嘅錯誤要包成**一個 grid cell**。之前個錯誤 div 係
   `.options-grid` 嘅直接仔,於是佢自己佔咗下一個格位 —— 四選項兩欄排嗰陣,
   右欄嘅錯誤會出咗喺左欄下面,指錯格。 */
/* min-width: 0 because a grid item defaults to min-width: auto, which stops a
   1fr track shrinking below the item's min-content — and an <input> reports a
   min-content of about twenty characters however small the screen gets. On a
   360px phone that held the option row at 316px inside a 262px card and let it
   hang 5px off the right of the screen. The input's own min-width: 0 cannot
   help while the track it sits in refuses to narrow. */
.option-cell { display: flex; flex-direction: column; gap: .25rem; min-width: 0; }
.option-error { margin-left: calc(1.05rem + .6rem + 1.9rem + .6rem); }

/* Correct 由行尾搬咗去形狀隔籬:四選項係兩欄排,個 checkbox 之前就貼住
   中間條分界線,眼好易撈亂邊個 Correct 屬邊格。而家「呢格係邊個顏色 +
   佢啱唔啱」變成一個視覺單位。淨係留 checkbox 唔留 "Correct" 字 —— 個
   title 補返語意,而四行重複四次同一個字對讀者冇幫助。 */
.option-correct { display: inline-flex; align-items: center; margin: 0; flex: none; }
.option-correct input { margin: 0; width: 1.05rem; height: 1.05rem; cursor: pointer; }

.tf-toggle { display: flex; gap: 1.25rem; }
.tf-toggle label { display: inline-flex; align-items: center; gap: .4rem; font-weight: 600; }

.add-question-panel { margin-top: 1rem; }
.add-question-label { margin: 0 0 .5rem; font-weight: 700; color: var(--navy); font-size: .85rem; }

@media (max-width: 640px) {
  .options-grid { grid-template-columns: 1fr !important; }
}

/* ---------- Landing page (home only) ----------
   Scoped to body[data-page="home"] on purpose: /quizzes, /admin and /account
   keep the light app shell. Only the front door goes dark, so it reads as a
   sibling of prize.masterconcept.ai without dragging the signed-in pages with
   it. Sits after the base rules so these win without !important. */
body[data-page="home"] {
  --lp-bg:    #0E1629;   /* matches Master Prize, so the two look related */
  --lp-panel: #16213c;
  --lp-line:  #26355c;
  background: var(--lp-bg);
  color: #E2E6EC;
  /* One screen, no scrolling to find out what this is. */
  height: 100vh;
  overflow: hidden;
}
body[data-page="home"] .nav {
  background: transparent;
  box-shadow: none;
  border-bottom: 1px solid var(--lp-line);
}

body[data-page="home"] .lp {
  flex: 1;
  min-height: 0;                       /* let the grid shrink inside the flex column */
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(1.5rem, 4vw, 4rem);
  max-width: 1180px;
  width: 100%;
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 4vw, 2.5rem);
}

/* ---- left: the words ---- */
body[data-page="home"] .lp-copy h1 {
  font-size: clamp(2rem, 3.6vw, 3.1rem);
  font-weight: 900;
  letter-spacing: -.025em;
  line-height: 1.08;
  margin: 0 0 1rem;
  text-wrap: balance;
}
body[data-page="home"] .lp-copy h1 .pop { color: var(--gold); }
body[data-page="home"] .lp-copy p {
  font-size: clamp(1rem, 1.35vw, 1.1rem);
  line-height: 1.6;
  color: #9AA6BC;
  max-width: 34ch;
  margin: 0 0 1.75rem;
}
/* 兩粒掣(Create quiz / Join a game)排一行,窄螢幕就 wrap。 */
body[data-page="home"] .lp-cta { display: flex; flex-wrap: wrap; gap: .75rem; }
body[data-page="home"] .lp-cta .btn {
  padding: .9rem 1.6rem;
  font-size: 1.02rem;
  display: inline-flex; align-items: center; gap: .5rem;
}
body[data-page="home"] .lp-cta .btn svg { width: 18px; height: 18px; }

/* ---- right: the product ---- */
body[data-page="home"] .lp-stage {
  display: flex; flex-direction: column; align-items: center; gap: 1rem;
  min-width: 0;
}
/* 張卡就係真投影幕嗰塊畫面 —— 色同結構直接抄 mcq-engine 嘅 index.css
   @theme(bg/sunk/track/ink/muted)同 Host.tsx 嘅 ANSWERING 一拍。抄唔係
   偷懶:呢張卡存在嘅理由就係「呢個就係個產品」,佢同真嘢唔同色、唔同
   結構嘅話,佢講嘅嘢就係假嘅。 */
body[data-page="home"] .lp-screen {
  --lp-ink:   #133061;
  --lp-muted: #8C7F72;
  --lp-sunk:  #F3EADA;
  --lp-track: #EFE5D2;

  width: 100%;
  max-width: 540px;
  background: #FDF8EC;                 /* engine --color-bg */
  border-radius: 16px;
  padding: 1.35rem 1.45rem 1.4rem;
  box-shadow: 0 24px 60px rgba(0,0,0,.45);
  color: var(--lp-ink);
}
body[data-page="home"] .lp-chips { display: flex; gap: .5rem; }
body[data-page="home"] .lp-chip {
  background: var(--lp-sunk);
  color: color-mix(in srgb, var(--lp-ink) 70%, transparent);   /* 真嘢係 text-ink/70 */
  border-radius: 999px; padding: .25rem .7rem;
  font-size: .78rem; font-weight: 700;
}
body[data-page="home"] .lp-chip.is-hidden { visibility: hidden; }
body[data-page="home"] .lp-question {
  font-size: 1.45rem; font-weight: 800; line-height: 1.25;
  margin: .7rem 0 0; color: var(--lp-ink); text-wrap: balance;
}

/* 倒數行。條 bar 對正卡嘅中線;Maco 用 `absolute` 唔佔 flow —— 一個佔位嘅
   flex sibling 會將 `justify-center` 嘅中線推歪半隻 Maco 咁多,同真投影幕
   撞過嘅係同一個問題(見 Host.tsx 嗰段註解)。 */
body[data-page="home"] .lp-timerrow {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  margin: 1rem 0 .9rem;
}
body[data-page="home"] .lp-bar {
  width: 100%; max-width: 300px; height: 8px;
  background: var(--lp-track); border-radius: 999px; overflow: hidden;
}
body[data-page="home"] .lp-bar i {
  display: block; height: 100%; width: 100%;
  background: var(--a1); border-radius: 999px;
  transition: width .7s linear;        /* 同 tick 一樣 700ms,行落去先夠順 */
}
body[data-page="home"] .lp-timer {
  font-size: 1.9rem; font-weight: 900; color: var(--lp-ink);
  font-variant-numeric: tabular-nums; line-height: 1;
}
body[data-page="home"] .lp-maco {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  width: 54px; height: auto; pointer-events: none;
}

body[data-page="home"] .lp-options {
  display: grid; grid-template-columns: 1fr 1fr; gap: .55rem;
}
body[data-page="home"] .lp-opt {
  display: flex; align-items: center; gap: .55rem;
  padding: .95rem 1rem;
  border-radius: 12px;
  font-size: 1rem; font-weight: 700;
  min-width: 0;                                  /* so long labels ellipsis instead of stretching the grid */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: opacity .45s ease, transform .45s ease;
}
/* Same four colours and shapes the players see on their phones — the point is
   that this is the product, not an illustration of it. Source of truth is
   COLOURS/SHAPES in the game's OptionButton.tsx; keep them in step. */
body[data-page="home"] .lp-opt-a { background: var(--a1); color: var(--a1-ink); }
body[data-page="home"] .lp-opt-b { background: var(--a2); color: var(--a2-ink); }
body[data-page="home"] .lp-opt-c { background: var(--a3); color: var(--a3-ink); }
body[data-page="home"] .lp-opt-d { background: var(--a4); color: var(--a4-ink); }
/* The reveal dims the wrong answers, so the tick has to sit on the tile's own
   ink colour, not on white - white on the yellow tile is 1.6:1. */
body[data-page="home"] .lp-shape {
  flex: none;
  width: 1.25em; height: 1.25em;
  background: currentColor;
  -webkit-mask: var(--shape) center / contain no-repeat;
          mask: var(--shape) center / contain no-repeat;
}
body[data-page="home"] .lp-opt-a .lp-shape { --shape: url(/brand/shapes/flame.svg); }
body[data-page="home"] .lp-opt-b .lp-shape { --shape: url(/brand/shapes/eye.svg); }
body[data-page="home"] .lp-opt-c .lp-shape { --shape: url(/brand/shapes/wink.svg); }
body[data-page="home"] .lp-opt-d .lp-shape { --shape: url(/brand/shapes/smile.svg); }

/* 答題緊:得形狀 + 名。揭曉:名收起,meter + 票數出。 */
body[data-page="home"] .lp-label { overflow: hidden; text-overflow: ellipsis; }
body[data-page="home"] .lp-meter,
body[data-page="home"] .lp-count { display: none; }

body[data-page="home"] .lp-options.revealed .lp-label { display: none; }
body[data-page="home"] .lp-options.revealed .lp-meter {
  display: block; flex: 1; min-width: 0;
  height: 8px; border-radius: 999px;
  /* Track 用暗色凹槽,唔用半透明白 —— 一條 100% 嘅 fill 同一條空 track
     淨係差個光暗,十米外分唔出。同 AnswerBars.tsx 一樣嘅取捨。 */
  background: color-mix(in srgb, currentColor 22%, transparent);
}
body[data-page="home"] .lp-options.revealed .lp-meter i {
  display: block; height: 100%; width: var(--pct, 0%);
  background: currentColor; border-radius: 999px;
  transition: width .6s ease .15s;
}
body[data-page="home"] .lp-options.revealed .lp-count {
  display: block; flex: none; font-weight: 900;
  font-variant-numeric: tabular-nums;
}

/* 答錯嘅格淡走 + 去色,啱嗰格用返自己嘅墨色粗框 —— 唔會改色。真遊戲
   特登唔將啱嗰格變綠:玩家撳緊自己個紅色三角,抬頭望投影幕,自己嗰格
   卻已經唔係紅色。呢度照跟。 */
body[data-page="home"] .lp-options.revealed .lp-opt {
  opacity: .28; filter: grayscale(.7);
}
body[data-page="home"] .lp-options.revealed .lp-opt[data-correct] {
  opacity: 1; filter: none; transform: scale(1.03);
  box-shadow: 0 0 0 3px currentColor inset;
}

body[data-page="home"] .footer { color: #6B7796; padding: 1rem; }
body[data-page="home"] .footer a { color: #8892A4; }

/* Stack on narrow screens, and let the page scroll again — a phone cannot fit
   both halves at once, and forcing it to would clip the CTA. */
@media (max-width: 860px) {
  body[data-page="home"] { height: auto; overflow: auto; }
  body[data-page="home"] .lp {
    grid-template-columns: 1fr;
    padding-top: 2rem; padding-bottom: 2.5rem;
    gap: 2rem;
  }
  body[data-page="home"] .lp-copy p { max-width: none; }
}

/* ---------- Contextual guidance ----------
   Shown where the user is about to act, rather than in a first-visit tour. */
.guide { border-left: 3px solid var(--accent); }
.guide-title { margin: 0 0 .7rem; font-size: 1rem; color: var(--navy); }
.guide-list { margin: 0; padding-left: 1.1rem; display: grid; gap: .5rem; }
.guide-list li { color: var(--muted); font-size: .9rem; line-height: 1.5; }
.guide-list strong { color: var(--ink); font-weight: 600; }
.guide-foot { margin: 0; font-size: .88rem; color: var(--muted); }
/* The guide's call to action. Wraps rather than letting the button squeeze the
   line to one word per row on a narrow screen. */
.guide-cta { margin: .9rem 0 0; display: flex; align-items: center; gap: .8rem 1rem; flex-wrap: wrap; }

/* Direct load of /quizzes/new or /quizzes/<id>. Set on <html> before the body
   paints (see the inline script in quizzes.html) and cleared by app.js the
   moment it renders a view, so the two never both steer. The !important is
   there to match .hidden's — that one exists so a runtime toggle always beats
   a layout rule, and these selectors are more specific, so they win for as
   long as the class is on and lose the instant it comes off. */
.route-new #quizListView,
.route-edit #quizListView { display: none !important; }
.route-new #quizEditorView,
.route-edit #quizEditorView { display: flex !important; }

/* #editorTitle is empty in the markup so neither route inherits the other's
   heading. app.js sets the real text and drops the class in the same step, so
   this never doubles up with it. */
.route-new #editorTitle::before { content: "New quiz"; }
.route-edit #editorTitle::before { content: "Loading…"; }

/* Pressed the hero CTA before Firebase had answered. Rather than guessing
   "signed out" and opening a dialog an already-signed-in user then watches
   close itself, the button says it is working and boot() decides. */
.auth-pending #heroSignin { opacity: .65; pointer-events: none; }
.auth-pending #heroSignin::after { content: " …"; }

/* Unlisted tools, kept visually separate from the main admin views so they
   don't read as part of the product. */
.sidebar-nav-alt { margin-top: auto; padding-top: .6rem; border-top: 1px solid var(--border); }

/* ---------- Sharing ---------- */
/* Laid out after Google Drive's share dialog, because that is the one every
   colleague already knows: the add box on top, the people who have access
   below it, and a plain statement of who else can reach the thing at the
   bottom. Removing someone is an option inside their role menu, not a
   separate button — one control per row, again as Drive does it. */
.modal.dialog:has(.share-editor) { max-width: 520px; }
/* The sign-in modal owns `.modal h3 { text-align: center }`, `.modal .btn {
   width: 100% }` and `.modal input { width: 100% }`. All three are wrong here
   — a centred title over a left-aligned list, and a row whose select and
   button each try to fill it. Undone inside the share dialog only. */
.modal.dialog:has(.share-editor) h3 { text-align: left; margin-bottom: 1.1rem; }
.share-editor .btn { width: auto; margin-bottom: 0; }
.share-editor input, .share-editor select { margin-bottom: 0; }
.share-editor { text-align: left; color: var(--ink); }

.share-add { display: flex; gap: .5rem; align-items: center; }
.share-add .share-email { flex: 1; min-width: 0; }

.share-heading {
  margin: 1.25rem 0 .5rem; font-size: .95rem; color: var(--navy);
}
.share-list { display: flex; flex-direction: column; }

.share-row { display: flex; align-items: center; gap: .75rem; padding: .45rem 0; }
.share-avatar {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  display: grid; place-items: center; font-weight: 700; font-size: .85rem;
}
/* The name and address are the part that can be arbitrarily long: this column
   takes the slack and truncates, so the role control never moves. */
.share-who { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.share-who strong { font-size: .92rem; font-weight: 600; }
.share-who span {
  font-size: .8rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The role control is a text button plus a caret, not a <select>, because
   "Remove access" is not a role: inside a select it reads as a third option
   sitting level with viewer and editor. In a menu it can sit below a rule,
   which is what it is — an action, not a choice of level. Drive draws it the
   same way for the same reason. */
.role-picker { position: relative; flex: none; }
.role-btn {
  display: inline-flex; align-items: center; gap: .2rem;
  padding: .3rem .4rem; border: 0; border-radius: 8px; background: transparent;
  color: var(--ink); font-family: var(--ff); font-size: .88rem; cursor: pointer;
}
.role-btn:hover { background: var(--surface); }
.role-btn svg { width: 15px; height: 15px; }
.role-menu {
  display: none; position: absolute; right: 0; top: calc(100% + .25rem);
  min-width: 190px; padding: .35rem 0; z-index: 20;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(84,39,21,.18);
}
.role-menu.open { display: block; }
.role-item {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .5rem .8rem; border: 0; background: transparent; cursor: pointer;
  font-family: var(--ff); font-size: .9rem; color: var(--ink); text-align: left;
}
.role-item:hover { background: var(--surface); }
.role-tick { width: 1rem; flex: none; color: var(--accent); font-weight: 700; }
.role-sep { height: 1px; margin: .35rem 0; background: var(--border); }
.role-remove { color: var(--danger); }

.share-add-role { flex: none; }
.share-fixed { flex: none; color: var(--muted); font-size: .88rem; padding-right: .5rem; }

.quiz-row-sub { display: block; font-size: .78rem; color: var(--muted); margin-top: .15rem; }
.share-note { margin: .9rem 0 0; font-size: .8rem; color: var(--muted); line-height: 1.45; }
.share-err { margin: .6rem 0 0; font-size: .85rem; }

/* Read-only editor: a viewer gets a quiz they can read, not an editor whose
   every control does nothing. The fields are `disabled` (so the keyboard and
   a screen reader agree with what the eye sees); the controls that only ever
   write are gone entirely. Collapse stays — reading a fifty-question quiz
   needs it. */
/* Disabled fields still have to be READ, so they keep their ink and only lose
   the affordance: a flat, tinted box instead of one that looks typeable. */
.read-only #questionsContainer input:disabled,
.read-only #questionsContainer textarea:disabled,
.read-only #questionsContainer select:disabled,
.read-only #quizTitleInput:disabled {
  background: var(--surface); color: var(--ink); cursor: default; opacity: 1;
  -webkit-text-fill-color: var(--ink);
}
.read-only .add-question-panel,
.read-only #editorSaveBtn,
.read-only .question-card-tools,
.read-only .q-grip { display: none; }
