/* ══════════════════════════════════════════════════════════════════
   HEADER
   ══════════════════════════════════════════════════════════════════ */

header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: #ece9e9;
  border-bottom: 1px solid var(--line);
  /* Left padding matches the sidebar nav's icon inset (panel 8px + item 8px =
     16px = --sp-4) so the logo lines up flush-left with the menu-row headers,
     per Big. Right stays --sp-6 so the header's right-side controls don't move. */
  padding: var(--sp-3) var(--sp-6) var(--sp-3) var(--sp-4);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.logo {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 700;
  font-size: var(--fs-lg);
  letter-spacing: -0.01em;
  color: var(--ink);
  white-space: nowrap;
}
/* The mark is one file: assets/logo.svg. Replace that file and both the
   header and the login screen update — no CSS or HTML edit needed. */
.logo-mark {
  width: 50px;
  height: 50px;
  flex-shrink: 0;
  display: block;
  border-radius: var(--r-sm);
  object-fit: contain;
}
.logo-sub {
  color: var(--muted);
  font-weight: 600;
}

/* Where the TERM picker lands on the pages that use it (per Big, 2026-07-22).
   Left-aligned on its own row above the page's filters, so it reads as "this
   page is showing THIS term" rather than as one more filter chip. */
.term-slot:empty {
  display: none; /* no stray gap on the pages the picker never visits */
}
.term-slot {
  display: flex;
  margin-bottom: var(--sp-3);
}

/* Term selector — a fully custom dropdown (not a native <select>), so the
   open list gets the same rounded corners and shadow as everything else.
   Built from a trigger button + a popover listbox (see js/modal.js).
   The [hidden] rule has to be spelled out: the browser's own `display: none`
   for a hidden element is weaker than the `display: flex` below, so without
   it the picker would keep showing on the pages it isn't wanted on. */
.term-pick[hidden] {
  display: none;
}
.term-pick {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  /* Even inset all round. The old 14px on the left was breathing room for the
     "TERM" chip that used to sit before the button — with the chip gone
     (per Big, 2026-07-23) it just looked lopsided. */
  padding: 4px 5px;
  box-shadow: var(--sh-1);
  transition:
    border-color var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out);
}
.term-pick:hover {
  border-color: var(--muted);
  box-shadow: var(--sh-2);
}
.term-pick:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(15, 23, 42, 0.09);
}
.term-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
  background: var(--surface-2);
  border: none;
  border-radius: var(--r-full);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur-1) var(--ease-out);
}
.term-trigger:hover {
  background: var(--surface-3);
}
.term-chevron {
  width: 12px;
  height: 12px;
  color: var(--muted);
  transition: transform var(--dur-2) var(--ease-out);
}
.term-trigger[aria-expanded="true"] .term-chevron {
  transform: rotate(180deg);
}

/* The open list. `popover` puts it in the browser's top layer, so it is
   never clipped by the header and needs no z-index juggling. JS sets its
   left/top from the trigger's position. */
.term-menu {
  position: fixed;
  margin: 0;
  inset: auto;
  padding: var(--sp-1);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  max-height: 60vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-width: 240px;
}
/* "All terms" — Reservation only (per Big, 2026-07-29). Sits at the very top
   of the menu as a full-width row, set off from the years below by a rule. Its
   `.on` state borrows the selected-term band's look (accent frame + tint) so
   "this is what's showing" reads the same as a chosen slot does. */
