/* CSNL Module Picker
   Precision-instrument aesthetic: neutral zinc, one disciplined blue accent,
   Geist Sans + Geist Mono, dual light/dark, native CSS, restrained motion.
   Shape scale: controls 10px, cards 12px, panels 16px, events 8px, chips pill.
   Z-index scale: sticky header 50, timetable events 20, clash 30. */

/* ------------------------------------------------------------------ fonts */
@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-SemiBold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("fonts/GeistMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("fonts/GeistMono-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ------------------------------------------------------------- theme tokens */
:root {
  color-scheme: light;
  --bg: #f4f5f7;
  --surface: #ffffff;
  --surface-2: #f1f2f5;
  --surface-3: #e9ebef;
  --border: #e2e4ea;
  --border-strong: #c8ccd6;
  --text: #17181c;
  --text-2: #555c66;
  --text-3: #7a828e;
  --placeholder: #6f7783;

  --accent: #1d4ed8;
  --accent-hover: #1e40af;
  --accent-soft: #eef2fe;
  --accent-ring: rgba(29, 78, 216, 0.32);
  --accent-text: #ffffff;

  --danger: #b91c1c;
  --danger-bright: #dc2626;
  --danger-soft: #fef2f2;
  --danger-border: #f3c1c1;

  --ok: #047857;
  --ok-soft: #ecfdf5;
  --ok-border: #b7e4cf;

  --shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 6px 20px rgba(16, 24, 40, 0.06);

  --font-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg: #101216;
  --surface: #171a20;
  --surface-2: #1d2128;
  --surface-3: #232830;
  --border: #262b33;
  --border-strong: #3a4150;
  --text: #e8eaf0;
  --text-2: #a7aebb;
  --text-3: #858d9b;
  --placeholder: #9aa2b0;

  --accent: #60a5fa;
  --accent-hover: #93c5fd;
  --accent-soft: rgba(96, 165, 250, 0.14);
  --accent-ring: rgba(96, 165, 250, 0.35);
  --accent-text: #0b1220;
  --accent-solid: #2563eb;
  --accent-solid-hover: #1d4ed8;

  --danger: #f87171;
  --danger-bright: #ef4444;
  --danger-soft: rgba(248, 113, 113, 0.1);
  --danger-border: rgba(248, 113, 113, 0.35);

  --ok: #34d399;
  --ok-soft: rgba(52, 211, 153, 0.1);
  --ok-border: rgba(52, 211, 153, 0.35);

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.35);
}

/* no-JS fallback: honour the OS preference */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    color-scheme: dark;
    --bg: #101216;
    --surface: #171a20;
    --surface-2: #1d2128;
    --surface-3: #232830;
    --border: #262b33;
    --border-strong: #3a4150;
    --text: #e8eaf0;
    --text-2: #a7aebb;
    --text-3: #858d9b;
    --placeholder: #9aa2b0;
    --accent: #60a5fa;
    --accent-hover: #93c5fd;
    --accent-soft: rgba(96, 165, 250, 0.14);
    --accent-ring: rgba(96, 165, 250, 0.35);
    --accent-text: #0b1220;
    --accent-solid: #2563eb;
    --accent-solid-hover: #1d4ed8;
    --danger: #f87171;
    --danger-bright: #ef4444;
    --danger-soft: rgba(248, 113, 113, 0.1);
    --danger-border: rgba(248, 113, 113, 0.35);
    --ok: #34d399;
    --ok-soft: rgba(52, 211, 153, 0.1);
    --ok-border: rgba(52, 211, 153, 0.35);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.35);
  }

  /* Component-level dark styles for the OS-preference case (no data-theme).
     Mirrors the explicit [data-theme="dark"] rules below so colours that are
     hard-coded (not tokens) still flip when the user follows their OS theme. */
  :root:not([data-theme]) .timetable-event {
    background: hsl(var(--ev-hue) 40% 16%);
    color: hsl(var(--ev-hue) 50% 82%);
    border: 1px solid hsl(var(--ev-hue) 50% 38%);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  }

  :root:not([data-theme]) .icon-btn .icon-sun {
    display: block;
  }

  :root:not([data-theme]) .icon-btn .icon-moon {
    display: none;
  }

  :root:not([data-theme]) .csnl-badge.warn {
    background: rgba(251, 191, 36, 0.09);
    border-color: rgba(251, 191, 36, 0.32);
    color: #f2d48f;
  }

  :root:not([data-theme]) .course-card.over-limit {
    border-color: rgba(248, 113, 113, 0.4);
    background: rgba(248, 113, 113, 0.07);
  }

  :root:not([data-theme]) .policy-warning {
    background: rgba(251, 191, 36, 0.09);
    border-color: rgba(251, 191, 36, 0.32);
    color: #f2d48f;
  }

  :root:not([data-theme]) .policy-warning .policy-mod {
    background: rgba(251, 191, 36, 0.08);
    border-color: rgba(251, 191, 36, 0.3);
    color: #fcd34d;
  }

  :root:not([data-theme]) .total-warning {
    background: rgba(251, 191, 36, 0.09);
    border-color: rgba(251, 191, 36, 0.32);
    color: #f2d48f;
  }

  :root:not([data-theme]) .total-warning.over {
    background: rgba(248, 113, 113, 0.1);
    border-color: rgba(248, 113, 113, 0.35);
    color: #f87171;
  }
}

/* ------------------------------------------------------------------ base */
* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

/* headings and the main landmark keep clear of the sticky header when
   jumped to (skip link, fragment links) */
h1,
h2,
h3,
#main-content {
  scroll-margin-top: 84px;
  text-wrap: balance;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.hidden {
  display: none !important;
}

:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-ring);
  border-radius: 6px;
}

/* skip link: off-screen until focused (keyboard users) */
.skip-link {
  position: fixed;
  top: 0;
  left: 12px;
  z-index: 200;
  transform: translateY(-150%);
  background: var(--accent);
  color: var(--accent-text);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  padding: 9px 16px;
  border-radius: 0 0 10px 10px;
  transition: transform 0.15s ease;
}

.skip-link:focus {
  transform: translateY(0);
}

/* tap targets: no double-tap zoom delay, no flash highlight on mobile */
.btn,
.icon-btn,
.theme-toggle,
.offering,
.retry-module,
.summary-item .s-x {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* ---------------------------------------------------------------- header */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}

