/* Additions for the apps/aurora-web build stage's own interactive features (cart/favourites,
   search, course matching, wired enquiry status) that the static mirror (apps/aurora/public,
   frontend.css) never had markup for. Reuses the mirror's design tokens (--ink/--muted/--line/
   --panel/--cyan/--blue/--violet/--shadow, .btn/.card/.tag/.price/.eyebrow/.text-link etc. from
   frontend.css) rather than inventing a new palette — this file only adds SELECTORS the mirror
   has no equivalent for. Kept separate from frontend.css (documented elsewhere as a byte-for-
   byte copy of the mirror's stylesheet) so that file stays a clean diff against the mirror. */

/* ── Header icon buttons (課程配對) ──────────────────────────────────────────────────── */

.icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: #fff;
  color: #1c2a57;
  font-size: 18px;
  cursor: pointer;
  flex: none;
}
.icon-btn:hover { background: var(--soft); }

.badge-count {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--violet);
  color: #fff;
  font-size: 11px;
  font-weight: 900;
  line-height: 18px;
  text-align: center;
}
.badge-count.is-hidden { display: none; }

/* ── Header account state (JOB2 — signed-out is the default; site.ts swaps in `.account-menu`
   once `/api/session` confirms a real session) — `.auth-buttons` used to be a bare fragment of
   two flex children of `.header-account`; it is now one wrapping element, so it needs its own
   flex+gap to keep the 登入/免費註冊 pair spaced the same way. */
.auth-buttons { display: flex; align-items: center; gap: 10px; }
.auth-buttons[hidden] { display: none; }

/* ── Favourite (♥) toggle — overlaid on a course card's image, like .course-badge ──────── */