.term-all {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-1);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out);
}
.term-all:hover,
.term-all:focus-visible {
  background: var(--surface-2);
}
.term-all.on {
  border-color: var(--accent);
  background: var(--surface-3);
}
.term-all-icon {
  flex-shrink: 0;
  font-size: var(--fs-md);
  color: var(--muted);
  line-height: 1;
}
.term-all.on .term-all-icon {
  color: var(--accent);
}
.term-all-label {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-2);
}
.term-all.on .term-all-label {
  color: var(--ink);
}
.term-all-sub {
  margin-left: auto;
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* Academic-year row inside the menu: collapsible toggle + (Heads) a tiny
   delete-year ✕. Only the selected term's year starts expanded. */
.term-year-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.term-year-row + .term-year-row,
.term-lang-cols + .term-year-row {
  margin-top: var(--sp-2);
  border-top: 1px solid var(--line);
  padding-top: var(--sp-1);
}

/* An open year shows its three language calendars side by side — Thai |
   English | Chinese (per Big, 2026-07-22). Each language runs its own 12
   terms on its own dates, so picking a term picks a language too. On a narrow
   screen the columns stack instead of squeezing. */
.term-lang-cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* NO column gap: the three columns butt together so the selected term's
     frame can run unbroken across all of them (see .in-slot below). The
     coloured headers keep their own margin so they still read as separate. */
  column-gap: 0;
  row-gap: var(--sp-2);
  padding: var(--sp-1) 0 var(--sp-2);
}
@media (max-width: 720px) {
  .term-lang-cols {
    grid-template-columns: 1fr;
  }
}
.term-lang-col {
  min-width: 0;
}
.term-lang-head {
  padding: var(--sp-1) var(--sp-2);
  margin: 0 3px var(--sp-1);
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.term-lang-head.c-thai {
  background: var(--thai-bg);
  color: var(--thai-title);
  border: 1px solid var(--thai-ring);
}
.term-lang-head.c-eng {
  background: var(--eng-bg);
  color: var(--eng-title);
  border: 1px solid var(--eng-ring);
}
.term-lang-head.c-chi {
  background: var(--chi-bg);
  color: var(--chi-title);
  border: 1px solid var(--chi-ring);
}
.term-lang-empty {
  padding: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--muted);
}
/* Inside a column the rows are tighter — three of them share the width. */
.term-lang-col .term-option {
  gap: var(--sp-2);
  padding-inline: var(--sp-2);
  font-size: var(--fs-xs);
}
.term-year-toggle {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  background: none;
  border: none;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease-out);
}
.term-year-toggle:hover {
  background: var(--surface-2);
}
.term-year-chev {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  transition: transform var(--dur-2) var(--ease-out);
}
.term-year-chev.open {
  transform: rotate(90deg);
}
.term-year-count {
  margin-left: auto;
  font-weight: 600;
  color: var(--muted);
}
.term-year-del {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--muted);
  background: none;
  border: none;
  border-radius: var(--r-md);
  cursor: pointer;
  transition:
    color var(--dur-1) var(--ease-out),
    background-color var(--dur-1) var(--ease-out);
}
.term-year-del:hover {
  color: var(--danger);
  background: var(--danger-bg);
}
/* Heads only: create next year's 12 terms in one go — green like the other
   primary "add" actions. */
.term-newyear {
  display: block;
  width: 100%;
  margin-top: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  font: inherit;
  font-size: var(--fs-base);
  font-weight: 600;
  text-align: center;
  color: #fff;
  background: var(--ok);
  border: 1px solid var(--ok);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease-out);
}
.term-newyear:hover {
  background: #14532d;
  border-color: #14532d;
}
.term-menu:popover-open {
  animation: term-menu-in var(--dur-2) var(--ease-out);
}
@keyframes term-menu-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
}
.term-option {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: var(--fs-md);
  color: var(--ink-2);
  background: none;
  border: none;
  border-radius: var(--r-md);
  padding: var(--sp-2);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur-1) var(--ease-out);
}
.term-option:hover,
.term-option:focus-visible {
  background: var(--surface-2);
}
/* A finished term. Dimmed so it is obviously behind you, but still clickable —
   this picker chooses what you are LOOKING at, and last term's rosters get read
   all the time. Booking into a past term is refused in the FORMS instead
   (per Big, 2026-07-24). Hover lifts the dimming back off, so the row you are
   actually about to click always reads clearly. */
