/* =========================================================================
   ApexityEdu — Chrome
   The register ribbon, topbar, sidebar, bottom navigation, search,
   notification panel, role switcher, avatar menu.
   ========================================================================= */

/* IMPL-MOBILE (v2.7 rebuild): the global `overflow-x: clip/hidden` rule
   that used to live here has been REMOVED. It was the root cause of
   off-screen content being invisible — clipping hides anything wider than
   the viewport, so users couldn't see (or scroll to) wide tables, grids,
   or modals on phones. The correct fix is responsive reflow at the
   component level (table text wrapping, grid collapse, full-bleed
   modals/drawers, bottom-sheet navigation), NOT a global clip. See
   components.css for the raw `table.tbl` mobile wrap rule and the
   @media (max-width: 880px) block below for the mobile chrome layout
   (sidebar hidden, bottom-nav + more-sheet pattern, no body overflow
   lock). html/body now use their default overflow (visible) so any
   inadvertently-wide content remains scrollable to rather than clipped. */

/* --- Register ribbon (signature element) --------------------------- */
.ribbon {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--ribbon-h);
  background: var(--ribbon-color);
  z-index: var(--z-ribbon);
}

/* --- App shell layout ---------------------------------------------- */
.app-shell {
  display: grid;
  /* v2.31 (MOBILE-TAB-FLOW): `1fr` is `minmax(auto, 1fr)`, so the content
     track could never shrink below the min-content width of whatever the
     active screen renders — a wide tab strip (People / SMS / Academics Setup
     / Templates) or the 7-column Routine grid pushed the WHOLE page past the
     viewport, and the app then had to be panned both sideways and down "like a
     bigger screen" while the fixed bottom-nav stayed put. `minmax(0, 1fr)`
     lets the track collapse to the viewport, which is what makes the
     components' own horizontal scrollers (.tabs, .table-wrap, .routine-grid)
     actually engage on phones. */
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: var(--topbar-h) 1fr;
  grid-template-areas:
    "topbar topbar"
    "sidebar main";
  min-height: 100vh;
  /* v2.31 (MOBILE-TAB-FLOW): `100vh` is the LARGE viewport on iOS/Android —
     with the dynamic browser chrome it is taller than what the user can
     actually see, so even a short screen looked like it had extra page to
     scroll. The dynamic viewport removes that phantom vertical scroll; the
     vh line above stays as the fallback for older browsers. */
  min-height: 100svh;
  /* Never wider than the viewport — the content column is the scroller. */
  max-width: 100%;
  padding-top: var(--ribbon-h);
}

.app-shell.no-sidebar {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "topbar"
    "main";
}

.app-topbar { grid-area: topbar; }
.app-sidebar { grid-area: sidebar; }
.app-main { grid-area: main; }

/* SHELL-8: Skip-to-content link for keyboard / SR users. Visually
   hidden off-screen until focused, then slides into view. */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: var(--ink-surface);
  color: var(--on-ink-surface);
  padding: 8px 16px;
  z-index: 10000;
  text-decoration: none;
  border-radius: 0 0 var(--radius-md) 0;
}
.skip-link:focus {
  top: 0;
}

/* SHELL-19: Lightweight top progress bar shown while a route is
   loading. Replaces the brief white flash between screens. */
.route-loading {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--marigold);
  animation: route-loading-progress 1.5s ease-in-out infinite;
  z-index: 9999;
}
@keyframes route-loading-progress {
  0%   { transform: scaleX(0); transform-origin: left; }
  50%  { transform: scaleX(1); transform-origin: left; }
  100% { transform: scaleX(0); transform-origin: right; }
}

/* --- Topbar -------------------------------------------------------- */
.app-topbar {
  display: flex;
  flex-wrap: wrap;  /* IMPL-MOBILE: allow the topbar to wrap to 2 rows on
     narrow screens instead of overflowing horizontally. Multi-role admin
     users (4 role buttons + bell + chip + theme + avatar) can't fit on a
     360px screen in a single row. */
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-5);
  background: var(--topbar-bg);
  border-bottom: 1px solid var(--topbar-border);
  position: sticky;
  top: var(--ribbon-h);
  z-index: var(--z-topbar);
  min-height: var(--topbar-h);  /* wrapping may grow the height; never
     shrink below the canonical topbar height so the grid row stays
     predictable on desktop. */
}

.colophon {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
  min-width: 0;
}
.colophon .colophon-mark {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--ink);
  letter-spacing: -0.02em;
  font-style: italic;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
}
/* v2.14: the school logo renders LARGER (40px), with NO border / frame /
   background box — the artwork itself is the mark (PNG/JPG/WebP/SVG all
   supported). Transparent PNGs sit directly on the topbar. */
.colophon .colophon-mark.has-logo {
  font-style: normal;
  font-family: inherit;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  padding: 0;
  width: 40px;
  height: 40px;
  overflow: visible;
}
.colophon .colophon-mark.has-logo img {
  display: block;
  width: 40px;
  height: 40px;
  object-fit: contain;
  border-radius: var(--radius-sm);
}
.colophon .colophon-name {
  /* v2.14: generic small font, bold + readable. No uppercase, no fancy
     small-caps — a plain bold label that never truncates, so even a
     short name like "Hermes Institute" stays fully visible on mobile. */
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--ink);
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.25;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.colophon .colophon-bn {
  font-family: var(--font-bengali);
  font-size: var(--fs-2xs);
  color: var(--ink-mute);
  line-height: 1.2;
}