.topbar-inner {
  max-width: 1560px;
  margin: 0 auto;
  padding: 12px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.brand-mark {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.02em;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}

.brand h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.brand p {
  margin: 1px 0 0;
  font-size: 12.5px;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* ----------------------------------------------------------- status pill */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-2);
  font-family: var(--font-mono);
  overflow: hidden;
  max-width: 380px;
}

.status-pill .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok);
  flex-shrink: 0;
}

/* Ticker: the status text stays inside the pill and scrolls left when it
   overflows. The ghost copy makes the -50% translate loop seamlessly. */
.status-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 16px, #000 calc(100% - 16px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 16px, #000 calc(100% - 16px), transparent);
}

.status-text-track {
  display: inline-flex;
  white-space: nowrap;
  will-change: transform;
}

.status-text-copy {
  padding-right: 48px;
}

.status-text-ghost {
  display: none;
}

.status-text.scroll .status-text-ghost {
  display: inline-block;
}

.status-text.scroll .status-text-track {
  animation: status-ticker var(--ticker-duration, 18s) linear infinite;
}

.status-pill:hover .status-text-track {
  animation-play-state: paused;
}

@keyframes status-ticker {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

.status-pill.loading .dot {
  background: var(--accent);
  animation: pulse 1.1s ease-in-out infinite;
}

.status-pill.error .dot {
  background: var(--danger-bright);
}

@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.72); }
}

/* ----------------------------------------------------------- icon button */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease, transform 0.08s ease;
}

.icon-btn:hover {
  color: var(--text);
  border-color: var(--border-strong);
  background: var(--surface-2);
}

.icon-btn:active {
  transform: translateY(1px);
}

.icon-btn svg {
  width: 16px;
  height: 16px;
}

.icon-btn .icon-sun {
  display: none;
}

.icon-btn .icon-moon {
  display: block;
}

[data-theme="dark"] .icon-btn .icon-sun {
  display: block;
}

[data-theme="dark"] .icon-btn .icon-moon {
  display: none;
}

.refresh-btn-glyph {
  display: inline-flex;
  align-items: center;
  margin-right: 6px;
  vertical-align: -2px;
}

.refresh-btn-glyph svg {
  width: 13px;
  height: 13px;
}

.print-btn-glyph {
  display: inline-flex;
  align-items: center;
  margin-right: 6px;
  vertical-align: -2px;
}

.print-btn-glyph svg {
  width: 13px;
  height: 13px;
}

/* ---------------------------------------------------------- progress bar */
.fetch-progress {
  max-width: 1280px;
  margin: 14px auto 0;
  padding: 0 20px;
}

.fetch-progress-bar {
  height: 4px;
  background: var(--surface-3);
  border-radius: 999px;
  overflow: hidden;
}

.fetch-progress-fill {
  height: 100%;
  width: 100%;
  background: var(--accent);
  border-radius: 999px;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.3s ease;
}

.fetch-progress-text {
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-3);
  font-family: var(--font-mono);
}

/* ---------------------------------------------------------------- layout */
.layout {
  max-width: 1560px;
  margin: 0 auto;
  padding: 12px 14px;
}

/* Modules + timetable share the left column; sidebar spans both rows
   so it stays sticky as you scroll through both. */
.layout-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 440px;
  grid-template-rows: auto auto;
  gap: 18px;
  align-items: start;
}

@media (max-width: 960px) {
  .layout-top {
    grid-template-columns: 1fr;
  }
}

.col-modules {
  min-width: 0;
}

.col-side {
  grid-row: span 2;
  position: sticky;
  top: 78px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* The three stacked panels can be taller than the screen. Cap the sticky
     sidebar to the viewport and scroll it internally, otherwise the bottom
     panel falls below the fold and can't be reached while the page scrolls. */
  max-height: calc(100vh - 98px); /* 78px sticky offset + 20px bottom gap */
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.col-timetable {
  min-width: 0;
}

@media (max-width: 960px) {
  .col-side {
    grid-row: auto;
    position: static;
    max-height: none;
    overflow-y: visible;
    overscroll-behavior: auto;
  }
}

/* ---------------------------------------------------------------- panels */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: var(--shadow);
  padding: 18px;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}

.panel-head h2 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.course-card.current-semester {
  border-color: var(--ok-border);
  box-shadow: 0 0 0 1px var(--ok-border);
}

.muted {
  color: var(--text-3);
  font-size: 12.5px;
}

/* CSNL-only badge: green when the catalogue was re-verified against UCD's
   CSNL page on this load, amber when it couldn't be (fallback list). */
.panel-head-meta {
  display: flex;
  align-items: center;
  gap: 8px;
}

.csnl-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ok);
  background: var(--ok-soft);
  border: 1px solid var(--ok-border);
  border-radius: 999px;
  padding: 2px 9px;
  cursor: help;
  white-space: nowrap;
}

.csnl-badge::before {
  content: "\2713";
}

.csnl-badge.warn {
  color: #7c4a03;
  background: #fffbeb;
  border-color: #f2d48f;
}

.csnl-badge.warn::before {
  content: "\26A0";
}

[data-theme="dark"] .csnl-badge.warn {
  background: rgba(251, 191, 36, 0.09);
  border-color: rgba(251, 191, 36, 0.32);
  color: #f2d48f;
}

.csnl-source {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
  text-decoration: none;
  border-bottom: 1px dashed var(--border-strong);
  padding-bottom: 1px;
  white-space: nowrap;
}

.csnl-source svg {
  width: 11px;
  height: 11px;
  flex: none;
  opacity: 0.7;
}

.csnl-source:hover,
.csnl-source:focus-visible {
  color: var(--accent);
  border-bottom-style: solid;
}

/* ------------------------------------------------- inputs and buttons */
.input,
.search {
  width: 100%;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 9px 12px;
  font-size: 14px;
  color: var(--text);
  background: var(--surface);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
  font-family: inherit;
}

.input::placeholder,
.search::placeholder {
  color: var(--placeholder);
}

.input:focus-visible,
.search:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

.search {
  margin-bottom: 10px;
}

.add-code-row {
  margin-bottom: 6px;
}

.add-code-error {
  margin: 4px 2px 8px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--danger);
}

.add-code-hint {
  margin: 0 0 14px;
  font-size: 12px;
  color: var(--text-3);
}

/* --------------------------------------------------------- timing status */
/* At-a-glance per-module timetable status: counts + click-to-filter chips. */
.timing-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 12px;
}