.term-option.past {
  opacity: 0.45;
}
.term-option.past:hover,
.term-option.past:focus-visible {
  opacity: 1;
}
.term-option:focus-visible {
  outline-offset: -2px;
}
.term-option-num {
  flex-shrink: 0;
  min-width: 26px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--surface-3);
  color: var(--muted);
  font-size: var(--fs-sm);
  font-weight: 700;
}
.term-option-dates {
  flex: 1;
}
/* The same term slot in ALL THREE languages is tinted AND wrapped in ONE
   frame running Thai → English → Chinese (per Big, 2026-07-22) — you see the
   three teams' dates for that term, and how they differ, in one glance.
   The frame is drawn with inset box-shadows rather than a border so it never
   changes the row's height and knocks the columns out of alignment. Each
   column contributes one piece: the outer columns close the ends, the middle
   one only draws top and bottom, so it reads as a single rectangle. */
.term-option.in-slot {
  background: var(--surface-3);
  color: var(--ink);
  border-radius: 0;
  box-shadow:
    inset 0 2px 0 0 var(--ink),
    inset 0 -2px 0 0 var(--ink);
}
.term-option.in-slot .term-option-num {
  background: var(--line-strong);
  color: var(--ink-2);
}
.term-lang-col:first-child .term-option.in-slot {
  border-radius: var(--r-md) 0 0 var(--r-md);
  box-shadow:
    inset 2px 0 0 0 var(--ink),
    inset 0 2px 0 0 var(--ink),
    inset 0 -2px 0 0 var(--ink);
}
.term-lang-col:last-child .term-option.in-slot {
  border-radius: 0 var(--r-md) var(--r-md) 0;
  box-shadow:
    inset -2px 0 0 0 var(--ink),
    inset 0 2px 0 0 var(--ink),
    inset 0 -2px 0 0 var(--ink);
}
/* Stacked on a narrow screen the columns are no longer side by side, so each
   one closes its own frame instead of forming a band. */
@media (max-width: 720px) {
  .term-lang-col .term-option.in-slot {
    border-radius: var(--r-md);
    box-shadow: inset 0 0 0 2px var(--ink);
  }
}
/* The one actually picked, inside that band: bolder, dark number badge and
   the tick — the frame belongs to all three, so those mark which is yours. */
.term-option[aria-selected="true"] {
  background: var(--surface-3);
  font-weight: 700;
  color: var(--ink);
}
.term-option[aria-selected="true"] .term-option-num {
  background: var(--accent);
  color: var(--accent-ink);
}

@media (prefers-reduced-motion: reduce) {
  .term-menu:popover-open {
    animation: none;
  }
}

.spacer {
  flex: 1;
}

/* Undo / Redo header buttons (js/undo.js) — icon-only, same neutral chrome as
   the Change Log button. Disabled (dimmed) when the stack is empty. */
.hdr-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  cursor: pointer;
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out),
    opacity var(--dur-1) var(--ease-out);
}
.hdr-icon-btn svg {
  width: 17px;
  height: 17px;
}
.hdr-icon-btn:hover:not(:disabled) {
  background: var(--surface-2);
  border-color: var(--muted);
  color: var(--ink);
}
.hdr-icon-btn:disabled {
  opacity: 0.4;
  cursor: default;
}
/* display:inline-flex above would otherwise beat the UA [hidden] rule, so undo.js
   toggling .hidden on read-only pages (Dashboard / Find Student) had no effect. */
.hdr-icon-btn[hidden] {
  display: none;
}

.badge {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 2px var(--sp-2);
  border-radius: var(--r-full);
  vertical-align: 1px;
}
.badge.head {
  background: var(--surface-3);
  color: var(--muted);
  border: 1px solid var(--line);
}

/* Change Log — a header action button, not a tab. Icon + label. */
.log-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.log-btn svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}
.log-btn:hover {
  background: var(--surface-2);
  border-color: var(--muted);
  color: var(--ink);
}
.log-btn:active {
  background: var(--surface-3);
}
/* When the Change Log view is open, the button reads as "on". */
.log-btn[aria-current="page"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* ── Announcement megaphone (📢, js/announce.js) ─────────────────────
   Sits by the logo, visible to everyone. A quiet icon button until there is
   something unread — then it tints accent, shows a count, and shakes on a slow
   loop to catch the eye without nagging. */
.ann-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  margin-left: var(--sp-3);
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  cursor: pointer;
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.ann-btn .ann-ico {
  width: 30px;
  height: 30px;
  object-fit: contain;
  display: block;
}
.ann-btn:hover {
  background: var(--surface-2);
  border-color: var(--muted);
  color: var(--ink);
}
/* Unread: accent-tinted, and shaking (the shake itself is in dialogs.css so it
   sits with the other @keyframes; reduced-motion neutralises it there). */
.ann-btn.has-unread {
  color: var(--accent);
  border-color: var(--accent);
  transform-origin: 50% 8px; /* pivot near the top, like a ringing bell */
  animation: ann-shake 3s var(--ease-out) infinite;
}
/* The unread count, riding the top-right corner. */
.ann-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-full);
  background: var(--danger);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  box-shadow: 0 0 0 2px var(--surface);
}