.topbar-search {
  flex: 1;
  max-width: 480px;
  position: relative;
}
.topbar-search input {
  width: 100%;
  height: 36px;
  padding: 0 var(--space-4) 0 36px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
}
.topbar-search input:focus {
  background: var(--surface);
  border-color: var(--marigold);
}
.topbar-search .ts-icon {
  position: absolute;
  left: 11px; top: 50%;
  transform: translateY(-50%);
  width: 16px; height: 16px;
  color: var(--ink-mute);
}
.topbar-search .ts-kbd {
  position: absolute;
  right: 8px; top: 50%;
  transform: translateY(-50%);
  display: flex;
  gap: 2px;
}

.topbar-actions {
  display: flex;
  flex-wrap: wrap;  /* IMPL-MOBILE: children wrap to a 2nd row on narrow
     screens instead of overflowing. The role-switcher + DEMO chip + health
     chip + bell + theme toggle + avatar are too many for one 360px row. */
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-1);
  margin-left: auto;
}

/* Role switcher */
.role-switcher {
  display: flex;
  background: var(--paper-soft);
  border-radius: var(--radius-md);
  padding: 2px;
  gap: 2px;
}
.role-switcher button {
  padding: 5px 11px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--ink-soft);
  cursor: pointer;
  text-transform: capitalize;
  transition: all var(--dur-fast) var(--ease-out);
}
.role-switcher button:hover:not(.is-active):not(:disabled) { color: var(--ink); }
.role-switcher button.is-active {
  background: var(--ink);
  color: var(--paper);
}
/* SHELL-10: disabled / loading state for role switcher buttons */
.role-switcher button:disabled,
.role-switcher button.is-loading {
  cursor: progress;
  opacity: 0.6;
}

/* SHELL-12: prominent DEMO badge — loud vermillion background so users
   don't miss that they're in demo mode (data isn't saved). */
.chip-demo {
  background: var(--vermillion);
  color: var(--paper);
  font-weight: 700;
  border: 0;
  padding: 3px 8px;
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* Avatar menu */
.avatar-menu {
  position: relative;
}
/* BUG-030-FIX: when the avatar menu is open, bump its z-index so the
   .menu-popover is guaranteed to appear above main#app-main and any
   other content. Without this, the dropdown could be clipped or
   hidden behind the main content area on some layouts. */
.avatar-menu.is-open {
  z-index: calc(var(--z-modal) + 1);
}
.avatar-menu .avatar { cursor: pointer; border: 2px solid transparent; transition: border-color var(--dur-fast) var(--ease-out); }
.avatar-menu:hover .avatar { border-color: var(--marigold); }
.avatar-menu .menu-popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-2);
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
  z-index: var(--z-modal);
}
.avatar-menu.is-open .menu-popover {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.menu-popover .menu-header {
  padding: var(--space-3);
  border-bottom: 1px solid var(--hairline);
  margin-bottom: var(--space-1);
}
.menu-popover .menu-header .mh-name {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
  color: var(--ink);
}
.menu-popover .menu-header .mh-id {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-mute);
  margin-top: 2px;
}
.menu-popover .menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  cursor: pointer;
  width: 100%;
  text-align: left;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.menu-popover .menu-item:hover { background: var(--paper-soft); color: var(--ink); }
.menu-popover .menu-item .icon { width: 16px; height: 16px; }
.menu-popover .menu-item.danger { color: var(--vermillion); }
.menu-popover .menu-item.danger:hover { background: var(--vermillion-soft); }
.menu-popover .menu-divider {
  height: 1px;
  background: var(--hairline);
  margin: var(--space-1) 0;
}

/* IMPL-MOBILE: Role section inside the avatar menu popover. Shown only
   on narrow viewports (rendered by chrome.js renderAvatarMenu when
   matchMedia('(max-width: 880px)').matches). The label is a small
   uppercase eyebrow; the list is a vertical stack of .menu-item rows
   (reuses the existing menu-item padding/hover). The active role gets
   an .is-role-active marker using the new --ink-surface / --on-ink-surface
   tokens (stays dark-bg/light-text in both themes — consistent with the
   rest of the intentional dark chrome migrated by IMPL-THEME). */
.menu-popover .menu-role-label {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-family: var(--font-body);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-faint);
}
.menu-popover .menu-role-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.menu-popover .menu-role-list .menu-item .icon {
  /* The leading check/circle icon should be subtle for inactive roles
     and accent-colored for the active role. */
  color: var(--ink-mute);
}
.menu-popover .menu-item.is-role-active {
  background: var(--ink-surface);
  color: var(--on-ink-surface);
  font-weight: var(--fw-semibold);
}
.menu-popover .menu-item.is-role-active .icon { color: var(--on-ink-surface); }
.menu-popover .menu-item.is-role-active:hover {
  background: var(--ink-surface);
  color: var(--on-ink-surface);
}

/* Notification bell + panel */
.notif-bell { position: relative; }
.notif-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 360px;
  max-height: 480px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  overscroll-behavior: contain;
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.notif-panel.is-open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.notif-panel .np-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--hairline);
}
.notif-panel .np-header h4 {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-mute);
}
.notif-panel .np-list { overflow-y: auto; flex: 1; }
.notif-panel .np-footer {
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--hairline);
  text-align: center;
  font-size: var(--fs-xs);
}