.ts-chip,
.sem-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 11px;
  font-size: 12px;
  font-family: inherit;
  color: var(--text-2);
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

.ts-chip:hover,
.sem-chip:hover {
  border-color: var(--border-strong);
  color: var(--text);
}

.ts-chip .ts-count {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 11px;
  color: var(--text-3);
}

.ts-chip.ts-live .ts-count {
  color: var(--ok);
}

.ts-chip.ts-examfree .ts-count {
  color: var(--ok);
}

.ts-chip.ts-failed .ts-count {
  color: var(--danger);
}

.ts-chip.active,
.sem-chip.active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

.ts-chip.active .ts-count {
  color: var(--accent);
}

/* Semester selector above the weekly timetable grid. */
.sem-selector {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 10px;
}

.btn {
  border: 1px solid transparent;
  border-radius: 10px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.08s ease;
  white-space: nowrap;
}

.btn:active {
  transform: translateY(1px);
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-text);
}

.btn-primary:hover {
  background: var(--accent-hover);
}

[data-theme="dark"] .btn-primary {
  background: var(--accent-solid);
  color: #ffffff;
}

[data-theme="dark"] .btn-primary:hover {
  background: var(--accent-solid-hover);
}

.btn-secondary {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text);
}

.btn-secondary:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

.btn-ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--text-2);
}

.btn-ghost:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

.btn-danger {
  background: transparent;
  border-color: var(--danger-border);
  color: var(--danger);
}

.btn-danger:hover {
  background: var(--danger-soft);
}

.grow {
  flex: 1;
}

.row {
  display: flex;
  gap: 8px;
}

/* ------------------------------------------------------------ course list */
.theme {
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: 12px;
  background: var(--surface);
  overflow: hidden;
  transition: border-color 0.15s ease;
}

.theme-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 13px 16px;
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  color: var(--text);
  text-align: left;
  transition: background-color 0.15s ease;
}

.theme-toggle:hover {
  background: var(--surface-2);
}

.theme-toggle .chev {
  transition: transform 0.2s ease;
  color: var(--text-3);
  flex-shrink: 0;
  display: inline-flex;
}

.theme-toggle .chev svg {
  width: 12px;
  height: 12px;
  display: block;
}

.theme-toggle:not(.collapsed) .chev {
  transform: rotate(180deg);
}

.theme-count {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-3);
  background: var(--surface-3);
  border-radius: 999px;
  padding: 1px 8px;
  margin-left: 8px;
  font-family: var(--font-mono);
}

.theme-body {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
  padding: 4px 16px 16px;
}

@media (max-width: 560px) {
  .theme-body {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------ course card */
.course-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--surface);
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.course-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
  box-shadow: var(--shadow);
}

.course-card .card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
}

.card-code-col {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.course-card h3 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.005em;
}

.module-link {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s ease;
}

.module-link:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 7px;
}

.badge {
  font-size: 11px;
  font-weight: 500;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--text-2);
  font-family: var(--font-mono);
}

.badge.live {
  background: var(--accent-soft);
  color: var(--accent);
}

.badge.core {
  background: var(--accent-soft);
  color: var(--accent);
}

.badge.optional {
  background: var(--surface-3);
  color: var(--text-2);
}

.card-desc {
  font-size: 12.5px;
  color: var(--text-2);
  line-height: 1.5;
  margin: 0 0 10px;
}

.card-note {
  font-size: 12px;
  color: var(--text-3);
  line-height: 1.45;
  margin: 10px 0 4px;
  padding: 7px 10px;
  border-left: 2px solid var(--accent-soft);
  background: var(--surface-2);
  border-radius: 0 8px 8px 0;
}

.badge.exam {
  background: var(--danger-soft);
  color: var(--danger);
  border: 1px solid var(--danger-border);
  font-weight: 600;
}

.badge.none {
  background: var(--danger-soft);
  color: var(--danger);
}

.badge.fail {
  background: var(--danger-soft);
  color: var(--danger-bright);
  border: 1px solid var(--danger-border);
}

/* brief highlight when jumping to a module via the code box */
/* a selected module that pushes the selection over a CSNL credit limit */
.course-card.over-limit {
  border-color: var(--danger-border);
  background: color-mix(in srgb, var(--danger-soft) 55%, var(--surface));
}

.course-card.over-limit:hover {
  border-color: var(--danger-bright);
}

[data-theme="dark"] .course-card.over-limit {
  border-color: rgba(248, 113, 113, 0.4);
  background: rgba(248, 113, 113, 0.07);
}

.badge.over-limit-badge {
  background: var(--danger-soft);
  border-color: var(--danger-border);
  color: var(--danger);
  font-weight: 600;
}