/* Language switcher (per Big, 2026-07-23): a compact dropdown in the header,
   styled to match the TERM picker. The trigger shows the current language; the
   popover lists all three, each in its own script (EN / ไทย / 中文) so the
   choice is legible in any current UI language. */
.lang-pick {
  position: relative;
}
.lang-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur-1) var(--ease-out);
}
.lang-trigger:hover {
  background: var(--surface-3);
}
/* A little flag chip. Fixed 20×14 with rounded corners; the SVG inside fills it
   and is clipped to the rounded box. A faint inset ring keeps a white flag (the
   Thai centre, the UK white) from bleeding into the surface. */
.lang-flag {
  display: inline-flex;
  flex-shrink: 0;
  width: 20px;
  height: 14px;
  border-radius: 3px;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.12);
}
.lang-flag svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* Left group of an option: flag + label together, with the tick pushed to the
   far right by the option's space-between. */
.lang-opt-main {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.lang-chevron {
  width: 12px;
  height: 12px;
  color: var(--muted);
  transition: transform var(--dur-1) var(--ease-out);
}
.lang-trigger[aria-expanded="true"] .lang-chevron {
  transform: rotate(180deg);
}
/* The menu is a fixed popover; shell.js sets left/top from the trigger. */
.lang-menu {
  position: fixed;
  margin: 0;
  min-width: 160px;
  padding: var(--sp-1);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--sh-3);
  overflow: visible;
}
.lang-menu:popover-open {
  animation: term-menu-in var(--dur-2) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .lang-menu:popover-open {
    animation: none;
  }
}
.lang-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);
  text-align: left;
  background: transparent;
  border: none;
  border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
}
.lang-option:hover,
.lang-option:focus-visible {
  background: var(--surface-2);
  outline: none;
}
.lang-option[aria-selected="true"] {
  background: var(--surface-3);
}
/* The tick shows only on the chosen row — hidden (not removed) elsewhere so the
   label column stays aligned. */
.lang-check {
  color: var(--accent);
  font-weight: 700;
  visibility: hidden;
}
.lang-option[aria-selected="true"] .lang-check {
  visibility: visible;
}

/* ══════════════════════════════════════════════════════════════════
   SIDEBAR NAV — collapsed to an icon rail; hover flies it open, the pin
   button keeps it open. The rail reserves --rail-w in the layout; the
   panel is absolutely positioned so the fly-out overlays content instead
   of shoving it sideways.
   ══════════════════════════════════════════════════════════════════ */

.app-body {
  display: flex;
  align-items: flex-start;
}

.sidebar {
  position: sticky;
  top: var(--header-h, 60px);
  align-self: flex-start;
  flex-shrink: 0;
  /* Above the sticky room/name columns (z-sticky) so the fly-out covers
     them instead of letting their text bleed through. */
  z-index: var(--z-sidebar);
  width: var(--rail-w);
  height: calc(100vh - var(--header-h, 60px));
  transition: width var(--dur-2) var(--ease-out);
}
.sidebar.pinned {
  width: var(--panel-w);
}
.sidebar-panel {
  position: absolute;
  inset: 0;
  width: var(--rail-w);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-2);
  background: var(--surface);
  border-right: 1px solid var(--line);
  transition:
    width var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}
.sidebar.pinned .sidebar-panel {
  width: var(--panel-w);
}
/* Fly-out on hover OR keyboard focus (so tabbing through the rail reveals
   the labels), only when NOT pinned — floats over the content. */