/* --- Sidebar ------------------------------------------------------- */
.app-sidebar {
  background: var(--sidebar-bg);
  border-right: 1px solid var(--topbar-border);
  padding: var(--space-4) 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  position: sticky;
  top: calc(var(--ribbon-h) + var(--topbar-h));
  height: calc(100vh - var(--ribbon-h) - var(--topbar-h));
}
.sidebar-group {
  margin-bottom: var(--space-4);
}
.sidebar-group .sg-label {
  padding: 0 var(--space-5);
  margin-bottom: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-faint);
}
.sidebar-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 8px var(--space-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--ink-soft);
  cursor: pointer;
  border-left: 2px solid transparent;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
  position: relative;
  text-decoration: none;
}
.sidebar-item:hover { background: var(--sidebar-hover-bg); color: var(--sidebar-hover-fg); text-decoration: none; }
.sidebar-item.is-active {
  background: var(--sidebar-active-bg);
  color: var(--sidebar-active-fg);
  border-left-color: var(--sidebar-active-border);
  font-weight: var(--fw-semibold);
}
.sidebar-item .icon {
  width: 16px; height: 16px;
  color: currentColor;
  flex-shrink: 0;
}
.sidebar-item .si-badge {
  margin-left: auto;
}

/* --- Main content area --------------------------------------------- */
.app-main {
  padding: var(--space-6) var(--space-8);
  max-width: var(--content-max);
  width: 100%;
  /* v2.31 (MOBILE-TAB-FLOW): `min-width: 0` is the other half of the fix —
     without it this grid item sizes to its content's min-content width and the
     page grows past the viewport on phones (see .app-shell above). With it,
     the column is exactly the viewport and wide children scroll inside their
     own containers instead of dragging the whole document sideways. */
  min-width: 0;
  margin: 0 auto;
}
/* The outlet never overflows the content column; wide screen content scrolls
   itself (`.tabs`, `.table-wrap`, `.routine-grid`), so nothing is clipped. */
.app-outlet { min-width: 0; max-width: 100%; }
.app-main.wide { max-width: none; }
/* Screen roots: the tab host + its content host are plain blocks, so they must
   be allowed to shrink below their children's intrinsic width too. */
.app-main > * { min-width: 0; }

@media (max-width: 1100px) {
  .app-main { padding: var(--space-5); }
}

/* --- Mobile sidebar drawer (hamburger) ------------------------------
   Hidden by default on desktop. The hamburger button and backdrop element
   are always injected by chrome.js, but only visible/active on mobile
   via the @media (max-width: 880px) block below. */
.topbar-menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 38px; height: 38px;
  background: transparent;
  border: 0;
  color: var(--ink);
  cursor: pointer;
  border-radius: var(--radius-md);
  flex-shrink: 0;
}
.topbar-menu-btn:hover { background: var(--paper-soft); }
.topbar-menu-btn .icon { width: 22px; height: 22px; }

.sidebar-backdrop {
  display: none;
}