.fav-toggle {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 0;
  background: rgba(255, 255, 255, .94);
  color: #98a4bd;
  font-size: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(31, 45, 80, .16);
}
.fav-toggle.is-active { color: #ef4468; }
.fav-toggle:hover { color: #ef4468; }
.course-card { position: relative; }

.fav-toggle-inline {
  position: static;
  box-shadow: none;
  border: 1px solid var(--line);
}

.course-purchase-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* ── 選擇班次 dialog (course/[slug].astro, W7) ───────────────────────────────────────────
   Replaces the old 加入購物車 flow entirely: one dialog, every batch of THIS course as a
   checkbox row, a sticky footer totalling the selection. Distinct from the legacy mirror's
   PER-BATCH `.batch-modal`/`[data-batch-modal]` (frontend.css/frontend.js — a "choose dates for
   this one batch" dialog with its own JS binding) — different markup, different data-attributes
   (`data-batch-select-*`, never `data-batch-modal`), so the two never collide. */

.batch-select-dialog {
  border: 0;
  border-radius: 16px;
  padding: 28px;
  max-width: 640px;
  width: calc(100% - 32px);
  max-height: calc(100dvh - 64px);
  overflow: auto;
  box-shadow: var(--shadow);
}
.batch-select-dialog::backdrop { background: rgba(15, 23, 42, .55); }
.batch-select-dialog h2 { margin: 0 34px 12px 0; font-size: 24px; }
.batch-select-dialog .modal-close { position: absolute; top: 18px; right: 20px; float: none; }
.batch-select-dialog { position: relative; }
.batch-select-dialog > p.muted { font-size: 14px; line-height: 1.7; margin: 0 0 10px; }

.batch-select-list { display: grid; gap: 12px; margin: 18px 0; }
.batch-select-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 14px;
  align-items: start;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
}
.batch-select-row:has(input:checked) { border-color: var(--aurora-primary); background: #f2fbfa; }
.batch-select-row.is-disabled { cursor: not-allowed; opacity: .58; background: #f8fafc; }
.batch-select-row input[type='checkbox'] { margin-top: 4px; width: 18px; height: 18px; accent-color: var(--aurora-primary); }
.batch-select-row-body p { margin: 2px 0; }
.batch-select-row-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.batch-select-row-head .price { font-size: 15px; }
.batch-select-row-head .old-price { margin-left: 6px; font-size: 13px; }

.batch-select-footer {
  position: sticky;
  bottom: -28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin: 0 -28px -28px;
  padding: 16px 28px;
  background: #fff;
  border-top: 1px solid var(--line);
}
.batch-select-footer strong { font-size: 20px; }
.batch-select-footer [data-batch-select-submit]:disabled { filter: grayscale(1); opacity: .55; cursor: not-allowed; }

@media (max-width: 640px) {
  .batch-select-footer { flex-wrap: wrap; }
  .batch-select-footer button { flex: 1 1 100%; }
}

/* ── Checkout: method-choice + FPS/transfer instructions (W7) ───────────────────────────── */

.checkout-summary-list { display: grid; gap: 8px; margin: 18px 0 10px; }
.checkout-summary-row { display: flex; justify-content: space-between; gap: 12px; font-size: 15px; }
.checkout-summary-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 19px;
  padding-top: 12px;
  margin-bottom: 20px;
  border-top: 1px solid var(--line);
}
/* 優惠碼 — its own block above the payment methods, not inside them: it is a separate form (see
   plan.astro), and the visual break is what stops it reading as a fourth payment option. */
.checkout-coupon { margin: 0 0 20px; }
.checkout-coupon-label { display: block; font-weight: 800; margin-bottom: 8px; }
.checkout-coupon-row { display: flex; gap: 8px; align-items: stretch; flex-wrap: wrap; }
.checkout-coupon-row input {
  flex: 1 1 200px;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  font: inherit;
  text-transform: uppercase;
}
.checkout-coupon-row .btn { flex: 0 0 auto; }
.checkout-coupon .form-status { margin: 10px 0 0; }
.checkout-summary-row code { font-family: inherit; font-weight: 800; letter-spacing: .04em; }

/* 預購留位 · 退款條款 — bordered, because it is the one block on this page that BLOCKS the submit;
   it has to read as a thing to do, not as more small print. */
.checkout-preorder-ack {
  border: 1px solid #f0c36d !important;
  background: #fffaf0;
  border-radius: 10px;
  padding: 14px 16px !important;
  margin: 0 0 18px !important;
}
.checkout-preorder-ack legend { font-weight: 800; padding: 0 6px !important; }
.checkout-preorder-ack > p { margin: 0 0 10px; font-size: 14px; }
.checkout-ack-option { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; }
.checkout-ack-option input { margin-top: 3px; flex: 0 0 auto; }
.checkout-ack-option a { margin-left: 4px; }

.checkout-method-form fieldset { border: 0; padding: 0; margin: 0 0 18px; display: grid; gap: 8px; }
.checkout-method-form legend { font-weight: 800; padding: 0 0 8px; }
.checkout-method-option {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  cursor: pointer;
  font-weight: 650;
}
.checkout-method-option:has(input:checked) { border-color: var(--aurora-primary); background: #f2fbfa; }
.checkout-instructions { background: #dff6ff; color: #14566f; border-radius: 10px; padding: 16px; margin: 16px 0; }
.checkout-instructions h2 { margin: 0 0 8px; font-size: 18px; }
.checkout-proof-form { margin-top: 20px; }

/* ── Enquiry form status ─────────────────────────────────────────────────────────────── */

.form-status {
  border-radius: 10px;
  padding: 12px 16px;
  font-weight: 700;
  margin: 0 0 16px;
}
.form-status.is-success { background: #e8f9f1; color: #0a8a52; }
.form-status.is-error { background: #fdecec; color: #c22a2a; }
.field-hint { margin: -6px 0 0; font-size: 13px; }

/* ── /match page ─────────────────────────────────────────────────────────────────────── */

.match-form { display: grid; gap: 20px; margin-bottom: 30px; }
.match-form fieldset { border: 0; padding: 0; margin: 0; }
.match-form legend { font-weight: 800; padding: 0 0 10px; }
.match-subject-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 10px;
}
.match-subject-option {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  cursor: pointer;
  font-weight: 600;
}
.match-subject-option:has(input:checked) { border-color: var(--violet); background: #f6f3ff; }
.match-level-field { max-width: 240px; }
.match-badge {
  position: absolute;
  bottom: 12px;
  left: 12px;
  z-index: 2;
  padding: 5px 11px;
  border-radius: 8px;
  background: rgba(11, 23, 68, .85);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
}

/* ── Header account state ──────────────────────────────────────────────────────────────
   The header used to be signed-out HTML for everyone. These are the styles for the two
   states it now has: a name + dropdown when a session resolves, and the split
   登入 / 免費註冊 pair (Requirement-HomePage-1) when it does not. */
.account-menu { position: relative; }
.account-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 42px;
  padding: 5px 12px 5px 6px;
  border: 1px solid #d5e2f3;
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-weight: 750;
  cursor: pointer;
}
.account-trigger:hover,
.account-trigger:focus-visible { border-color: var(--aurora-primary); }
.account-avatar {
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--aurora-primary), var(--aurora-secondary));
  color: #fff;
  font-size: 13px;
  font-weight: 900;
}
/* The name is the widest, least important part of the control — clamp it rather than let a
   long display name push the search box off the header. */
.account-name { max-width: 9em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-caret { color: var(--muted); font-size: 11px; }
.account-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  min-width: 180px;
  display: grid;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 18px 44px rgba(15, 33, 74, .16);
}
.account-dropdown[hidden] { display: none; }
.account-dropdown a,
.account-logout {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.account-dropdown a:hover,
.account-dropdown a:focus-visible,
.account-logout:hover,
.account-logout:focus-visible { background: #eefaf9; color: var(--aurora-primary); }
.account-dropdown form { margin: 0; border-top: 1px solid var(--line); }
.account-logout { color: #b3384a; }

/* ── 001 限時新用戶課程優惠 · 002/004-B 熱門課程系列 shelves ───────────────────────────
   Requirement 001/002/004 both call for "一行 4 個；多過 4 個 → 向右邊 carousel 移動，底部有一
   條 bar 顯示而家係第幾版". frontend.css already styles the 4-up carousel; what was missing is
   the BAR — the spec is explicit that this pager is a bar, not the hero's round dots. */
.shelf-pager { display: flex; align-items: center; gap: 12px; margin-top: 20px; }
.shelf-pager .carousel-dots { flex: 0 1 auto; justify-content: center; gap: 6px; margin-inline: auto; }
.shelf-pager .carousel-dots button {
  width: 34px;
  height: 4px;
  border: 0;
  border-radius: 999px;
  background: #dbe4f0;
  padding: 0;
  cursor: pointer;
}
.shelf-pager .carousel-dots button.is-active { width: 52px; background: var(--aurora-primary); }
/* A shelf with 4 or fewer items has exactly one page — the arrows and bar would be inert
   furniture, so the carousel script hides the whole control block. */
.shelf-pager[hidden] { display: none; }

/* 優惠倒數計時器 (日/時/分/秒). The digits are server-rendered so the clock is right on first
   paint; site.ts only ticks them. */
.offer-countdown[hidden] { display: none; }
.offer-countdown b { font-variant-numeric: tabular-nums; }

/* Promo cards (001) deliberately carry NO teacher and NO enrol count — «呢個區嘅課程卡唔使顯示
   老師，淨係要課程名 + 價錢（可加原價劃線）就得». The chips are the 影片課程/直播課程 · 共 N 堂
   pair from Requirement-HomePage-1. */
.offer-card-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 17px 12px; }
.offer-card-chip {
  padding: 4px 9px;
  border-radius: 6px;
  background: rgba(13, 31, 70, .06);
  color: #35486d;
  font-size: 12px;
  font-weight: 800;
}

/* Full course/series cards (002, 004) — the row of 星級評分 + 評分數量 + 累積報讀人次 that the
   promo card omits. */
/* One row per card, always present even when a course has no rating, so the tutor/price/CTA rows
   below it start at the same height on every card of a shelf (2026-07-27: 「size 要並列」).
   `min-height` rather than a fixed height: a very long capacity label may still wrap to two lines
   on a narrow card, and that must not clip. */
.card-metrics {
  display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap;
  min-height: 20px; font-size: 13px; color: var(--muted);
}
/* Keeps the parts of one metric (stars + score + count, or count + capacity chip) together when
   the row wraps, so a rating never breaks apart across two lines. */
.card-metric-group { display: inline-flex; align-items: center; gap: 6px; }
.card-metrics .rating { letter-spacing: 0; font-size: 13px; }
.card-metrics .rating-count { color: var(--muted); font-weight: 650; }
.enrol-count { color: #4c5f80; font-weight: 700; }

/* 004-A 最新實時課程 — «一行 4 個；超過 4 個就繼續向下排（唔使向右移）». A grid, explicitly not
   a carousel: the difference between the two sections is the whole point of that requirement. */
.subject-latest-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; }
@media (max-width: 1023px) { .subject-latest-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .subject-latest-grid { grid-template-columns: 1fr; } }

/* Series detail page (/series/:slug) — the route the homepage linked to before it existed. */
.series-hero { padding: 46px 0 10px; }
.series-hero h1 { margin: 10px 0 0; font-size: clamp(32px, 4vw, 48px); }
.series-hero > .container > p:last-child { max-width: 780px; margin: 14px 0 0; color: var(--muted); line-height: 1.75; }
.series-hero-meta { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 16px; }

/* Legal pages (/terms, /privacy) — plain prose, reachable from the 005 popup's consent line. */
.legal-page { max-width: 860px; }
.legal-page h2 { margin: 32px 0 10px; font-size: 22px; }
.legal-page p, .legal-page li { color: #3d4d6b; line-height: 1.85; }
.legal-page ul { padding-left: 22px; }

@media (max-width: 767px) {
  .account-name { display: none; }
  .account-trigger { padding: 5px; }
}

/* ── Signed-in gating (2026-07-27) ─────────────────────────────────────────────────────
   `.has-session` is set on <html> before first paint from the `ds_session_hint` cookie
   (Site.astro), then corrected by site.ts once /api/session answers. Anything marked
   `data-hide-when-authed` is for visitors who have not signed in yet. */
.has-session [data-hide-when-authed] { display: none; }


/* 上課地點 bar under a batch title (2026-07-27). Never carries a raw location id — the service
   resolves the classroom name, and this renders nothing when there is none. */
.batch-venue-bar {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 6px 0 0; padding: 4px 11px;
  border-radius: 999px; background: #f1f5fb;
  font-size: 13px; font-weight: 600; color: #45557a;
}
.batch-venue-bar span { font-size: 10px; color: var(--cyan-dark); }

/* 005 registration popup — email + password signup (2026-07-27; replaces the phone form, whose
   `.phone-register-form` grid was built around a +852 prefix cell that no longer exists). */
.account-register-form { display: grid; gap: 10px; }
.account-register-form input {
  width: 100%; min-width: 0; height: 48px; padding: 0 12px;
  border: 1px solid #ccd8ed; border-radius: 7px; font: inherit; background: #fff;
}
.account-register-form input:focus-visible { outline: 2px solid var(--aurora-primary); outline-offset: 1px; }
.account-register-form .btn { height: 48px; }

/* ── Native <details> accordion (2026-07-27 bug fix) ───────────────────────────────────
   `.accordion-content` is `display:none` in frontend.css and revealed only by an
   `.accordion-item.is-open` class that the mirror's own JS adds. LearningPromise.astro
   deliberately uses a native <details> instead (no JS needed, keyboard- and search-correct),
   which never gets that class — so clicking a 退款及使用條款 row opened the disclosure and
   revealed nothing at all. That is the client's 「Toggle function not working」.
   Reveal on the element's own [open] state; `.is-open` keeps working for the JS accordions. */
details.accordion-item[open] > .accordion-content { display: block; }

/* Chevron affordance, so a closed row visibly reads as expandable rather than as dead text. */
.promise-accordion .accordion-title { list-style: none; }
.promise-accordion .accordion-title::-webkit-details-marker { display: none; }
.promise-term-toggle::after {
  content: '⌄';
  display: inline-block;
  font-size: 15px;
  line-height: 1;
  color: var(--muted);
  transition: transform .18s ease;
}
details.accordion-item[open] .promise-term-toggle::after { transform: rotate(180deg); }

/* course/[slug].astro's #series (E10) puts the section's real <h2> inside the accordion summary,
   rather than a plain span like the FAQ rows — a screen-reader user navigating by heading still
   hears 課程系列 as a heading, but visually it must sit inline with the chevron at the row's own
   size, not the page's normal (much larger) heading size. */
.course-detail-section .accordion-title h2 { margin: 0; font-size: inherit; font-weight: inherit; }

/* Policy documents (/policy/<slug>, and /terms + /privacy once the console takes them over). */
.policy-summary { font-size: 15.5px; color: #45557a; margin: 0 0 22px; }
.legal-page .text-link { font-size: 14px; }

/* ══ 導師詳情頁 + 名師團隊 (20260727-TeacherModule.pdf) ═══════════════════════════════════ */

/* Shared chip filter — 個案分類, 課程年級, 導師科目. The markup is server-rendered in full; the
   script only toggles is-active and hidden, so with JS off every item stays visible. */
.chip-filter { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 22px; }
.chip-filter-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 18px; border: 1px solid var(--line); border-radius: 999px;
  background: #fff; color: #34456b; font: inherit; font-size: 14px; font-weight: 650;
  cursor: pointer; white-space: nowrap;
}
.chip-filter-btn:hover { border-color: var(--aurora-primary); color: var(--cyan-dark); }
.chip-filter-btn.is-active {
  border-color: transparent; color: #fff;
  background: linear-gradient(120deg, var(--aurora-primary), var(--aurora-secondary));
}
.tutor-empty-filter { margin-top: 18px; }

/* 01 主視覺 banner */
.tutor-banner {
  padding: 56px 0 44px;
  background: linear-gradient(140deg, #f2fbfb 0%, #eef3ff 55%, #f7f2ff 100%);
}
.tutor-banner-grid {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 40px; align-items: center;
}
.tutor-banner-copy h1 { margin: 12px 0 6px; font-size: clamp(34px, 4.4vw, 56px); line-height: 1.1; }
.tutor-positioning {
  display: inline-block; margin: 4px 0 10px; padding: 7px 16px; border-radius: 999px;
  background: linear-gradient(120deg, var(--aurora-primary), var(--aurora-accent));
  color: #fff; font-weight: 700; font-size: 15px;
}
.tutor-bullets { list-style: none; padding: 0; margin: 18px 0 24px; display: grid; gap: 9px; }
.tutor-bullets li { display: flex; align-items: flex-start; gap: 10px; font-size: 15.5px; color: #33415f; }
.tutor-bullets span { color: var(--aurora-accent); line-height: 1.6; }
.tutor-banner-actions { display: flex; flex-wrap: wrap; gap: 12px; }
.tutor-banner-art { display: flex; justify-content: center; }
.tutor-banner-art img { max-width: 100%; height: auto; border-radius: 18px; }
.tutor-banner-initial {
  display: grid; place-items: center; width: 200px; height: 200px; border-radius: 50%;
  background: linear-gradient(140deg, var(--aurora-primary), var(--aurora-accent));
  color: #fff; font-size: 76px; font-weight: 800;
}

/* 03 學生個案卡 */
.tutor-case-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 18px; }
.tutor-case-card { border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: #fff; }
.tutor-case-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 18px; color: #fff;
  background: linear-gradient(120deg, var(--aurora-secondary), var(--aurora-accent));
}
.tutor-case-head h3 { margin: 0; font-size: 17px; line-height: 1.4; }
.tutor-case-kind {
  font-size: 12px; font-weight: 700; padding: 3px 10px; border-radius: 999px;
  background: rgba(255, 255, 255, .22);
}
.tutor-case-body { display: flex; gap: 14px; padding: 16px 18px; }
.tutor-case-body img { width: 110px; height: 110px; object-fit: cover; border-radius: 10px; flex: none; }
.tutor-case-body p { margin: 0 0 8px; font-size: 14.5px; line-height: 1.7; color: #3d4d6b; }
.tutor-case-jump { display: flex; align-items: center; gap: 8px; font-weight: 800; }
.tutor-case-level { padding: 2px 9px; border-radius: 6px; background: #eef2f9; color: #45557a; font-size: 13px; }
.tutor-case-level.is-new { background: #e6f7f1; color: #0a8a52; }
.tutor-case-meta { font-size: 12.5px; }

/* 04 影片分享 */
.tutor-videos-section { background: #f7f9ff; }
.tutor-video-stage { max-width: 760px; margin: 0 auto 18px; }
.tutor-video-player {
  position: relative; aspect-ratio: 16 / 9; border-radius: 16px; overflow: hidden; background: #0d1220;
}
.tutor-video-player iframe { width: 100%; height: 100%; border: 0; display: block; }
.tutor-video-poster {
  display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; cursor: pointer;
}
.tutor-video-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tutor-video-play {
  position: absolute; inset: 0; margin: auto; width: 72px; height: 72px;
  display: grid; place-items: center; border-radius: 50%;
  background: rgba(255, 255, 255, .92); color: var(--aurora-secondary); font-size: 26px;
}
.tutor-video-caption { text-align: center; font-weight: 650; color: #33415f; margin: 12px 0 0; }
.tutor-video-strip {
  display: flex; gap: 12px; overflow-x: auto; padding: 6px 2px 12px; scroll-snap-type: x mandatory;
}
.tutor-video-thumb {
  flex: 0 0 218px; scroll-snap-align: start; text-align: left; cursor: pointer;
  padding: 0 0 10px; border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
  background: #fff; font: inherit;
}
.tutor-video-thumb.is-active { border-color: var(--aurora-primary); box-shadow: 0 0 0 2px rgba(8, 170, 168, .18); }
.tutor-video-thumb img { width: 100%; height: auto; display: block; }
.tutor-video-thumb-title { display: block; padding: 9px 12px 0; font-size: 13.5px; font-weight: 650; color: #22304f; }
.tutor-video-thumb-result { display: block; padding: 3px 12px 0; font-size: 12.5px; color: var(--cyan-dark); }
.tutor-video-fallback { margin: 12px 0 0; padding-left: 20px; }

/* 05 完成課程後你將可以 */
.tutor-outcome-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 18px; }
.tutor-outcome-card { border: 1px solid var(--line); border-radius: 14px; padding: 22px; background: #fff; }
.tutor-outcome-card h3 { margin: 14px 0 8px; font-size: 18px; }
.tutor-outcome-card p { margin: 0; color: #3d4d6b; line-height: 1.75; font-size: 14.5px; }
.tutor-outcome-icon {
  display: grid; place-items: center; width: 46px; height: 46px; border-radius: 12px;
  background: #eef7ff; color: var(--aurora-secondary); font-size: 20px;
}
.tutor-outcome-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0; }

/* 06 — the tutor page is the only place a course card carries two CTAs. */
.course-cta-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.course-cta-pair .btn { width: 100%; }

/* 07 固定 CTA */
.tutor-sticky-cta {
  position: sticky; bottom: 0; z-index: 40;
  background: rgba(255, 255, 255, .97); border-top: 1px solid var(--line);
  backdrop-filter: blur(8px); padding: 12px 0;
}
.tutor-sticky-cta .container {
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
}
.tutor-sticky-cta span:first-child { font-weight: 700; color: #22304f; }
.tutor-sticky-actions { display: flex; gap: 10px; }

/* 名師團隊 directory */
.tutor-directory-count { margin: 0 0 16px; font-size: 15px; color: var(--muted); }
.tutor-directory-count b { color: var(--aurora-secondary); font-size: 18px; }
.tutor-directory-list { display: grid; gap: 16px; }
.tutor-directory-card {
  display: grid; grid-template-columns: 180px minmax(0, 1fr) 220px; gap: 22px; align-items: center;
  border: 1px solid var(--line); border-radius: 16px; padding: 18px; background: #fff;
}
.tutor-directory-portrait { display: block; border-radius: 14px; overflow: hidden; }
.tutor-directory-portrait img { width: 100%; height: auto; display: block; object-fit: cover; }
.tutor-directory-initial {
  display: grid; place-items: center; width: 100%; aspect-ratio: 1; border-radius: 14px;
  background: linear-gradient(140deg, var(--aurora-primary), var(--aurora-accent));
  color: #fff; font-size: 58px; font-weight: 800;
}
.tutor-directory-body h2 { margin: 0 0 6px; font-size: 24px; }
.tutor-directory-body p { margin: 0 0 8px; }
.tutor-directory-side { display: grid; gap: 8px; justify-items: stretch; }
.tutor-directory-metric { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 14px; color: #45557a; }
.tutor-directory-metric .rating { color: #f5a623; }

@media (max-width: 900px) {
  .tutor-banner-grid { grid-template-columns: 1fr; }
  .tutor-banner-art { order: -1; }
  .tutor-directory-card { grid-template-columns: 110px minmax(0, 1fr); }
  .tutor-directory-side { grid-column: 1 / -1; }
  .tutor-case-body { flex-direction: column; }
  .tutor-case-body img { width: 100%; height: 180px; }
}

/* 預購留位 / 已額滿→排候補 on the 選擇班次 rows (2026-07-27 報讀系統). §13A: 預購留位 is the
   `urgent` role's own usage note (「名額少、預購留位、需注意」), so its text colour follows
   `--role-urgent-bg` like every other 緊張 state on the site. */
.batch-capacity-state.is-preorder { background: #fff4e5; color: var(--role-urgent-bg, #b54708); }
.batch-preorder-note {
  margin: 8px 0 0; padding: 9px 12px; border-radius: 8px;
  background: #fff8ef; color: #8a5210; font-size: 13px; line-height: 1.7;
}
.batch-preorder-ack { display: flex; align-items: center; gap: 7px; margin-top: 6px; font-weight: 600; }
.batch-preorder-ack input { width: 15px; height: 15px; }
.batch-waitlist-cta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0 0; }
.batch-waitlist-cta .btn-sm { padding: 6px 14px; font-size: 13px; }

/* ══ 報讀流程 (20260727-ShoppingCart.pdf) ═══════════════════════════════════════════════ */

.enrol-flow { padding-bottom: 120px; } /* room for the sticky counter row */
.enrol-back { display: inline-block; margin-bottom: 12px; background: none; border: 0; cursor: pointer; padding: 0; }
.enrol-exit { margin-top: 26px; }
.enrol-error { margin-top: 16px; color: #c22a2a; font-weight: 650; }

/* 步驟 1 方案卡 */
.plan-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; margin-top: 22px; }
.plan-card {
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--line); border-radius: 16px; padding: 22px; background: #fff;
}
.plan-card h2 { margin: 0; font-size: 19px; }
.plan-card-price { margin: 6px 0 0; font-size: 30px; font-weight: 800; color: var(--aurora-secondary); }
/* §13A `primary` role — its own usage note names 「強力升級提示」 explicitly. */
.plan-card-saving { margin: 0; color: var(--role-primary-bg, #c2415a); font-weight: 700; font-size: 14px; }
.plan-card .btn { margin-top: auto; }

/* 步驟 2 階梯表 + 期數清單 */
.tier-table { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin: 20px 0 26px; }
.tier-rung {
  position: relative; border: 1px solid var(--line); border-radius: 12px;
  padding: 16px 14px; text-align: center; background: #fff;
}
.tier-rung[data-recommended='true'] { border-color: var(--role-primary-bg, var(--aurora-secondary)); box-shadow: 0 0 0 2px rgba(41, 110, 232, .16); }
.tier-rung[data-best='true'] { border-color: var(--role-primary-bg, #c2415a); box-shadow: 0 0 0 2px rgba(194, 65, 90, .16); }
/* §13A — 「最推薦」 AND 「全套最抵」 are both the `primary` role's own usage note verbatim
   ("最推薦方案、全套最抵、強力升級提示"), so both badges now wear the SAME configurable colour —
   deliberately, this is the exact "twenty shades of red" problem §13A's design note warns about:
   before §13 these were two hand-picked hex values (a blue and a pink) standing in for one concept.
   The fallback pair (--aurora-secondary blue / #c2415a pink) is preserved per-class so a role
   config an admin has never edited still tells them apart until they set one. */
.tier-badge {
  position: absolute; top: -10px; left: 50%; transform: translateX(-50%);
  padding: 3px 10px; border-radius: 999px; background: var(--role-primary-bg, var(--aurora-secondary));
  color: var(--role-primary-fg, #fff); font-size: 11px; font-weight: 800; white-space: nowrap;
}
.tier-badge.is-best { background: var(--role-primary-bg, #c2415a); color: var(--role-primary-fg, #fff); }
.tier-count { margin: 0; font-size: 13px; color: var(--muted); }
.tier-price { margin: 4px 0 0; font-size: 22px; font-weight: 800; }
.tier-saving { margin: 2px 0 0; font-size: 13px; color: #c2415a; font-weight: 700; }

.period-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }
.period-row label {
  display: grid; grid-template-columns: auto 1fr auto; gap: 12px; align-items: center;
  border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; cursor: pointer; background: #fff;
}
.period-row label:has(input:checked) { border-color: var(--aurora-primary); background: #f2fbfa; }
.period-row input { width: 18px; height: 18px; accent-color: var(--aurora-primary); }
.period-title b { color: var(--aurora-secondary); margin-right: 6px; }
.period-price { color: var(--muted); font-size: 14px; }

/* 規格 v13 D6 — 已擁有嘅期灰底標示，價錢位置寫「已擁有」而唔係一個數字。The row stays visible and
   still ticked: hiding it would make the student think the 期 disappeared from the catalogue. */
.period-row.is-owned label { background: #f6f7f9; border-color: var(--line); color: var(--muted); }
.period-row.is-owned .period-title b { color: var(--muted); }
.period-row.is-owned .period-price { font-weight: 700; color: var(--aurora-primary); }
.counter-owned { margin: 4px 0 0; font-size: 13px; color: var(--muted); }

/* 步驟 3 班次／形式 — every option side by side, never a dropdown (rule C4). */
.bulk-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.bulk-actions .btn-sm { padding: 7px 14px; font-size: 13px; }
.bulk-report { margin: 0 0 16px; padding: 10px 14px; border-radius: 10px; background: #fff4e5; color: #8a5210; font-size: 13.5px; }
.period-options { margin-bottom: 26px; }
.period-options h2 { font-size: 18px; margin: 0 0 10px; }
.option-auto { font-size: 13px; margin-top: 8px; }
.option-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; }
.option-card {
  display: flex; flex-direction: column; gap: 5px;
  border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px;
  background: #fff; cursor: pointer; font-size: 13.5px; color: #45557a;
}
.option-card input[type='radio'] { accent-color: var(--aurora-primary); }
.option-card b { color: var(--ink); font-size: 15px; }
.option-card:has(input:checked) { border-color: var(--aurora-primary); box-shadow: 0 0 0 2px rgba(8, 170, 168, .16); }
.option-card.is-disabled { opacity: .62; cursor: not-allowed; background: #f8fafc; }
.option-kind {
  align-self: flex-start; padding: 2px 9px; border-radius: 999px;
  background: #eef2f9; color: #45557a; font-size: 11.5px; font-weight: 700;
}
.option-state { font-weight: 700; font-size: 12.5px; }
/* §13A semantic roles — the four states a 班次/形式 card can be in map onto the four non-primary
   roles one-to-one (available/urgent/disabled/special), so an admin recolouring "urgent" sitewide
   recolours 名額緊張, 預購留位 AND this state line together, never one without the others. Fallback
   hex is what each rule hardcoded before §13 existed. */
.option-state.is-open { color: var(--role-available-bg, #067647); }
.option-state.is-low { color: var(--role-urgent-bg, #b54708); }
.option-state.is-preorder { color: var(--role-urgent-bg, #b54708); }
.option-state.is-full { color: var(--role-disabled-bg, #98a2b3); }
.option-state.is-video { color: var(--role-special-bg, #6941c6); }
/* 名額緊張度 bar (2026-07-27) — replaces the `餘 N 位` count that used to sit here. The track is a
   FIXED width, and the fill a fixed percentage per level (services/aurora/catalogue/availability.ts):
   a bar whose width tracked the real seat count could be measured back into that count, since the
   course page publishes 共 N 位. Colours are the existing `.option-state` palette, so the bar and
   the state line above it read as one thing. */
.option-seatbar { display: flex; align-items: center; gap: 7px; margin-top: 2px; }
.option-seatbar-track {
  flex: 0 0 62px; height: 6px; border-radius: 999px;
  background: #e8edf5; overflow: hidden;
}
.option-seatbar-fill { display: block; height: 100%; border-radius: inherit; background: currentColor; }
.option-seatbar-text { font-weight: 700; font-size: 12.5px; }
.option-seatbar[data-level='plenty'] { color: var(--role-available-bg, #067647); }
.option-seatbar[data-level='tight'] { color: var(--role-urgent-bg, #b54708); }
/* Not `.option-state.is-full`'s `disabled` role — that means "you cannot have this", and a
   快將額滿 batch is still very much for sale. Also `urgent` (same role as `tight` above): the 5-role
   budget §13A caps admins at (「建議唔好超過 6 個」) has no separate "more urgent than urgent" role,
   so the two levels stay distinguished by their label/fill-percent, not by a sixth colour. */
.option-seatbar[data-level='almost_full'] { color: var(--role-urgent-bg, #c22a2a); }
.option-note { font-size: 12.5px; color: var(--muted); }
.option-ack { display: flex; align-items: center; gap: 6px; margin-top: 4px; font-size: 12.5px; }
/* A <button> since it opens this page's 候補名單 dialog rather than navigating, so the UA's own
   button chrome (border, grey background, its own font) is reset back to the link it looks like. */
.option-waitlist {
  margin-top: 4px; font-weight: 700; color: var(--aurora-secondary);
  display: block; padding: 0; border: 0; background: none; font: inherit; font-weight: 700;
  text-align: left; cursor: pointer;
}
.option-waitlist:hover { text-decoration: underline; }

/* 加購區 — the heading is a question, the 差價 leads (rules E7/E8). */
.addon-zone { border: 2px solid #c2415a; border-radius: 16px; padding: 22px; margin-top: 8px; background: #fff; }
.addon-zone h2 { margin: 0 0 14px; font-size: 20px; }
.addon-card { display: flex; gap: 12px; align-items: flex-start; cursor: pointer; }
.addon-delta { display: block; margin-top: 4px; color: var(--role-primary-bg, #c2415a); font-size: 20px; font-weight: 800; }

/* 常駐計數列 (rules E1/E2/E3) */
.enrol-counter {
  /* z-index ABOVE the bottom-right floats (frontend.css: WhatsApp 80, chat 79), not below them.
     Lifting the floats clear of this bar fixes the visible collision, but the clearance is a
     pixel guess against a bar whose height changes with its own content (the hint line, the
     applied-rules line, and a wrapped layout on narrow screens). This is the invariant that does
     not depend on getting that guess right: where the purchase bar and a decorative float ever
     overlap, the bar wins the click. It was 60 — below both — which is how 前往結帳 came to open
     WhatsApp on a 1366×768 laptop. */
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  background: rgba(255, 255, 255, .98); border-top: 1px solid var(--line);
  backdrop-filter: blur(8px); padding: 12px 0;
}
.enrol-counter .container { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.enrol-counter .counter-money { margin-left: auto; display: flex; align-items: baseline; gap: 10px; }
.enrol-counter .counter-money b { font-size: 24px; }
.enrol-counter .counter-money del { color: var(--muted); font-size: 15px; }
.counter-saving { color: var(--role-primary-bg, #c2415a); font-weight: 700; font-size: 14px; }
.counter-hint { font-weight: 600; font-size: 13.5px; }
/* Rule B7 — 「差價越平，提示要越搶眼」. `sr-strong-hint` fires when the marginal price is at/under
   its threshold; scripts/enrol.ts toggles this class AND sets `--counter-hint-role` inline (to
   whichever role the admin picked, default `primary`) rather than hardcoding one role here, so a
   changed rule roleKey restyles the hint without a script change. */
.counter-hint.is-strong {
  display: inline-block; margin-top: 4px; padding: 8px 14px; border-radius: 10px;
  font-weight: 800; font-size: 15px;
  background: var(--counter-hint-role-bg, var(--role-primary-bg, #c2415a));
  color: var(--counter-hint-role-fg, var(--role-primary-fg, #fff));
}
.counter-hint {
  margin: 8px 0 0; padding: 9px 14px; border-radius: 10px;
  background: #fff1f3; color: #c2415a; font-weight: 700; font-size: 14px;
}
.enrol-counter [data-counter-next]:disabled { filter: grayscale(1); opacity: .55; cursor: not-allowed; }
.checkout-saved { margin: 14px 0 0; font-size: 18px; font-weight: 800; color: #c2415a; }

@media (max-width: 640px) {
  .enrol-counter .container { gap: 10px; }
  .enrol-counter .counter-money { margin-left: 0; width: 100%; }
  .enrol-counter [data-counter-next] { width: 100%; }
}

/* ══ 課程詳情頁 — AfterSchool-style layout (2026-07-28 rebuild) ═══════════════════════════ */

/* Breadcrumb above the sticky anchor nav. Reuses `.container` for width/centring; this only
   adds spacing/colour. */
.course-breadcrumb { margin: 18px auto 0; padding-top: 4px; font-size: 13.5px; color: var(--muted); }
.course-breadcrumb a { color: var(--muted); }
.course-breadcrumb a:hover, .course-breadcrumb a:focus-visible { color: var(--cyan-dark); }
.course-breadcrumb span[aria-current] { color: #22304f; font-weight: 650; }

/* The sticky site header is ~78px tall (frontend.css's `.course-purchase-card` sticky offset
   already hard-codes that figure); frontend.css's `.course-anchor-nav{top:0}` collides with it,
   so the nav would sit UNDER the header instead of just below it. site.ts's
   `initCourseSectionNav` sets `--site-header-h` from the header's live height; 78px is the
   fallback for the same reason `.course-purchase-card` hard-codes it. */
.course-anchor-nav { top: var(--site-header-h, 78px); }
.course-anchor-nav a.is-active { color: var(--cyan-dark); font-weight: 800; }

/* Circular tutor avatar overlaid bottom-right of the hero image. `.course-player-shell` has no
   `position` of its own in frontend.css — set additively here rather than editing that file. */
.course-player-shell { position: relative; }
.course-hero-avatar {
  position: absolute; right: 16px; bottom: 16px; z-index: 2;
  width: 64px; height: 64px; border-radius: 50%; overflow: hidden;
  border: 3px solid #fff; box-shadow: 0 6px 16px rgba(15, 23, 42, .3);
  display: grid; place-items: center;
  background: linear-gradient(140deg, var(--aurora-primary, #08aaa8), var(--aurora-accent, #7c5cf5));
}
.course-hero-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.course-hero-avatar-initial { color: #fff; font-weight: 800; font-size: 24px; }

/* 優惠 highlight box on the purchase card — shown only when a discount is configured. */
.course-promo-box {
  margin: 12px 0; padding: 11px 14px; border-radius: 10px;
  background: linear-gradient(120deg, #fff1f3, #fff8ef); border: 1px solid #ffd9de;
}
.course-promo-box p { margin: 0; font-size: 13.5px; color: #b3283f; font-weight: 650; }
.course-promo-box p + p { margin-top: 4px; color: #8a5210; font-weight: 500; }

/* 名額緊張度 fixed-fill bar for a #batches card, reusing frontend.css's `.capacity-progress`
   track/fill — these only add the tone colours (default `i` is the amber "open" colour already). */
.capacity-progress { margin-top: 8px; }
.capacity-progress.is-low i { background: var(--role-urgent-bg, #d28a00); }
.capacity-progress.is-full i { background: var(--role-disabled-bg, #b91c1c); }

/* #tutor mini bullets — plain list beside the existing `.tutor-summary` avatar/name block. */
.tutor-mini-bullets { list-style: none; padding: 0; margin: 8px 0 10px; display: grid; gap: 4px; }
.tutor-mini-bullets li { font-size: 13.5px; color: #3d4d6b; padding-left: 14px; position: relative; }
.tutor-mini-bullets li::before {
  content: '✦'; position: absolute; left: 0; top: 2px; font-size: 11px;
  color: var(--aurora-accent, #7c5cf5);
}

/* #reviews cards */
.course-review-card { display: grid; gap: 8px; }
.course-review-head { display: flex; align-items: center; gap: 10px; }
.course-review-head img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; flex: none; }
.course-review-initial {
  display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; flex: none;
  background: var(--soft); color: var(--cyan-dark); font-weight: 800;
}
.course-review-head strong { display: block; font-size: 14.5px; }
.course-review-head .muted { font-size: 12.5px; }
.course-review-stars { letter-spacing: 1px; }
.course-review-card p { margin: 0; color: #3d4d6b; font-size: 14px; line-height: 1.7; }

/* #faq — numbered native <details> accordion, same `.promise-accordion` pattern as
   tutor/[slug].astro's 常見問題. */
.course-faq-number {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 24px; height: 24px; border-radius: 50%; margin-right: 2px;
  background: var(--soft); color: var(--cyan-dark); font-size: 12.5px; font-weight: 800;
}

/* 課程大綱 試看 preview — the button and its player both span the whole `.outline-lesson` grid
   row (that grid is `1fr auto auto`, sized for title/duration/tag), so the player sits UNDER the
   lesson it belongs to rather than squeezing into a column. `frontend.css` already styles
   `.outline-lesson button`; these only add the spanning + the stage. */
.outline-lesson button[data-lesson-preview] {
  grid-column: 1 / -1; justify-self: start; text-align: left;
  cursor: pointer; font-weight: 700; padding: 2px 0;
}
.lesson-preview-stage {
  grid-column: 1 / -1; width: 100%; max-width: 640px;
  background: #000; border-radius: 10px; overflow: hidden;
}
.lesson-preview-stage iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }

/* ── 補報 (規格 v13 §9C/9D) ────────────────────────────────────────────────────────────────
   Only ever rendered for a signed-in student who already owns part of the series; a first-time
   visitor sees none of this. */
.makeup-banner {
  margin: 0 0 14px; padding: 10px 14px; border-radius: 10px;
  background: #f3efff; color: #4b3a99; font-size: 13.5px;
}
.makeup-banner b { color: #3a2c78; }
.makeup-note { margin: 0 0 10px; font-size: 13px; }

/* §9C — an unusable 方案 card stays on the page, dimmed and explained. Hiding it would read as
   the offer having been withdrawn. */
.plan-card[data-blocked] { opacity: .72; background: #fafbfc; }
.plan-card-flag {
  display: inline-block; margin-bottom: 6px; padding: 2px 8px; border-radius: 999px;
  background: #eceff3; color: #6b7280; font-size: 11px; font-weight: 700;
}
.plan-card-blocked { margin: 6px 0 10px; font-size: 13px; color: #8a5210; line-height: 1.6; }
.plan-card [data-plan-choose]:disabled,
.plan-card .btn-primary:disabled { opacity: .55; cursor: not-allowed; }

/* §9D① — 已報讀嘅期：灰底、勾選框禁用、掛「✓ 你已報讀」、右邊寫「已擁有」而唔係價錢。 */
.period-row.is-owned label { cursor: default; }
.period-owned-tag {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  background: #e8f5ef; color: #0f7b4f; font-size: 11px; font-weight: 700; white-space: nowrap;
}