.sidebar:not(.pinned):hover .sidebar-panel,
.sidebar:not(.pinned):focus-within .sidebar-panel {
  width: var(--panel-w);
  box-shadow: var(--sh-3);
  border-right-color: var(--line-strong);
}

.nav-item,
.sidebar-pin {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 40px;
  padding: var(--sp-2);
  border: none;
  background: none;
  border-radius: var(--r-md);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  text-align: left;
  transition:
    background-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.nav-icon {
  /* Bumped 20 → 24px (per Big, 2026-07-30) — the colourful icons read a touch
     small at 20. 24px is the full content width of the collapsed rail
     (40px − 2×8px padding), so it no longer needs the centring nudge below. */
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  margin-left: 0;
  /* Most nav icons are now colourful <img> files (assets/icon_*.svg, supplied
     by Big 2026-07-30) with assorted viewBoxes — contain scales each to 20px
     without stretching. Harmless on the inline <svg> icons that remain
     (Attendance / Manage / Setting, which have no file yet). Colour is baked
     into the images, so they no longer tint on hover/active — the accent bar,
     the tinted row and the bold label already mark the current page. */
  object-fit: contain;
}
/* Labels fade in as the panel opens; they never wrap or push the icon. */
.nav-label {
  flex: 1;
  overflow: hidden;
  opacity: 0;
  transition: opacity var(--dur-1) var(--ease-out);
}
.sidebar.pinned .nav-label,
.sidebar:hover .nav-label,
.sidebar:focus-within .nav-label {
  opacity: 1;
}

/* Hover was --surface-2 (#f8fafc), barely off-white — too faint to see the
   row light up (per Big, 2026-07-30). Bumped to --surface-3, a clear step
   darker; the active row goes darker still (below) so the two stay distinct. */
.nav-item:hover {
  background: var(--surface-3);
  color: var(--ink);
}
.nav-item:focus-visible {
  outline-offset: -2px;
}
/* Active page: tinted fill + an accent bar on the left as the current-page
   marker (a meaningful state indicator, the standard sidebar affordance). */
.nav-item.active {
  position: relative;
  /* One step darker than the new hover (--surface-3) so the selected row still
     reads as "more" than a hovered one — on top of the accent bar + bold. */
  background: var(--line);
  color: var(--ink);
  font-weight: 700;
}
.nav-item.active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 20px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}

/* Pin toggle sits apart from the nav items, above them. */
.sidebar-pin {
  color: var(--muted);
  margin-bottom: var(--sp-1);
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  padding-bottom: var(--sp-3);
}
.sidebar-pin:hover {
  color: var(--ink);
}
.sidebar-pin[aria-pressed="true"] {
  color: var(--accent);
}
.sidebar-pin[aria-pressed="true"] .nav-icon {
  fill: color-mix(in srgb, var(--accent) 15%, transparent);
}

/* ── Signed-in user + Sign out, docked at the foot of the menu ──────────
   The nav takes all the free height (and scrolls if the list ever outgrows
   the rail) so this footer always sits at the very bottom. In the collapsed
   rail only the avatar and the logout icon show; name, role and label fade in
   with the panel, exactly like the nav labels. */