.sidebar-close {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  background: transparent;
  border: 0;
  color: var(--ink-soft);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.sidebar-close:hover { background: var(--paper-soft); color: var(--ink); }
.sidebar-close .icon { width: 18px; height: 18px; }

/* --- Mobile bottom nav --------------------------------------------- */
.bottom-nav {
  display: none;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  background: var(--surface);
  border-top: 1px solid var(--hairline);
  z-index: var(--z-topbar);
  padding: var(--space-2) var(--space-3) calc(env(safe-area-inset-bottom, 0px) + var(--space-2));
}
.bottom-nav .bn-items {
  display: flex;
  justify-content: space-around;
  align-items: center;
  max-width: 600px;
  margin: 0 auto;
}
.bottom-nav .bn-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--space-2) var(--space-1);
  background: transparent;
  border: 0;
  color: var(--ink-mute);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
  position: relative;
}
.bottom-nav .bn-item.is-active { color: var(--nav-active-color); }
.bottom-nav .bn-item .icon { width: 22px; height: 22px; }
/* NAV-MOBILE-2: keep nav labels from pushing the bar wider than the phone. */
.bottom-nav .bn-item span:last-child {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bottom-nav .bn-item .bn-badge {
  position: absolute;
  top: 2px; right: calc(50% - 18px);
}

@media (max-width: 880px) {
  .app-shell {
    /* v2.31 (MOBILE-TAB-FLOW): keep the collapsed track shrinkable as well —
       see the .app-shell comment for why `1fr` alone blew the page out. */
    grid-template-columns: minmax(0, 1fr);
    /* IMPL-MOBILE: 3 rows — topbar (may wrap to 2 visual rows), ribbon
       status (DEM0 + health, 0-height when not rendered), main. The
       topbar row is `auto` so flex-wrap can grow it past --topbar-h. */
    grid-template-rows: auto auto 1fr;
    grid-template-areas:
      "topbar"
      "ribbon"
      "main";
    /* IMPL-MOBILE (v2.7 rebuild): reserve space at the bottom of the
       scrollable content so the fixed bottom-nav never overlaps the last
       row of a screen. The 72px matches the bottom-nav's intrinsic height
       (~52px) plus breathing room; the safe-area-inset-bottom addition
       ensures iPhones with a home indicator (34px inset) don't clip the
       last list row behind the nav bar. */
    padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px));
  }

  /* IMPL-MOBILE: mobile-only status ribbon. Rendered by chrome.js as a
     SIBLING of .app-topbar (inside .app-shell) when matchMedia detects a
     narrow viewport. Carries the DEMO + health chips that would otherwise
     crowd the topbar. Uses --paper-soft / --ink-soft (NOT --ink-surface)
     because this is a soft secondary bar, not a dark chrome surface. */
  .app-ribbon-status {
    grid-area: ribbon;
    display: flex; gap: var(--space-2); align-items: center; justify-content: center;
    padding: 4px var(--space-3);
    background: var(--paper-soft);
    border-bottom: 1px solid var(--hairline);
    font-size: var(--fs-2xs);
    color: var(--ink-soft);
  }
  .app-ribbon-status .ribbon-chip { display: inline-flex; align-items: center; gap: 4px; }
  .app-ribbon-status .ribbon-chip-demo { color: var(--marigold-deep); font-weight: 700; }
  .app-ribbon-status .ribbon-chip-health { color: var(--forest); }

  /* IMPL-MOBILE: hide the topbar-resident versions on mobile — the ribbon
     shows them. The .topbar-chip-demo / .topbar-chip-health marker classes
     are added by chrome.js so these selectors match the actual chip
     elements. Defense-in-depth: chrome.js also skips rendering them on
     mobile, but this rule covers the initial-paint + re-render edge cases. */
  .topbar-chip-demo, .topbar-chip-health { display: none; }
  /* IMPL-MOBILE: hide the role-switcher in the topbar on mobile — the
     avatar menu popover shows a vertical Role section instead. */
  .role-switcher { display: none; }
  /* IMPL-MOBILE: allow the avatar menu popover to shrink on small phones
     (was min-width: 220px which overflowed <300px viewports). The fixed
     positioning + left/right: 8px below already constrains width. */
  .menu-popover { min-width: 0; }

  /* IMPL-MOBILE (v2.7 rebuild): the sidebar is HIDDEN on mobile rather
     than transformed into a slide-out drawer. The previous drawer
     implementation required document.body.style.overflow locking (to
     prevent background scroll), which conflicted with the new "no body
     overflow manipulation" policy and could leave the page scroll-locked
     on iOS Safari if the drawer closed mid-animation. Mobile navigation
     is now handled exclusively by the bottom-nav (primary routes) + the
     more-sheet (overflow routes) — both are position:fixed overlays with
     their own scroll contexts, no body lock needed. The sidebar DOM is
     still rendered by renderSidebar() so a desktop↔mobile resize doesn't
     require a re-render, but it's visually display:none here. */
  .app-sidebar { display: none; }

  /* IMPL-MOBILE (v2.7 rebuild): the hamburger button (.topbar-menu-btn)
     is no longer rendered on mobile (chrome.js skips it entirely for all
     viewports now). The base rule above already sets display:none, so this
     override is a no-op — kept as explicit documentation that the
     hamburger is gone and the bottom-nav + more-sheet is the sole mobile
     navigation pattern. */
  .topbar-menu-btn { display: none; }

  /* IMPL-MOBILE (v2.7 rebuild): the sidebar-backdrop element is no longer
     injected by chrome.js (ensureSidebarDrawerBackdrop is not called).
     Keep display:none here as defense-in-depth in case a stale backdrop
     lingers in the DOM from a previous version cached in a user's
     browser. */
  .sidebar-backdrop { display: none; }

  /* IMPL-MOBILE (v2.7 rebuild): the sidebar-close button (rendered inside
     .app-sidebar by renderSidebar) is invisible on both desktop (base
     rule: display:none) and mobile (.app-sidebar itself is display:none).
     Keep this rule as documentation; the button is harmless dead DOM. */
  .sidebar-close { display: none; }

  .bottom-nav { display: block; }
  .colophon { min-width: 0; flex: 1; }
  /* SHELL-26: keep the school name visible on mobile — the Bengali
     subtitle is hidden and the name WRAPS (no ellipsis) so even a short
     name like "Hermes Institute" never gets truncated. */
  .colophon .colophon-bn { display: none; }
  .colophon .colophon-mark { width: 34px; height: 34px; }
  .colophon .colophon-mark.has-logo { width: 34px; height: 34px; }
  .colophon .colophon-mark.has-logo img { width: 34px; height: 34px; }
  .colophon .colophon-name {
    max-width: none;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    font-size: var(--fs-xs);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  /* On mobile, hide the inline topbar search — replaced by the search
     icon-button + full-screen overlay (.mobile-search-overlay). */
  .topbar-search { display: none; }
  .mobile-search-btn { display: inline-flex; }
  .app-main { padding: var(--space-4); }
  .app-topbar { padding: 0 var(--space-3); gap: var(--space-2); }
  /* Notification panel: clamp to viewport so it doesn't overflow on phones */
  .notif-panel {
    position: fixed;
    top: calc(var(--ribbon-h) + var(--topbar-h) + 8px);
    right: 8px;
    left: 8px;
    width: auto;
    max-width: 420px;
    margin-left: auto;
    max-height: min(70vh, 520px);
  }
  /* Avatar menu popover: clamp to viewport too */
  .avatar-menu .menu-popover {
    position: fixed;
    top: calc(var(--ribbon-h) + var(--topbar-h) + 8px);
    right: 8px;
    left: 8px;
    width: auto;
    max-width: 320px;
    margin-left: auto;
  }
  /* Role switcher: hidden on mobile (see .role-switcher { display: none }
     above) — the avatar menu popover shows a vertical Role section instead.
     This button-sizing rule only applies on >880px where the role-switcher
     is still shown. */
  .role-switcher button { padding: 4px 8px; font-size: var(--fs-2xs); }
}