.course-card.flash {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* -------------------------------------------------------------- offerings */
.offerings {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.offering {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
  cursor: pointer;
  background: var(--surface);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.offering:hover {
  border-color: var(--border-strong);
}

.offering:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

.offering input[type="checkbox"] {
  margin-top: 3px;
  accent-color: var(--accent);
  width: 15px;
  height: 15px;
  cursor: pointer;
  flex-shrink: 0;
}

.offering.checked {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

.offering .off-main {
  flex: 1;
  min-width: 0;
}

.offering .off-type {
  font-weight: 600;
  font-size: 12.5px;
}

.offering .off-time {
  font-size: 12.5px;
  color: var(--text);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}

.offering .off-meta {
  font-size: 11px;
  color: var(--text-3);
  margin-top: 2px;
  font-family: var(--font-mono);
}

.note {
  font-size: 12.5px;
  color: var(--text-3);
  padding: 6px 2px;
  line-height: 1.5;
}

.note.warn {
  color: var(--danger);
  background: var(--danger-soft);
  border-radius: 8px;
  padding: 8px 10px;
}

.note.fail {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.retry-module {
  border: 1px solid var(--danger-border);
  background: transparent;
  color: var(--danger);
  font-size: 11.5px;
  font-weight: 600;
  font-family: inherit;
  padding: 3px 10px;
  border-radius: 999px;
  cursor: pointer;
  margin-left: auto;
  transition: background-color 0.15s ease, color 0.15s ease;
  white-space: nowrap;
}

.retry-module:hover {
  background: var(--danger);
  color: var(--accent-text);
}

.note.loading {
  animation: pulse-soft 1.4s ease-in-out infinite;
}

@keyframes pulse-soft {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

.empty {
  text-align: center;
  color: var(--text-3);
  padding: 30px 10px;
  font-size: 14px;
}

/* ------------------------------------------------------------- sidebar bits */
.credits-badge {
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 500;
  font-size: 12px;
  border-radius: 999px;
  padding: 4px 12px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

[data-theme="dark"] .credits-badge {
  background: var(--accent-solid);
  color: #ffffff;
}

/* live COMP / level credit breakdown under the Selection header */
.credits-breakdown {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  margin-bottom: 10px;
}

.cb-cell {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 9px;
  font-variant-numeric: tabular-nums;
}

.cb-cell .cb-val {
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
}

.cb-cell .cb-label {
  font-size: 10.5px;
  color: var(--text-3);
  white-space: nowrap;
}

.cb-cell.over {
  background: var(--danger-soft);
  border-color: var(--danger-border);
}

.cb-cell.over .cb-val {
  color: var(--danger);
}

.summary-list {
  display: flex;
  flex-direction: column;
  margin-bottom: 14px;
  max-height: 240px;
  overflow-y: auto;
}

.summary-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12.5px;
  padding: 7px 2px;
  border-bottom: 1px solid var(--border);
}

.summary-item:last-child {
  border-bottom: none;
}

.summary-item .s-code {
  font-weight: 600;
}

.summary-item .s-detail {
  color: var(--text-3);
  font-size: 11px;
  font-family: var(--font-mono);
  margin-top: 1px;
}

.summary-item .s-x {
  border: none;
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  line-height: 1;
  padding: 4px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.summary-item .s-x svg {
  width: 13px;
  height: 13px;
  display: block;
}

.summary-item .s-x:hover {
  color: var(--danger);
  background: var(--danger-soft);
}

.timetable-tools {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: 1px solid var(--border);
  padding-top: 14px;
}

/* ------------------------------------------------------- clash-free plans */
.plan-hint {
  margin: 0 0 10px;
  font-size: 12px;
  color: var(--text-3);
  line-height: 1.45;
}

.plan-controls {
  margin-bottom: 8px;
}

.plan-target {
  width: 110px;
  flex-shrink: 0;
}

.plan-sem {
  flex: 1;
  min-width: 0;
}

.plan-results {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 320px;
  overflow-y: auto;
}

.plan-results-head {
  margin: 0;
  font-size: 12px;
  color: var(--text-2);
}

.plan-kind-note {
  font-size: 10px;
  font-weight: 600;
  font-family: var(--font-mono);
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--ok-soft);
  color: var(--ok);
  white-space: nowrap;
}

.plan-more-row {
  margin-top: 10px;
}

.plan-exhausted {
  margin: 10px 0 0;
  font-size: 12px;
}

.plan-item {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  background: var(--surface);
}

.plan-item-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.plan-total {
  font-size: 13px;
}

.plan-badge {
  font-size: 10px;
  font-weight: 600;
  font-family: var(--font-mono);
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--text-3);
}

.plan-badge.exact {
  background: var(--ok-soft);
  color: var(--ok);
}

.plan-locate {
  font-size: 11px;
  padding: 4px 9px;
}

.plan-use {
  margin-left: auto;
  padding: 4px 10px;
  font-size: 11.5px;
}

.plan-mods {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.plan-mod {
  font-size: 12px;
  color: var(--text-2);
  display: flex;
  align-items: center;
  gap: 6px;
}

.plan-mod .mono {
  color: var(--text-3);
  font-size: 11px;
}

.plan-credits {
  font-size: 10.5px;
  font-weight: 600;
  font-family: var(--font-mono);
  background: var(--surface-3);
  color: var(--text-2);
  border-radius: 999px;
  padding: 1px 7px;
  flex-shrink: 0;
}

/* ------------------------------------------------------------ suggestions */
.suggest-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.suggest-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
  margin-top: 6px;
}

.suggest-label:first-of-type {
  margin-top: 0;
}

.suggest-text {
  resize: vertical;
  min-height: 84px;
  line-height: 1.5;
  font-family: inherit;
}

.suggest-done {
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--ok);
  background: var(--ok-soft);
  border: 1px solid var(--ok-border);
  border-radius: 8px;
  padding: 8px 10px;
}

/* ---------------------------------------------------------- timetable grid */
.timetable-wrap {
  /* grid metrics in px, read by app.js for event positioning and overridden
     by the print stylesheet so the printed grid stays smaller */
  --hour-h: 62;
  --head-h: 44;
  --time-w: 58;
  position: relative;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}

.timetable-empty {
  margin: 8px;
  padding: 14px 16px;
  border: 1px dashed var(--border-strong);
  border-radius: 8px;
  color: var(--text-2);
  font-size: 13px;
}

.timetable-grid {
  /* position: relative anchors the absolutely-positioned event layer to the
     grid itself, so event percentages resolve against the real grid width
     (which overflows the wrap on narrow screens) instead of the wrap. */
  position: relative;
  display: grid;
  /* min 96px lets the columns shrink to fit the min-width grid on phones;
     a 150px floor made the tracks overflow the grid element and pushed every
     event into the wrong day column on mobile. */
  grid-template-columns: calc(var(--time-w) * 1px) repeat(5, minmax(96px, 1fr));
  grid-template-rows: calc(var(--head-h) * 1px) repeat(12, calc(var(--hour-h) * 1px));
  min-width: 640px;
}

.day-header {
  grid-row: 1 / 2;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12.5px;
  font-weight: 600;
  font-family: var(--font-mono);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-3);
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}

.day-header.today {
  color: var(--accent);
}

.time-slot {
  grid-column: 1 / 2;
  font-size: 11px;
  color: var(--text-3);
  text-align: right;
  padding-right: 10px;
  line-height: 1;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding-top: 3px;
  font-family: var(--font-mono);
}

.grid-cell {
  border-top: 1px solid var(--border);
  border-right: 1px solid var(--border);
}

.timetable-events {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.timetable-event {
  position: absolute;
  border-radius: 10px;
  padding: 7px 10px;
  font-size: 12.5px;
  overflow: visible; /* let the hover tooltip escape the event box */
  pointer-events: auto;
  transition: filter 0.15s ease, transform 0.15s ease;
  background: hsl(var(--ev-hue) 45% 93%);
  color: hsl(var(--ev-hue) 40% 20%);
  border: 1px solid hsl(var(--ev-hue) 60% 70%);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

[data-theme="dark"] .timetable-event {
  background: hsl(var(--ev-hue) 40% 16%);
  color: hsl(var(--ev-hue) 50% 82%);
  border: 1px solid hsl(var(--ev-hue) 50% 38%);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.timetable-event:hover {
  filter: brightness(0.96);
}

.timetable-event.clash {
  z-index: 30;
  background: var(--danger-soft) !important;
  color: var(--danger) !important;
  border: 2px solid var(--danger-bright) !important;
}

.timetable-event .ev-title {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.timetable-event .ev-time {
  opacity: 0.85;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}

/* event hover tooltip */
.ev-tooltip {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  min-width: 190px;
  max-width: 300px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: var(--shadow);
  padding: 10px 12px;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease, visibility 0.12s ease;
  text-align: left;
  font-size: 12px;
  line-height: 1.45;
}

.ev-tooltip.show {
  opacity: 1;
  visibility: visible;
}

.ev-tooltip .tt-title {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.ev-tooltip .tt-title .mono {
  color: var(--text-3);
  font-size: 10.5px;
}

.ev-tooltip .tt-sub {
  font-size: 11px;
  color: var(--text-3);
  margin-top: 2px;
}

.ev-tooltip .tt-divider {
  height: 1px;
  background: var(--border);
  margin: 7px 0 6px;
}

.ev-tooltip .tt-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  font-size: 11.5px;
  color: var(--text-2);
  margin-top: 3px;
}

.ev-tooltip .tt-badge {
  font-size: 10px;
  font-weight: 600;
  font-family: var(--font-mono);
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--text-2);
}

.ev-tooltip .tt-badge.exam {
  background: var(--danger-soft);
  color: var(--danger);
}

.ev-tooltip .tt-badge.core {
  background: var(--accent-soft);
  color: var(--accent);
}

.clash-warning {
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  color: var(--danger);
  border-radius: 10px;
  padding: 8px 12px;
  font-size: 12.5px;
  font-weight: 500;
  margin-bottom: 10px;
}

/* CSNL credit-limit warning — amber to sit beside (and read differently
   from) the red clash warning */
.policy-warning {
  background: #fffbeb;
  border: 1px solid #f2d48f;
  color: #7c4a03;
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 12.5px;
  line-height: 1.5;
  margin-bottom: 10px;
}

.policy-warning .policy-head {
  font-weight: 600;
}

.policy-warning .policy-note {
  font-size: 11.5px;
  opacity: 0.85;
  margin: 2px 0 6px;
}

.policy-warning .policy-rule {
  margin-top: 6px;
}

.policy-warning .policy-mods {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 4px;
}

.policy-warning .policy-mod {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  background: #ffffff;
  border: 1px solid #ecd9ac;
  color: #7c4a03;
  border-radius: 999px;
  padding: 1px 8px;
  cursor: help;
  white-space: nowrap;
}

[data-theme="dark"] .policy-warning {
  background: rgba(251, 191, 36, 0.09);
  border-color: rgba(251, 191, 36, 0.32);
  color: #f2d48f;
}

[data-theme="dark"] .policy-warning .policy-mod {
  background: rgba(251, 191, 36, 0.08);
  border-color: rgba(251, 191, 36, 0.3);
  color: #fcd34d;
}

/* total-credit target warning — amber when under the year's ~90 credits,
   red-tinted when clearly over-enrolled */
.total-warning {
  background: #fffbeb;
  border: 1px solid #f2d48f;
  color: #7c4a03;
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 12.5px;
  line-height: 1.5;
  margin-bottom: 10px;
}

.total-warning .tw-head {
  font-weight: 600;
}

.total-warning .tw-note {
  font-size: 11.5px;
  opacity: 0.85;
  margin-top: 2px;
}

.total-warning.over {
  background: var(--danger-soft);
  border-color: var(--danger-border);
  color: var(--danger);
}

[data-theme="dark"] .total-warning {
  background: rgba(251, 191, 36, 0.09);
  border-color: rgba(251, 191, 36, 0.32);
  color: #f2d48f;
}

[data-theme="dark"] .total-warning.over {
  background: rgba(248, 113, 113, 0.1);
  border-color: rgba(248, 113, 113, 0.35);
  color: #f87171;
}

/* --------------------------------------------------------------- footnote */
.footnote {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 16px 4px 0;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 10px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-2);
}

.fn-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok);
  flex-shrink: 0;
  margin-top: 5px;
  animation: pulse 1.5s ease-in-out infinite;
}

.footnote a {
  color: var(--accent);
  font-weight: 500;
  text-decoration: none;
}

.footnote a:hover {
  text-decoration: underline;
}

/* ----------------------------------------------------------- boot screen */
.boot-screen {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  transition: opacity 0.35s ease, visibility 0.35s ease;
}

.boot-screen.done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.boot-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 320px;
  gap: 14px;
}

.boot-mark {
  width: 56px;
  height: 56px;
  border-radius: 16px;
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 700;
  font-size: 20px;
  letter-spacing: 0.02em;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
  animation: boot-breathe 2.2s ease-in-out infinite;
}

@keyframes boot-breathe {
  0%, 100% {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), 0 0 0 0 var(--accent-ring);
  }
  50% {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), 0 0 0 14px transparent;
  }
}