.sidebar-nav {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}
.sidebar-user {
  flex-shrink: 0;
  margin-top: var(--sp-1);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.sidebar-user .user-chip {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2);
  min-height: 40px;
}
.avatar {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: var(--r-full);
  /* Neutral on purpose: an amber avatar would imply "Thai team". */
  background: var(--ink);
  color: var(--accent-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: var(--fs-sm);
}
/* Name + role: fade in with the panel, and never wrap or push the avatar. */
.user-meta {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity var(--dur-1) var(--ease-out);
}
.sidebar.pinned .user-meta,
.sidebar:hover .user-meta,
.sidebar:focus-within .user-meta {
  opacity: 1;
}
.user-line {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  white-space: nowrap;
}
.user-line b {
  min-width: 0;
  color: var(--ink);
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
}
.user-meta .role {
  color: var(--muted);
  font-size: var(--fs-xs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Sign out — a full-width menu row like the nav items, but tinted toward
   danger on hover so it reads as the one destructive action in the rail.
   Redesigned and moved here from the header (per Big, 2026-07-30). */
.signout-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 40px;
  padding: var(--sp-2);
  border: 1px solid transparent;
  background: none;
  border-radius: var(--r-md);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  text-align: left;
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.signout-btn .nav-icon {
  color: inherit;
}
.signout-btn:hover {
  background: color-mix(in srgb, var(--danger) 10%, var(--surface));
  border-color: color-mix(in srgb, var(--danger) 35%, var(--line));
  color: var(--danger);
}
.signout-btn:active {
  background: color-mix(in srgb, var(--danger) 16%, var(--surface));
}
.signout-btn:focus-visible {
  outline-offset: -2px;
}

/* ══════════════════════════════════════════════════════════════════
   LAYOUT
   ══════════════════════════════════════════════════════════════════ */

main {
  /* flex child next to the sidebar. min-width:0 lets wide tables scroll
     inside their own container instead of stretching the whole page. */
  flex: 1;
  min-width: 0;
  padding: var(--sp-6) var(--sp-6) var(--sp-10);
}
.view {
  display: none;
}
/* A quick fade+rise when a view becomes active. The resting state is fully
   visible, so if the animation never runs the content still shows. */
.view.active {
  display: block;
  animation: view-in var(--dur-3) var(--ease-out);
}
@keyframes view-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
}
.hint {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-left: auto;
  max-width: 60ch;
  text-align: right;
}

/* Small label before a chip group ("Language", "Rooms"). */
.filter-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: var(--sp-1);
}

/* ── Filter chips ── */
.chip {
  border: 1px solid var(--line-strong);
  background: var(--surface);
  border-radius: var(--r-full);
  padding: var(--sp-1) var(--sp-3);
  font: inherit;
  font-size: var(--fs-base);
  font-weight: 600;
  cursor: pointer;
  color: var(--muted);
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.chip:hover {
  color: var(--ink);
  border-color: var(--muted);
}
/* A default ink background so an "on" chip is NEVER invisible, even if it
   carries no language colour class (the status chips on Reservation don't).
   The .c-* rules below override this for the language filter chips. */
.chip.on {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
.chip.on:hover {
  filter: brightness(1.1);
}
/* Filled chips use the darker solids — white text needs 4.5:1. */
.chip.on.c-thai {
  background: var(--thai-solid);
}
.chip.on.c-eng {
  background: var(--eng-solid);
}
.chip.on.c-chi {
  background: var(--chi-solid);
}
.chip.on.c-all {
  background: var(--accent);
}
/* A chip that only REPORTS state (the language, decided by the Term picked in
   the header) — it isn't clickable, so it must not look clickable. */
.chip-static {
  display: inline-block; /* it's a <span>, not a <button> */
  cursor: default;
}
.chip-static:hover {
  filter: none;
}

/* ── Level filter chips (Roster) ──────────────────────────────────────
   Each chip takes the colour of its own course family — Journey green,
   Explore orange, Connect blue (the school's level colours, per Big
   2026-07-22): a soft tint the moment you hover/touch it, then filled
   solid once it's the active filter. Levels that aren't J/E/C (English
   "Intermediate", etc.) fall back to the neutral accent. */
.chip.lv-J:hover {
  color: #15803d;
  border-color: #86efac;
  background: #f0fdf4;
}
.chip.on.lv-J {
  background: #15803d;
  border-color: transparent;
  color: #fff;
}
.chip.lv-E:hover {
  color: #c2410c;
  border-color: #fdba74;
  background: #fff7ed;
}
.chip.on.lv-E {
  background: #c2410c;
  border-color: transparent;
  color: #fff;
}
.chip.lv-C:hover {
  color: #1d4ed8;
  border-color: #93c5fd;
  background: #eff6ff;
}
.chip.on.lv-C {
  background: #1d4ed8;
  border-color: transparent;
  color: #fff;
}
.chip.lv-other:hover {
  color: var(--ink);
  border-color: var(--muted);
  background: var(--surface-2);
}
.chip.on.lv-other {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
}