/* --- More sheet (mobile nav overflow) ------------------------------ */
.more-sheet {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  background: var(--surface);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-drawer);
  z-index: var(--z-modal);
  transform: translateY(100%);
  transition: transform var(--dur-slow) var(--ease-out);
  max-height: min(75vh, calc(100dvh - 64px));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: var(--space-4) var(--space-2) calc(env(safe-area-inset-bottom, 0px) + var(--space-6));
}
.more-sheet.is-open { transform: translateY(0); }
.more-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  z-index: var(--z-modal);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}
.more-sheet-backdrop.is-open { opacity: 1; pointer-events: auto; }
.more-sheet .ms-handle {
  width: 40px; height: 4px;
  background: var(--hairline-strong);
  border-radius: var(--radius-pill);
  margin: 0 auto var(--space-4);
}
.more-sheet .ms-title {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-mute);
  padding: 0 var(--space-4);
  margin-bottom: var(--space-2);
}
.more-sheet .ms-grid {
  display: grid;
  /* NAV-MOBILE-2: auto-fill keeps the grid fully responsive — 4 columns on
     wide phones, fewer on narrow ones — so no item ever overflows. */
  grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
  gap: var(--space-2);
  padding: 0 var(--space-2);
}
.more-sheet .ms-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-1);
  min-height: 64px; /* comfortable touch target */
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  color: var(--ink-soft);
  font-size: var(--fs-2xs);
  cursor: pointer;
}
/* Long labels (e.g. "Academics Setup", "Salary Tracker") wrap instead of
   overflowing their cell. */
.more-sheet .ms-item span:last-child {
  text-align: center;
  line-height: 1.25;
  max-width: 100%;
  overflow-wrap: anywhere;
}
.more-sheet .ms-item:hover { background: var(--paper-soft); color: var(--ink); }
.more-sheet .ms-item .icon { width: 22px; height: 22px; }

/* --- Search results dropdown --------------------------------------- */
.search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0; right: 0;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  z-index: var(--z-modal);
  max-height: 420px;
  overflow-y: auto;
  display: none;
}
.search-results.is-open { display: block; }
.search-results .sr-section {
  padding: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.search-results .sr-section:last-child { border-bottom: 0; }
.search-results .sr-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-mute);
  padding: var(--space-2) var(--space-3);
}
.search-results .sr-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.search-results .sr-item:hover, .search-results .sr-item.is-focused { background: var(--paper-soft); }

/* SHELL-15: Mobile search overlay. On phones the topbar search input is
   hidden (no room); a search icon-button appears in the actions row
   which opens this full-screen overlay with the same search UI. */
.mobile-search-btn { display: none; }
.mobile-search-overlay {
  position: fixed;
  inset: 0;
  background: var(--surface);
  z-index: 10001;  /* above topbar */
  display: none;
  flex-direction: column;
  padding: var(--space-3);
}
.mobile-search-overlay.is-open { display: flex; }
.mobile-search-overlay .mso-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.mobile-search-overlay .mso-head .topbar-search {
  flex: 1;
  max-width: none;
  display: block;
}
.mobile-search-overlay .mso-results {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.mobile-search-overlay .search-results {
  position: static;
  display: block;
  box-shadow: none;
  border: 0;
  max-height: none;
}
.mobile-search-overlay .search-results:not(.is-open) { display: none; }

/* --- Public auth shell --------------------------------------------- */
.auth-shell {
  min-height: 100vh;
  min-height: 100svh;   /* v2.31 (MOBILE-TAB-FLOW): the SMALL viewport — no phantom scroll under the browser chrome */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding-top: var(--ribbon-h);
}
.auth-aside {
  background: var(--auth-aside-bg);
  color: var(--auth-aside-fg);
  padding: var(--space-12);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  position: relative;
  overflow: hidden;
}
.auth-aside::before {
  content: "";
  position: absolute;
  top: 0; right: 0;
  width: 4px; height: 100%;
  background: var(--ribbon-color);
}
.auth-aside .aa-mark {
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-semibold);
  font-style: italic;
  letter-spacing: -0.03em;
  line-height: 1;
}
.auth-aside .aa-mark .aa-product-logo { display:block;width:min(250px,100%);height:auto; }
/* v2.12: the login aside shows the ApexityEdu wordmark (same logo as the
   main page at /) instead of a straight line. The accent matches the
   landing page (Vec + marigold 'tis' + Edu). */
.auth-aside .aa-mark .accent { color: var(--brand-accent); font-style: italic; }
/* Tagline + Bengali line stay visible for ALL schools. */
.auth-aside.has-school .aa-tagline,
.auth-aside.has-school .aa-bn { display: block; }
.auth-aside .aa-foot { margin-top: var(--space-6); }
.auth-aside .aa-tagline {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  line-height: 1.3;
  margin-top: var(--space-4);
  max-width: 30ch;
}
.auth-aside .aa-bn {
  font-family: var(--font-bengali);
  color: var(--brand-accent);
  font-size: var(--fs-md);
  margin-top: var(--space-2);
}
.auth-aside .aa-foot {
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}
.auth-main {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-8) var(--space-6);
  background: var(--paper);
}
.auth-card {
  width: 100%;
  max-width: 400px;
}
.auth-card h1 {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  margin-bottom: var(--space-2);
}
.auth-card .auth-sub {
  color: var(--ink-mute);
  font-size: var(--fs-sm);
  margin-bottom: var(--space-6);
}
.auth-card .school-confirm {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--forest-soft);
  color: var(--forest);
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  margin-top: var(--space-2);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.auth-card .school-confirm.is-shown {
  opacity: 1;
  transform: translateY(0);
}
.auth-card .school-confirm .icon { width: 16px; height: 16px; }
.auth-card .auth-foot {
  margin-top: var(--space-6);
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--ink-mute);
}