.boot-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.boot-sub {
  margin: 0;
  font-size: 13px;
  color: var(--text-2);
}

.boot-track {
  position: relative;
  width: 100%;
  height: 22px;
  background: var(--surface-3);
  border-radius: 999px;
  overflow: hidden;
  margin-top: 4px;
}

.boot-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  border-radius: 999px;
  transition: width 0.3s ease;
}

.boot-percent {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 9px;
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
}

.boot-count {
  margin: 0;
  font-size: 12px;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

.boot-count.error {
  color: var(--danger);
}

#boot-retry {
  margin-top: 4px;
}

/* ------------------------------------------------- new feature styles */

/* Add-all-core button inside each theme */
.add-core-btn {
  width: 100%;
  margin: 0 0 8px;
  padding: 7px 12px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent, #2563eb);
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.add-core-btn:hover:not(.done),
.add-core-btn:focus-visible {
  background: var(--hover, rgba(37, 99, 235, 0.06));
  border-color: var(--accent, #2563eb);
}
.add-core-btn.done {
  color: var(--ok);
  border-style: solid;
  cursor: default;
  opacity: 0.75;
}

/* Prerequisites badge on cards */
.badge.prereq {
  color: var(--warn, #b45309);
  background: var(--warn-soft, rgba(180, 83, 9, 0.1));
  border: 1px solid var(--warn-border, rgba(180, 83, 9, 0.3));
  cursor: help;
}

/* Why a module is (not) exam-free — one quiet line on the card */
.exam-why {
  display: block;
  font-size: 11.5px;
  color: var(--text-3);
  margin: 6px 2px 0;
}

/* Credit progress bar (Selection panel) */
.credit-progress {
  margin: 10px 2px 4px;
}
.cp-track {
  height: 7px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
.cp-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent, #2563eb);
  transition: width 0.4s ease, background 0.3s ease;
}
.cp-fill.zone-near {
  background: var(--ok, #16a34a);
}
.cp-fill.zone-good {
  background: var(--ok, #16a34a);
}
.cp-fill.zone-over {
  background: var(--danger, #dc2626);
}
.cp-meta {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 11.5px;
  color: var(--text-3);
  margin-top: 5px;
}

/* Compare-plans rows */
.compare-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cmp-head {
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: 2px;
}
.cmp-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 10px;
}
@media (max-width: 720px) {
  /* Phone: slot letter + info on the first line, Save/Load/× as a full-width
     row underneath (the narrow column layout made the buttons unreadably
     small and the info text cramped). */
  .cmp-row {
    flex-wrap: wrap;
    row-gap: 6px;
  }
  .cmp-info {
    flex: 1 1 calc(100% - 30px);
  }
  .cmp-actions {
    width: 100%;
    flex-direction: row;
    justify-content: flex-end;
  }
  .cmp-actions .btn {
    flex: 1;
    padding: 7px 9px;
  }
}
.cmp-slot {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 12px;
  width: 20px;
  text-align: center;
  color: var(--accent, #2563eb);
}
.cmp-info {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  color: var(--text-2);
}
.cmp-info em {
  color: var(--text-3);
  font-style: normal;
}
.cmp-actions {
  display: flex;
  gap: 4px;
}
.cmp-actions .btn {
  padding: 4px 9px;
  font-size: 11.5px;
}

/* Toast (undo + copy/export feedback) */
.toast {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translate(-50%, 16px);
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(92vw, 560px);
  padding: 10px 16px;
  background: var(--surface);
  color: var(--text-1, inherit);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow);
  font-size: 13px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 90;
}
.toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}
.toast-undo {
  font: inherit;
  font-weight: 600;
  color: var(--accent, #2563eb);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}

/* Timetable panel action cluster */
.tt-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.tt-actions #print-btn .print-btn-glyph {
  display: inline-flex;
}

/* Data freshness stamp in the footnote */
.data-freshness {
  color: var(--text-3);
  opacity: 0.9;
}
.data-freshness::before {
  content: "— ";
}

/* Workload badge on plan cards */
.plan-badge.wl {
  color: var(--text-2);
}

/* Exam reason line inside the event tooltip */
.tt-why {
  color: var(--text-3);
  font-size: 11.5px;
}

/* ------------------------------------------------- batch-3 feature styles */

/* Assessment breakdown chips on module cards */
.assess-breakdown {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 8px 2px 0;
}
.assess-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10.5px;
  font-family: var(--font-mono);
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-2);
  cursor: help;
  max-width: 100%;
  overflow: hidden;
}
.assess-chip strong {
  font-weight: 700;
}
.assess-chip.final {
  color: var(--exam, #b91c1c);
  border-color: var(--exam-border, rgba(185, 28, 28, 0.4));
  background: var(--exam-soft, rgba(185, 28, 28, 0.08));
}
.assess-chip.midterm {
  color: var(--text-3);
  border-style: dashed;
}

/* Live clash preview on offerings */
.offering.clash-preview {
  border-color: var(--exam-border, rgba(185, 28, 28, 0.45));
  background: var(--exam-soft, rgba(185, 28, 28, 0.05));
}
.off-clash {
  font-size: 10.5px;
  color: var(--exam, #b91c1c);
  margin-top: 2px;
}

/* Plan preference filters */
.plan-prefs {
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin: 8px 0;
}
.pref {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--text-2);
  cursor: pointer;
  white-space: nowrap;
}
.pref input[type="checkbox"] {
  accent-color: var(--accent, #2563eb);
}
.pref input[type="number"] {
  width: 46px;
  padding: 3px 6px;
  font-size: 12px;
}

/* Theme completion meter */
.theme-meter {
  font-size: 11.5px;
  color: var(--ok, #16a34a);
  margin: 0 2px 6px;
}

/* Contact hours already inherit .plan-badge.wl */

/* Compare staging bar + table */
.compare-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  border: 1px solid var(--accent, #2563eb);
  border-radius: 12px;
  background: var(--surface);
  font-size: 13px;
  position: sticky;
  top: calc(var(--topbar-h, 90px) + 8px);
  z-index: 25;
}
.compare-bar.hidden {
  display: none;
}
.btn-compare-go {
  padding: 5px 12px;
  font-size: 12.5px;
}
.btn-compare-clear {
  padding: 5px 12px;
  font-size: 12.5px;
}
.cmp-pick {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  color: var(--text-3);
  margin-top: 4px;
  cursor: pointer;
}
.cmp-pick input {
  accent-color: var(--accent, #2563eb);
}
.compare-table {
  padding: 0;
}
.compare-table.hidden {
  display: none;
}
.cmp-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}
.cmp-table th,
.cmp-table td {
  border: 1px solid var(--border);
  padding: 8px 10px;
  text-align: left;
  vertical-align: top;
}
.cmp-table thead th,
.cmp-table tr:first-child th {
  background: var(--surface-2, var(--surface));
  font-family: var(--font-mono);
  font-size: 12px;
}
.cmp-table tr > th:first-child {
  width: 110px;
  color: var(--text-3);
  font-weight: 600;
  background: var(--surface-2, var(--surface));
}
.cmp-exam {
  display: inline-block;
  font-weight: 700;
  margin-right: 6px;
}
.cmp-exam.yes {
  color: var(--exam, #b91c1c);
}
.cmp-exam.no {
  color: var(--ok, #16a34a);
}
.cmp-exam-why {
  display: block;
  font-size: 11px;
  color: var(--text-3);
}
.cmp-assess {
  font-size: 11.5px;
  color: var(--text-2);
}
.cmp-assess strong {
  color: var(--text-1, inherit);
}

/* Report link on cards */
.report-link {
  display: inline-block;
  font-size: 10px;
  color: var(--text-3);
  margin-top: 4px;
  text-decoration: none;
}
.report-link:hover {
  color: var(--exam, #b91c1c);
  text-decoration: underline;
}

/* Export / Import row */
.import-label {
  position: relative;
  cursor: pointer;
  text-align: center;
}

/* Offering swap buttons inside the event tooltip */
.tt-swaps {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.swap-offering {
  font: inherit;
  font-size: 11px;
  font-family: var(--font-mono);
  padding: 3px 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-2, transparent);
  color: var(--text-2);
  cursor: pointer;
}
.swap-offering:hover {
  border-color: var(--accent, #2563eb);
  color: var(--accent, #2563eb);
}

/* ------------------------------------------------------------ motion gates */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------------------------------------- mobile */
@media (max-width: 960px) {
  .layout {
    padding: 14px;
  }

  /* Sticky mini-toolbar: the status chips stay reachable while scrolling
     the module list on a phone (the sticky topbar height is measured by
     app.js into --topbar-h). */
  .timing-summary {
    position: sticky;
    top: calc(var(--topbar-h, 90px) + 8px);
    z-index: 30;
    background: var(--surface);
    padding: 7px 0;
    margin: 0;
    box-shadow: 0 8px 10px -12px var(--shadow-color, rgba(0, 0, 0, 0.35));
  }
}

@media (max-width: 720px) {
  /* Long theme labels must never force the panel wider than the phone —
     they were the source of a 60px horizontal scroll on 390px screens.
     The label span itself gets the same treatment (it is a flex item of
     .theme-toggle, whose min-content width is set by the longest name). */
  .theme-toggle {
    min-width: 0;
  }
  .theme-toggle > span:first-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
  }
  .theme {
    min-width: 0;
  }
  .panel {
    min-width: 0;
  }
  .row {
    min-width: 0;
  }
  .row .input {
    min-width: 0;
  }
  .tt-actions .btn {
    flex: 1 1 auto;
    padding: 8px 10px;
  }
}

@media (max-width: 640px) {
  .topbar-inner {
    padding: 10px 14px;
    gap: 10px;
  }
  .layout {
    padding: 12px;
  }
  .layout-top {
    gap: 14px;
  }

  /* two-row topbar: brand on top, actions underneath */
  .topbar-inner {
    flex-direction: column;
    align-items: stretch;
  }
  .brand p {
    display: none;
  }
  .brand h1 {
    font-size: 15px;
  }
  .brand-mark {
    width: 34px;
    height: 34px;
    font-size: 13px;
    border-radius: 9px;
  }
  .topbar-actions {
    width: 100%;
    justify-content: space-between;
    gap: 8px;
  }
  .status-pill {
    max-width: none;
    flex: 1;
    min-width: 0;
    font-size: 11.5px;
    padding: 6px 10px;
  }
  .status-pill #status-text {
    min-width: 0;
    overflow: hidden;
  }

  /* on the smallest screens the pill gets the room and the refresh button
     shows just its icon glyph (the label is a bare text node, so hiding the
     button's font-size collapses it without touching the svg) */
  @media (max-width: 420px) {
    #refresh-btn {
      font-size: 0;
      padding: 10px 12px;
    }
    #refresh-btn .refresh-btn-glyph svg {
      width: 16px;
      height: 16px;
    }
  }

  /* roomier touch targets */
  .btn {
    padding: 10px 14px;
    font-size: 13.5px;
    min-height: 40px;
  }
  .icon-btn {
    width: 40px;
    height: 40px;
  }
  .panel {
    padding: 14px;
    border-radius: 14px;
  }
  .theme-toggle {
    padding: 14px 16px;
    font-size: 14.5px;
  }
  .theme-body {
    padding: 2px 14px 14px;
  }
  .course-card {
    padding: 14px;
  }
  .offering {
    padding: 11px 12px;
  }
  .offering input[type="checkbox"] {
    width: 19px;
    height: 19px;
  }
  .summary-item {
    padding: 9px 2px;
  }
  .summary-item .s-x {
    padding: 8px;
  }
  .summary-item .s-x svg {
    width: 16px;
    height: 16px;
  }
  .input,
  .search {
    padding: 11px 12px;
    font-size: 16px; /* >=16px stops iOS zoom-on-focus */
  }

  /* panel heads: let the meta chips wrap under the title on narrow screens */
  .panel-head {
    flex-wrap: wrap;
  }
  .panel-head h2 {
    min-width: 0;
  }
  .panel-head-meta {
    flex-wrap: wrap;
    margin-left: auto;
  }

  /* plan controls stack on narrow screens */
  .plan-controls {
    flex-wrap: wrap;
  }
  .plan-target {
    width: 100%;
  }
  .plan-sem {
    flex: 1;
  }

  /* keep the timetable readable while scrolling horizontally */
  .timetable-wrap {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
  }
  .day-header {
    scroll-snap-align: start;
  }
  .timetable-grid {
    min-width: 620px;
  }

  .fetch-progress {
    padding: 0 14px;
  }
  .boot-card {
    max-width: 280px;
  }
  .footnote {
    padding: 0 4px;
  }

  /* notched phones: keep content clear of the bottom home indicator */
  body {
    padding-bottom: env(safe-area-inset-bottom);
  }
  .topbar {
    padding-top: env(safe-area-inset-top);
  }
}

/* -------------------------------------------------- antigravity layer
   Spatial depth: panels float on layered diffused shadows, cards drop in
   with a per-index stagger, and hover states lift with a hint of 3D tilt.
   Everything is transform/opacity only and gated by prefers-reduced-motion. */
.layout-top {
  perspective: 1600px;
}

.panel {
  box-shadow:
    0 1px 2px rgba(16, 24, 40, 0.05),
    0 12px 32px -12px rgba(16, 24, 40, 0.12);
  transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1),
              box-shadow 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
  transform: translateZ(0);
}

@media (prefers-reduced-motion: no-preference) {
  .col-side .panel {
    animation: panel-float-in 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
  }
  .col-side .panel:nth-child(2) { animation-delay: 0.08s; }
  .col-side .panel:nth-child(3) { animation-delay: 0.16s; }
  .col-modules .panel { animation: panel-float-in 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) backwards; }
  .col-timetable .panel { animation: panel-float-in 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) 0.1s backwards; }
}

@keyframes panel-float-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Course cards: domino drop-in using the --i index set by app.js */
@media (prefers-reduced-motion: no-preference) {
  .course-card {
    animation: card-drop-in 0.42s cubic-bezier(0.22, 0.61, 0.36, 1) both;
    animation-delay: calc(var(--i, 0) * 28ms);
  }
}

@keyframes card-drop-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Hover lift with a subtle 3D tilt on cards, panels, and offerings */
@media (prefers-reduced-motion: no-preference) {
  .course-card:hover {
    transform: translateY(-3px) rotateX(2.5deg) rotateY(-1.5deg);
    box-shadow:
      0 2px 4px rgba(16, 24, 40, 0.06),
      0 18px 40px -16px rgba(16, 24, 40, 0.22);
  }
  .col-side .panel:hover {
    transform: translateY(-2px);
    box-shadow:
      0 2px 4px rgba(16, 24, 40, 0.05),
      0 20px 44px -18px rgba(16, 24, 40, 0.22);
  }
  .offering:hover {
    transform: translateY(-1px);
  }
  .timetable-event:hover {
    transform: translateZ(4px);
  }
}

/* Boot screen: glass card with a weightless floating mark */
@media (prefers-reduced-motion: no-preference) {
  .boot-card {
    animation: boot-card-in 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }
  .boot-mark {
    animation: boot-breathe 2.2s ease-in-out infinite,
               boot-float 3.2s ease-in-out infinite;
  }
}

@keyframes boot-card-in {
  from { opacity: 0; transform: translateY(16px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes boot-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}

/* ------------------------------------------------------------- printing */
.print-only {
  display: none;
}

@media print {
  @page {
    size: A4 landscape;
    margin: 10mm;
  }

  body {
    background: #fff;
  }

  /* only the timetable survives */
  .topbar,
  .col-modules,
  .col-side,
  .fetch-progress,
  .boot-screen,
  .footnote {
    display: none !important;
  }

  .layout {
    display: block;
    max-width: none;
    padding: 0;
  }

  .layout-top {
    display: block;
  }

  .col-timetable {
    display: block;
  }

  .panel {
    border: 0;
    box-shadow: none;
    padding: 0;
  }

  .print-only {
    display: block;
  }

  #print-btn {
    display: none !important;
  }

  /* compact grid that still fits one page; app.js re-reads these metrics */
  .timetable-wrap {
    --hour-h: 40;
    --head-h: 30;
    --time-w: 46;
    overflow: visible;
    border: 1px solid #c9ccd4;
    border-radius: 8px;
  }

  .timetable-grid {
    min-width: 0;
  }

  .grid-cell,
  .time-slot,
  .day-header,
  .timetable-event,
  .timetable-event.clash,
  .clash-warning,
  .policy-warning {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  .day-header {
    background: #eef0f4;
    color: #1c1e24;
  }

  /* print header + module legend */
  .pi-title {
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: #111;
  }

  .pi-sub {
    font-size: 11.5px;
    color: #555c66;
    margin: 2px 0 10px;
  }

  .pi-modules {
    margin-top: 12px;
    border-top: 1px solid #c9ccd4;
    padding-top: 8px;
  }

  .pi-mod {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 10.5px;
    padding: 1.5px 0;
  }

  .pi-code {
    font-family: var(--font-mono);
    font-weight: 600;
    color: #111;
    min-width: 84px;
  }

  .pi-name {
    color: #333;
  }

  .pi-cred {
    color: #555;
    margin-left: auto;
    white-space: nowrap;
  }

  .pi-total {
    margin-top: 6px;
    font-size: 11px;
    font-weight: 600;
    color: #111;
  }

  .pi-modules .muted {
    color: #666;
  }
}

/* ---------------------------------------------------------- discord CTA */
.discord-cta {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
  padding: 14px 18px;
  border-radius: 16px;
  text-decoration: none;
  color: #fff;
  background:
    radial-gradient(120% 200% at 0% 0%, rgba(255, 255, 255, 0.14) 0%, rgba(255, 255, 255, 0) 45%),
    linear-gradient(135deg, #5865f2 0%, #4752c4 55%, #6d5ae8 100%);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 10px 30px rgba(88, 101, 242, 0.35);
  transition: transform 0.18s ease, box-shadow 0.18s ease, filter 0.18s ease;
}

.discord-cta:hover,
.discord-cta:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 14px 38px rgba(88, 101, 242, 0.45);
  filter: saturate(1.08);
  color: #fff;
}

.discord-cta:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.discord-cta:active {
  transform: translateY(0);
}

.discord-cta-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 13px;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.22);
}

.discord-cta-icon svg {
  width: 27px;
  height: 27px;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.18));
}

.discord-cta-copy {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.discord-cta-copy strong {
  font-size: 15.5px;
  font-weight: 650;
  letter-spacing: 0.01em;
}

.discord-cta-copy span {
  font-size: 12.5px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.82);
}

.discord-cta-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
  flex: none;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #4752c4;
  background: #fff;
  border-radius: 999px;
  padding: 5px 11px;
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
}

.discord-cta-pill::before {
  content: "\2726";
  font-size: 10px;
}

.discord-cta-arrow {
  display: flex;
  flex: none;
  transition: transform 0.18s ease;
}

.discord-cta-arrow svg {
  width: 17px;
  height: 17px;
}

.discord-cta:hover .discord-cta-arrow,
.discord-cta:focus-visible .discord-cta-arrow {
  transform: translate(3px, -3px);
}

@media (max-width: 720px) {
  .discord-cta {
    flex-wrap: wrap;
    gap: 10px;
    padding: 13px 15px;
  }

  .discord-cta-pill {
    display: none;
  }

  .discord-cta-copy strong {
    font-size: 14.5px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .discord-cta,
  .discord-cta-arrow {
    transition: none;
  }

  .discord-cta:hover,
  .discord-cta:focus-visible {
    transform: none;
  }
}

/* Empty copy in the Selection panel. A class rather than a style="…" attribute
   on the generated <p>, because the deployed CSP is `style-src 'self'` with no
   'unsafe-inline' — a style attribute parsed out of markup would be dropped. */
.sel-empty {
  margin: 4px 2px 12px;
}