/* Mobile school-name header - shown only on <880px (the aside is hidden and
   this block takes over as the centered logo + school name above the form).
   v2.12.2 (LOGIN-MOBILE): reworked so the logo sits centered on top, the
   school name on the next line, then the form. The logo host (#ams-logo) is
   populated by login.html JS (school <img>, circular initial, or the
   ApexityEdu wordmark when there's no school). */
.auth-mobile-school {
  display: none;  /* hidden on desktop; the mobile media query below shows it */
}
.auth-mobile-school .ams-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-xl);
  display: block;
  color: var(--ink);
  line-height: 1.2;
  word-break: break-word;
}
.auth-mobile-school .ams-bn {
  font-family: var(--font-bengali);
  color: var(--ink-mute);
  font-size: var(--fs-sm);
  display: block;
  margin-top: 4px;
}

@media (max-width: 880px) {
  .auth-shell { grid-template-columns: minmax(0, 1fr); }
  .auth-aside { display: none; }
  /* LOGIN-MOBILE (v2.12.2): the branding header is a centered column -
     logo on top, school name on the next line. It now shows for EVERY
     login (school OR no-school) instead of only when a school code is in
     the URL. */
  .auth-mobile-school {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 2px;
    width: 100%;
    max-width: 420px;
    margin: 0 auto var(--space-5);
  }
  /* LOGIN-MOBILE (v2.12.2): STACK the header above the form. The previous
     layout kept .auth-main as a ROW flex, so the header and the form tried
     to sit side by side - the form (width:100%) crowded the header out and
     the page looked broken / unresponsive on phones. Column + centered
     children gives the requested logo-on-top -> name -> form flow. */
  .auth-main {
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: var(--space-2);
    padding: var(--space-8) var(--space-5) var(--space-8);
    min-height: calc(100vh - var(--ribbon-h));
    /* v2.31 (MOBILE-TAB-FLOW): dvh so the form is never taller than what
       is actually visible while the URL bar is showing. */
    min-height: calc(100svh - var(--ribbon-h));
  }
  .auth-card { max-width: 420px; width: 100%; }
}

/* v2.12 (LOGIN-MOBILE): extra-robust small-phone layout — bigger touch
   targets, comfortable spacing, no horizontal overflow. */
@media (max-width: 480px) {
  .auth-main { padding: var(--space-5) var(--space-4); padding-top: var(--space-8); }
  .auth-card h1 { font-size: var(--fs-xl); }
  .auth-card .auth-sub { font-size: var(--fs-xs); margin-bottom: var(--space-4); }
  .auth-card input {
    min-height: 48px;
    font-size: var(--fs-md); /* prevents iOS zoom-on-focus */
  }
  .auth-card .btn-lg { min-height: 48px; width: 100%; }
  .auth-card .actions-row { flex-direction: column; gap: var(--space-3); align-items: stretch; text-align: center; }
  .auth-mobile-school { margin: 0 auto var(--space-4); }
  .auth-mobile-school .ams-name { font-size: var(--fs-lg); }
  .auth-foot { margin-top: var(--space-4); }
}

/* --- Superadmin shell ---------------------------------------------- */
.sa-shell {
  min-height: 100vh;
  min-height: 100svh;   /* v2.31 (MOBILE-TAB-FLOW): the SMALL viewport — no phantom scroll under the browser chrome */

  padding-top: var(--ribbon-h);
  background: var(--paper);
}
.sa-topbar {
  display: flex;
  flex-wrap: wrap;  /* IMPL-MOBILE: allow the superadmin topbar to wrap on
     481-880px tablets where the breadcrumb + username chip + theme toggle
     + sign-out would otherwise crowd one line. */
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-6);
  background: var(--ink-surface);
  color: var(--on-ink-surface);
  min-height: var(--topbar-h);  /* wrapping may grow the height; never
     shrink below the canonical topbar height. */
  position: sticky;
  top: var(--ribbon-h);
  z-index: var(--z-topbar);
}
.sa-topbar .sa-mark {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-size: var(--fs-md);
  font-weight: 800;
  font-style: normal;
  letter-spacing: -0.04em;
}
.sa-topbar .sa-mark-lockup { display:block;width:160px;height:auto; }
.sa-topbar .sa-mark-icon { display:inline-flex;width:30px;height:30px; }
.sa-topbar .sa-mark-icon svg { display:block;width:100%;height:100%; }
.sa-topbar .sa-mark .sa-accent { color: var(--marigold); }
.sa-topbar .sa-breadcrumb {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-faint);
}
.sa-topbar .sa-actions { margin-left: auto; display: flex; gap: var(--space-2); align-items: center; }
.sa-main {
  display: grid;
  grid-template-columns: 240px 1fr;
}
.sa-sidebar {
  background: var(--surface);
  border-right: 1px solid var(--hairline);
  padding: var(--space-4) 0;
  height: calc(100vh - var(--ribbon-h) - var(--topbar-h));
  position: sticky;
  top: calc(var(--ribbon-h) + var(--topbar-h));
  overflow-y: auto;
}
.sa-content {
  padding: var(--space-6) var(--space-8);
  max-width: 1280px;
}
@media (max-width: 880px) {
  .sa-main { grid-template-columns: 1fr; }
  .sa-content { padding: var(--space-4); }

  /* v2.6: tighten the topbar on mobile — less padding, hide the breadcrumb
     text (the "Control plane" label is redundant once the sidebar is open),
     keep the hamburger + ApexityEdu mark + actions visible. */
  .sa-topbar {
    padding: 0 var(--space-3);
    gap: var(--space-2);
  }
  .sa-topbar .sa-breadcrumb { display: none; }

  /* v2.6: Superadmin sidebar becomes a slide-out drawer on mobile.
     Mirrors the main-app .app-sidebar drawer rule above (L499-518).
     The .topbar-menu-btn / .sidebar-backdrop / .sidebar-close rules
     earlier in this same media query apply automatically because we
     reuse the same class names. */
  .sa-sidebar {
    position: fixed;
    top: calc(var(--ribbon-h) + var(--topbar-h));
    left: 0;
    bottom: 0;
    width: min(280px, 85vw);
    height: auto;
    max-height: calc(100vh - var(--ribbon-h) - var(--topbar-h));
    /* v2.31 (MOBILE-TAB-FLOW): dvh — with 100vh the last menu rows sat
       under the browser chrome and could not be reached. */
    max-height: calc(100dvh - var(--ribbon-h) - var(--topbar-h));
    background: var(--surface);
    border-right: 1px solid var(--hairline);
    box-shadow: var(--shadow-drawer);
    transform: translateX(-100%);
    transition: transform var(--dur-slow) var(--ease-out);
    z-index: var(--z-modal);
    overflow-y: auto;
    padding-top: var(--space-2);
    display: block;  /* override the old `display: none` */
  }
  .sa-sidebar.is-open { transform: translateX(0); }
}

/* IMPL-MOBILE: username-chip hide rule moved from 480px to 600px so the
   chip hides earlier (on small tablets / large phones, not just phones).
   The ApexityEdu mark + theme-toggle + sign-out stay visible. The sa-mark
   shrink rule stays at 480px (only needed on very narrow phones). */
@media (max-width: 600px) {
  .sa-topbar .sa-actions .chip { display: none; }  /* username chip hidden */
}
@media (max-width: 480px) {
  .sa-topbar .sa-mark { font-size: var(--fs-md); }
  .sa-topbar .sa-mark-lockup { width:140px; }
}

/* IMPL-MOBILE: Superadmin school-detail drawer footer. The 5 action buttons
   (Edit / Activate / Maintenance / Suspend / Delete) are wrapped in a
   .sa-drawer-footer div by superadmin.html's renderSchoolDetail so we can
   target them specifically. The outer .drawer-footer (from components.js
   openDrawer) provides the top border + padding; this inner wrapper handles
   the button row layout itself. */
.sa-drawer-footer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  flex: 1 1 100%;
  justify-content: flex-end;
}
/* IMPL-MOBILE: icon-only on small phones. The buttons are rendered by
   C.button() with icon + label-as-text-node. We hide the text node via
   font-size:0 — the SVG icons have explicit width/height attributes so
   they remain visible at their intrinsic size. The :has(svg) guard
   ensures we never hide the label of a text-only button (which would
   render it invisible). The .btn-label selector is also included as
   documented intent (no-op today since C.button doesn't wrap labels in
   .btn-label spans; if a future refactor does, this rule starts working). */
@media (max-width: 480px) {
  .sa-drawer-footer .btn .btn-label { display: none; }
  .sa-drawer-footer .btn:has(svg) { font-size: 0; padding: 8px 10px; }
  .sa-drawer-footer .btn:has(svg) .icon { font-size: initial; }
}

/* --- Setup wizard (superadmin first run) --------------------------- */
.setup-shell {
  min-height: 100vh;
  min-height: 100svh;   /* v2.31 (MOBILE-TAB-FLOW): the SMALL viewport — no phantom scroll under the browser chrome */

  padding-top: var(--ribbon-h);
  background: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(var(--ribbon-h) + var(--space-8)) var(--space-6);
}
.setup-card {
  width: 100%;
  max-width: 560px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
}
.setup-card .sc-step {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--marigold-deep);
  margin-bottom: var(--space-2);
}
.setup-card h1 {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  margin-bottom: var(--space-2);
}
.setup-card .sc-sub {
  color: var(--ink-mute);
  margin-bottom: var(--space-6);
  font-size: var(--fs-sm);
}

/* --- Public admission form shell ----------------------------------- */
.form-shell {
  min-height: 100vh;
  min-height: 100svh;   /* v2.31 (MOBILE-TAB-FLOW): the SMALL viewport — no phantom scroll under the browser chrome */

  padding-top: var(--ribbon-h);
  background: var(--paper);
}
.form-topbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--space-6);
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
  height: var(--topbar-h);
}
.form-topbar .ft-school {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}
.form-topbar .ft-school .ft-name {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.form-main {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--space-8) var(--space-6);
}

/* --- Print styles for receipts & certificates ----------------------
   When printing, we want ONLY the .print-area to appear. We do this by:
   1. Hiding everything except the modal/drawer host that contains the print-area
   2. Stripping the modal/drawer chrome (backdrop, header, footer)
   3. Making the print-area fill the page
   This works for print-areas inside modals (receipts, certificates,
   marksheets) as well as print-areas on a standalone page. */
@media print {
  /* Hide everything except the modal/drawer hosts (which contain print-areas)
     AND the .app-shell (so a user can also print the current screen's
     content directly — not just receipts/certificates inside modals). */
  body > *:not(.modal-host):not(.drawer-host):not(.app-shell) { display: none !important; }

  /* SHELL-28: when printing a screen directly (no modal open), strip the
     chrome — keep only the .app-main content. When a modal IS open the
     rules below continue to apply and the .app-shell stays hidden via the
     .no-print class on the modal mask / explicit overrides. */
  .app-shell {
    display: block !important;
    padding: 0 !important;
    min-height: 0 !important;
  }
  .app-topbar, .app-sidebar, .bottom-nav, .ribbon,
  .topbar-search, .mobile-search-overlay { display: none !important; }
  .app-main {
    padding: 24px !important;
    max-width: none !important;
    width: 100% !important;
    margin: 0 !important;
  }

  /* Reset modal/drawer hosts to static, full-page */
  .modal-host, .drawer-host {
    position: static !important;
    background: white !important;
    pointer-events: auto !important;
    opacity: 1 !important;
  }

  /* Reset modal/drawer wrappers — strip chrome */
  .modal, .drawer {
    position: static !important;
    transform: none !important;
    width: 100% !important;
    max-width: none !important;
    max-height: none !important;
    box-shadow: none !important;
    border: 0 !important;
    background: white !important;
    padding: 0 !important;
    margin: 0 !important;
    overflow: visible !important;
    opacity: 1 !important;
  }

  /* Hide backdrops, headers, footers */
  .modal-backdrop, .drawer-backdrop,
  .modal-header, .drawer-header,
  .modal-footer, .drawer-footer,
  .toast-host, .no-print, .print-loading { display: none !important; }

  /* Let the body flow naturally */
  .modal-body, .drawer-body {
    padding: 0 !important;
    overflow: visible !important;
    max-height: none !important;
  }

  /* Print-area fills the page */
  .print-area {
    padding: 24px !important;
    background: white !important;
    width: 100% !important;
  }

  body { background: white !important; }
  @page { margin: 1cm; }
}

/* v2.14 (PRINT-LOADING): full-screen overlay while bulk print jobs render.
   Never printed (it's hidden via .no-print in the @media print block). */
.print-loading {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: rgba(10, 10, 25, 0.55);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  animation: pl-fade var(--dur-fast) var(--ease-out);
}
@keyframes pl-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}
.print-loading .pl-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6) var(--space-8);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  text-align: center;
  max-width: 360px;
}
.print-loading .pl-spinner {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 4px solid var(--marigold-soft);
  border-top-color: var(--marigold-deep);
  animation: pl-spin 0.9s linear infinite;
}
@keyframes pl-spin {
  to { transform: rotate(360deg); }
}
.print-loading .pl-label {
  font-weight: var(--fw-semibold);
  color: var(--ink);
  font-size: var(--fs-md);
}
.print-loading .pl-sub {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
}

/* Receipt layout */
.receipt {
  background: var(--surface);
  border: 1px solid var(--hairline);
  padding: var(--space-6);
  max-width: 580px;
  margin: 0 auto;
}
.receipt .r-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  border-bottom: 2px solid var(--ink);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-4);
}
.receipt .r-school-name {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
}
.receipt .r-school-bn {
  font-family: var(--font-bengali);
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}
.receipt .r-meta { text-align: right; font-size: var(--fs-xs); }
.receipt .r-row {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  font-size: var(--fs-sm);
  border-bottom: 1px dashed var(--hairline);
}
.receipt .r-row .r-label { color: var(--ink-mute); }
.receipt .r-row .r-val { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.receipt .r-total {
  display: flex;
  justify-content: space-between;
  padding: var(--space-3) 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  border-top: 2px solid var(--ink);
  margin-top: var(--space-3);
}
.receipt .r-foot {
  margin-top: var(--space-6);
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--ink-mute);
}

/* --------------------------------------------------------------------------
   Search polish — app topbar search (2026-08)
   Makes the global topbar search feel like a command palette: larger input,
   focused glow, elevated results with avatars and clear hierarchy.
   -------------------------------------------------------------------------- */
.topbar-search input {
  height: 38px;
  border-radius: 12px;
  background: var(--surface);
  border: 1.5px solid var(--hairline);
  box-shadow: 0 1px 2px rgba(20,20,43,0.04);
  transition: border-color 150ms ease, box-shadow 150ms ease, background 150ms ease;
}
.topbar-search input::placeholder { color: var(--ink-faint); }
.topbar-search input:hover { border-color: var(--hairline-strong); }
.topbar-search input:focus {
  background: var(--surface);
  border-color: var(--marigold);
  box-shadow: 0 0 0 3px var(--marigold-soft), var(--shadow-sm);
  outline: none;
}
.search-results {
  border-radius: 14px;
  border: 1px solid var(--hairline);
  box-shadow: 0 12px 32px rgba(20,20,43,0.14), 0 2px 8px rgba(20,20,43,0.06);
  overflow: hidden;
  animation: sr-in 140ms ease-out;
}
@keyframes sr-in { from { opacity:0; transform: translateY(4px) scale(0.98); } to { opacity:1; transform: translateY(0) scale(1); } }
.search-results .sr-label {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  background: var(--paper-soft);
  border-bottom: 1px solid var(--hairline);
  padding: 8px 14px;
}
.search-results .sr-item {
  padding: 10px 14px;
  gap: 12px;
  transition: background 120ms ease;
  border-radius: 0;
}
.search-results .sr-item + .sr-item { border-top: 1px solid var(--hairline-light); }
.search-results .sr-item:hover,
.search-results .sr-item:focus-visible {
  background: var(--marigold-soft);
  outline: none;
}
.search-results .sr-item .avatar { width: 32px; height: 32px; font-size: 13px; }
