/* =====================================================================
   PES Support — shared design system
   ---------------------------------------------------------------------
   This file is the single source of truth for design tokens (colors,
   spacing, radius, shadows) and every reusable component class (buttons,
   cards, tables, forms, badges, chips, modal, toast, sidebar/topbar
   layout). Page-specific rules belong in static/css/<app>/style.css and
   should only ever build on top of the classes defined here — never
   redefine a token.

   Theme switching uses the project's existing convention: the
   `data-bs-theme="light|dark"` attribute on <html>, persisted under the
   `pes-theme` localStorage key (see base.html / dashboard_base.html).
   ===================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Sora:wght@600;700;800&display=swap');

:root{
  /* Palette matches the pesu-events-dashboard.html reference exactly — this
     also happens to be this app's own original palette (only --radius,
     --vip/--vip-100 and the fonts below are actually new from the reference). */
  --primary:#00377B;
  --primary-600:#002A5E;
  --primary-100:#EEEEFD;
  --primary-text:var(--primary);
  --success:#17C671;
  --success-100:#E8FBF1;
  --warning:#FF9533;
  --warning-100:#FEF6E6;
  --danger:#F1416C;
  --danger-100:#FDEEF2;
  --info:#17A2E0;
  --info-100:#E8F8FE;
  --vip:#B8860B;
  --vip-100:#FBF3E0;

  --bg:#F5F6FB;
  --surface:#FFFFFF;
  --surface-2:#FAFBFE;
  --border:#E8EAF3;
  --text:#1B1E2B;
  --muted:#7C8098;
  --muted-2:#A1A5B7;
  --sidebar-bg:#FFFFFF;
  --shadow: 0 2px 10px rgba(24,28,50,0.06), 0 1px 2px rgba(24,28,50,0.05);
  --shadow-lg: 0 12px 32px rgba(24,28,50,0.10);
  --radius: 16px;

  --sidebar-width: 264px;
  --sidebar-width-collapsed: 84px;
  --topbar-height: 70px;
}

html[data-bs-theme="dark"]{
  --bg:#14161F;
  --surface:#1B1E2B;
  --surface-2:#20222F;
  --border:#2A2D3D;
  --text:#EDEEF5;
  --muted:#8A8DA3;
  --muted-2:#63667A;
  --primary-100:#25264A;
  --success-100:#123326;
  --warning-100:#332A14;
  --danger-100:#3A1C28;
  --info-100:#122C38;
  --vip:#E0B84B;
  --vip-100:#332C15;
  --shadow: 0 2px 8px rgba(0,0,0,0.25);
  --shadow-lg: 0 16px 40px rgba(0,0,0,0.45);
  /* --primary itself stays the same navy in dark mode (buttons rely on
     that), but that navy is unreadable as body-copy text on a dark
     surface — this token is for text-color usages only. */
  --primary-text:#5EA3FF;
}

/* ---------------------------- reset / base ---------------------------- */
*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; }
body{
  font-family:'Inter',sans-serif;
  background:var(--bg);
  color:var(--text);
  -webkit-font-smoothing:antialiased;
  transition:background .25s ease,color .25s ease;
}
body:has(.public-header){ background:#ffffff; }
h1,h2,h3,h4,.brand{ font-family:'Sora',sans-serif; }
h1,h2,h3,h4{ margin:0; color:var(--text); }
a{ color:inherit; text-decoration:none; }
button{ font-family:inherit; color:inherit; }
img{ max-width:100%; }
::selection{ background:var(--primary); color:#fff; }

/* Thin, theme-matching scrollbar everywhere (page scroll, table-wrap,
   dropdown menus) instead of the browser/OS's default chunky scrollbar. */
*{ scrollbar-width:thin; scrollbar-color:var(--border) transparent; }
::-webkit-scrollbar{ width:9px; height:9px; }
::-webkit-scrollbar-track{ background:transparent; }
::-webkit-scrollbar-thumb{ background-color:var(--border); border-radius:20px; }
::-webkit-scrollbar-thumb:hover{ background-color:var(--muted); }

/* ------------------------------- layout -------------------------------- */
.app{ display:flex; min-height:100vh; }

.sidebar{
  width:var(--sidebar-width); flex-shrink:0; background:var(--bg);
  border-right:none;
  display:flex; flex-direction:column;
  position:fixed; top:0; left:0; bottom:0; z-index:40;
  transition:transform .25s ease, width .2s ease, background .25s ease;
}
.sidebar.collapsed{ width:var(--sidebar-width-collapsed); }
.sidebar.dark{ background:var(--primary); }

.main{
  margin-left:var(--sidebar-width); flex:1; display:flex; flex-direction:column;
  min-width:0; transition:margin-left .2s ease;
}
.sidebar.collapsed ~ .main{ margin-left:var(--sidebar-width-collapsed); }

.topbar{
  height:var(--topbar-height); background:var(--bg);
  display:flex; align-items:center; gap:16px; padding:0 26px;
  position:sticky; top:0; z-index:30;
}

.content{ padding:45px 30px 60px; max-width:1360px; width:100%; margin:0 auto; }

.content-shell{
  /* No isolation:isolate — that would trap descendant modals' z-index inside
     this element's own stacking context, so they could never paint above
     the sidebar/topbar (which sit outside it with their own z-index). */
  position:relative; margin:0 22px 22px; border:1px solid var(--border);
  border-radius:26px; box-shadow:var(--shadow-lg); overflow:hidden;
  flex:1; display:flex; flex-direction:column;
  /* Distinct from .card's solid --surface white/dark, so cards actually
     stand out instead of blending into the shell behind them. */
  background:linear-gradient(180deg, var(--surface-2) 0%, var(--bg) 100%);
}
.content-shell::before{
  content:""; position:absolute; top:-120px; right:-100px; width:320px; height:320px; z-index:0;
  background:radial-gradient(circle, color-mix(in srgb, var(--primary) 10%, transparent) 0%, transparent 70%);
  pointer-events:none;
}
.content-shell .content{ flex:1; position:relative; z-index:1; }

.overlay{ position:fixed; inset:0; background:rgba(0,0,0,.4); z-index:35; display:none; }
.overlay.show{ display:block; }

/* ------------------------------ sidebar -------------------------------- */
.sidebar-brand{ display:flex; align-items:center; gap:10px; padding:18px 22px 14px; }
.sidebar-brand img.logo-mark{ width:36px; height:36px; border-radius:10px; object-fit:contain; flex-shrink:0; }
.sidebar-brand img.logo-full{ height:34px; width:auto; display:block; }
.sidebar.collapsed .sidebar-brand{ padding:18px 0 14px; justify-content:center; }
.sidebar.collapsed .sidebar-brand img.logo-full{ display:none; }
.sidebar:not(.collapsed) .sidebar-brand img.logo-mark{ display:none; }
html[data-bs-theme="dark"] .sidebar-brand img.logo-full-light{ display:none; }
html:not([data-bs-theme="dark"]) .sidebar-brand img.logo-full-dark{ display:none; }
html[data-bs-theme="dark"] .sidebar-brand img.logo-mark-light{ display:none; }
html:not([data-bs-theme="dark"]) .sidebar-brand img.logo-mark-dark{ display:none; }

.sidebar-scroll{ flex:1; overflow-y:auto; padding:6px 14px 20px; }
.nav-heading{
  font-size:11px; font-weight:700; letter-spacing:.09em; color:var(--muted-2);
  text-transform:uppercase; padding:16px 12px 8px;
}
.sidebar.collapsed .nav-heading{ display:none; }

.nav-item{
  display:flex; align-items:center; gap:12px; width:100%;
  padding:9px 12px; margin:2px 0; border-radius:8px; border:none; background:transparent;
  color:var(--muted); font-size:14px; font-weight:500; text-align:left;
  cursor:pointer; position:relative; transition:background .15s ease,color .15s ease;
}
.nav-item svg{ width:18px; height:18px; flex-shrink:0; }
.nav-item:hover{ background:var(--surface-2); color:var(--text); }
.nav-item.active{ background:var(--primary-100); color:var(--primary-text); font-weight:700; }
.sidebar.dark .nav-item{ color:rgba(255,255,255,.82); }
.sidebar.dark .nav-item:hover{ background:rgba(255,255,255,.08); color:#fff; }
.sidebar.dark .nav-item.active{ background:rgba(255,255,255,.14); color:#fff; }
.sidebar.collapsed .nav-item{ justify-content:center; }
.sidebar.collapsed .nav-item span.label,
.sidebar.collapsed .nav-item .nav-badge,
.sidebar.collapsed .nav-group-btn svg.chev{ display:none; }
.sidebar.collapsed .nav-children{ display:none !important; }

.nav-badge{
  margin-left:auto; background:var(--danger); color:#fff; font-size:10.5px;
  font-weight:700; padding:1px 7px; border-radius:20px;
}
.nav-children{
  margin-left:14px; padding-left:14px; border-left:1px solid var(--border);
  display:flex; flex-direction:column; gap:2px; margin-bottom:6px;
}
.sidebar.dark .nav-children{ border-left-color:rgba(255,255,255,.14); }
.nav-children .nav-item{ padding:7px 10px; font-size:12.8px; }

.nav-group-btn svg.chev{ width:14px; height:14px; margin-left:auto; flex-shrink:0; }
.nav-new-btn{
  display:flex; align-items:center; gap:8px; width:100%; margin-top:2px;
  padding:7px 10px; border-radius:8px; border:1px dashed var(--primary-text); background:transparent;
  color:var(--primary-text); font-size:12.2px; font-weight:700; cursor:pointer;
}
.nav-new-btn svg{ width:12px; height:12px; flex-shrink:0; }
.sidebar.dark .nav-new-btn{ border-color:rgba(255,255,255,.4); color:rgba(255,255,255,.9); }
.nav-new-btn.active{ background:var(--primary); border-style:solid; border-color:var(--primary); color:#fff; }
.sidebar.dark .nav-new-btn.active{ background:rgba(255,255,255,.16); border-color:transparent; color:#fff; }

/* "More below" cue — same bouncing-chevron idea as the homepage hero's own
   scroll-down indicator (.hero-ed-scroll), just shorter travel to suit a
   narrow sidebar. JS (initSidebar in scripts.js) toggles .show only while
   .sidebar-scroll actually has content scrolled out of view below, and
   clicking it scrolls the nav list down (see the same click handler). */
.sidebar-scroll-hint{
  display:flex; justify-content:center; padding:2px 0; color:var(--muted); flex-shrink:0;
  width:100%; border:none; background:transparent; font:inherit; cursor:pointer;
  opacity:0; pointer-events:none; transition:opacity .2s ease, color .15s ease;
}
.sidebar-scroll-hint:hover{ color:var(--text); }
.sidebar-scroll-hint svg{ width:15px; height:15px; }
.sidebar-scroll-hint.show{ opacity:1; pointer-events:auto; animation:sidebarScrollBounce 1.8s ease-in-out infinite; }
.sidebar.collapsed .sidebar-scroll-hint{ display:none; }
@keyframes sidebarScrollBounce{ 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(4px); } }
@media (prefers-reduced-motion:reduce){ .sidebar-scroll-hint.show{ animation:none; } }

.sidebar-footer{ padding:14px; }
.sidebar.collapsed .sidebar-footer{ display:none; }
.upgrade-card{
  background:linear-gradient(135deg,var(--primary),#001F4A);
  border-radius:14px; padding:16px; color:#fff;
}
.upgrade-card h4{ margin:0 0 4px; font-size:14px; color:#fff; }
.upgrade-card p{ margin:0 0 12px; font-size:12px; opacity:.85; line-height:1.4; }
.upgrade-card button{
  background:#fff; color:var(--primary-600); border:none; border-radius:999px;
  padding:7px 12px; font-size:12.5px; font-weight:700; cursor:pointer; width:100%;
}
.sidebar-footer-note{ padding:10px 12px 0; font-size:11px; color:var(--muted); }
.sidebar.dark .sidebar-footer-note{ color:rgba(255,255,255,.5); }
.sidebar.collapsed .sidebar-footer-note{ display:none; }

/* ------------------------------- topbar -------------------------------- */
.search-box{
  flex:1; max-width:420px; display:flex; align-items:center; gap:10px;
  background:var(--surface-2); border:1px solid var(--border); border-radius:999px;
  padding:9px 16px; color:var(--muted); position:relative;
}
.search-box input[type=text]{
  border:none; background:transparent; outline:none; flex:1; padding:0;
  font-size:13.5px; color:var(--text); border-radius:0;
}
.search-box input[type=text]:focus{ border:none; background:transparent; }
.search-box svg{ width:16px; height:16px; flex-shrink:0; display:block; }
/* .icon-btn (defined later, under topbar-right) also sets display:flex —
   needs matching .icon-btn.search-mobile-btn specificity here to actually
   win, a plain .search-mobile-btn{display:none} would otherwise lose that
   tie-break regardless of viewport width. */
.icon-btn.search-mobile-btn{ display:none; }
@media(max-width:820px){ .icon-btn.search-mobile-btn{ display:flex; } }
.search-modal{ max-width:520px; }
.search-modal .search-box{ max-width:none; }

/* Dashboard topbar's own round-logo mark — only relevant on mobile, where
   the sidebar (which normally carries the brand) is off-canvas and hidden. */
.topbar-brand{ display:none; align-items:center; }
.topbar-brand img.logo-mark{ width:32px; height:32px; border-radius:8px; object-fit:contain; }
html[data-bs-theme="dark"] .topbar-brand img.logo-mark-light{ display:none; }
html:not([data-bs-theme="dark"]) .topbar-brand img.logo-mark-dark{ display:none; }
@media(max-width:820px){ .topbar-brand{ display:flex; } }

.topbar-right{ display:flex; align-items:center; gap:8px; margin-left:auto; }
@media(max-width:820px){
  /* Sidebar-toggle hamburger moves from the far left to the far right on
     mobile, past the whole topbar-right cluster — search now lives inside
     .topbar-right itself (see header.html), right before the theme toggle,
     so the final left-to-right order is:
     logo → [search, theme, quick, notif, avatar] → hamburger. */
  .topbar-right{ order:2; }
  /* A little extra clearance from the true screen edge — .icon-btn's solid
     border reads as "flush against the edge" more than a rounded/padded
     pill would at the exact same container inset, so it gets a bit more
     room than .topbar's own gap alone gives it. */
  #sidebarToggleBtn{ order:3; margin-right:4px; }
}
.icon-btn{
  width:40px; height:40px; border-radius:50%; border:1px solid var(--border); background:var(--surface);
  display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--muted); position:relative;
  transition:background .15s ease,color .15s ease; flex-shrink:0;
}
.icon-btn:hover{ background:var(--surface-2); color:var(--text); }
.icon-btn svg{ width:18px; height:18px; }
.dot{
  position:absolute; top:8px; right:9px; width:7px; height:7px; border-radius:50%; background:var(--danger);
  border:1.5px solid var(--surface);
}
.user-chip{
  display:flex; align-items:center; gap:4px; padding:3px; border-radius:999px;
  border:1px solid var(--border); cursor:pointer; background:var(--surface); color:var(--text);
}
.avatar{
  width:32px; height:32px; border-radius:50%; background:linear-gradient(135deg,var(--primary),#001F4A);
  display:flex; align-items:center; justify-content:center; color:#fff; font-weight:700; font-size:13px; flex-shrink:0;
}
.user-chip svg{ flex-shrink:0; margin-right:4px; }

/* Single stacked icon (sun OR moon, never both) in one .icon-btn-shaped
   square — both [data-theme-set] buttons stay in the DOM absolutely
   positioned on top of each other, and only the *inactive* one (the theme
   you'd switch TO) is shown/clickable, so the toggle reads as one same-
   sized control next to search/quick-actions/notifications instead of a
   wider two-icon segmented pill. Same sizing as .icon-btn (40px desktop,
   32px at <=640px — see the responsive section at the end of this file)
   so every button in the topbar cluster is visually one consistent size. */
.theme-toggle{
  position:relative; width:40px; height:40px; border-radius:50%; flex-shrink:0;
  background:var(--surface); border:1px solid var(--border);
}
.theme-toggle:hover{ background:var(--surface-2); }
.theme-toggle button{
  position:absolute; inset:0; width:100%; height:100%; border:none; background:none; border-radius:50%;
  display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--muted);
  opacity:0; visibility:hidden; transition:opacity .15s ease, color .15s ease;
}
.theme-toggle button svg{ width:18px; height:18px; }
.theme-toggle button:not(.active){ opacity:1; visibility:visible; }
.theme-toggle:hover button:not(.active){ color:var(--text); }

/* ---------------------------- public site header ------------------------ */
/* Rendered by main/base.html's {% block header %} — shared by every public
   page (home, public webforms, login, error pages), not just the home page. */

/* Secondary utility bar above the main navbar, cloned from news.pes.edu's
   cross-site strip — same real PES destinations regardless of which PES
   sub-site you're on. */
.public-utility-bar{ background:var(--primary-600); color:#fff; }
.public-utility-inner{
  width:100%; max-width:1280px; margin:0 auto; padding:6px 40px; display:flex; align-items:center; justify-content:space-between; gap:16px;
  font-size:12px;
}
.utility-back{ display:inline-flex; align-items:center; gap:4px; color:rgba(255,255,255,.85); font-weight:700; white-space:nowrap; }
.utility-back:hover{ color:#fff; }
.utility-back svg{ width:13px; height:13px; }
.utility-links{ display:flex; align-items:center; gap:20px; }
.utility-link{ display:inline-flex; align-items:center; color:rgba(255,255,255,.85); font-weight:500; white-space:nowrap; }
.utility-link:hover{ color:#fff; }
.utility-divider{ width:1px; height:14px; background:rgba(255,255,255,.25); }
.utility-admissions-btn{
  display:inline-flex; align-items:center; gap:5px; padding:5px 14px; border-radius:999px;
  background:var(--warning); color:#fff; font-weight:700; white-space:nowrap; transition:filter .15s ease; font-size:11.5px;
}
.utility-admissions-btn:hover{ filter:brightness(1.06); }
.utility-admissions-btn svg{ width:13px; height:13px; }

/* Orange text + underline (not a solid navy pill) for whichever nav item is
   "current" — matches the PES Support reference's own active nav treatment.
   Two things drive .active here, both already wired up before this change
   and untouched: select_public_nav in each page's own view context (see
   public/base.html's {% if select_public_nav == '...' %}) for a plain link
   like Calendar/Guests, and initMegaMenus's setActiveTrigger (scripts.js)
   toggling the SAME class on whichever Campus/Department/Tags trigger's
   mega-panel is currently open — so a mega-menu counts as "selected" here
   for free, without extra JS. */
.public-nav-link.active,
.public-nav-link.active:hover{ background:transparent; color:var(--primary-text); }
.public-nav-link.active::after{
  content:""; position:absolute; left:16px; right:16px; bottom:2px; height:2px; background:var(--primary-text); border-radius:2px;
}

/* Full-width mega dropdown (per-campus quick links), cloned from
   news.pes.edu's "Browse by X" panels. Reparented to <body> and positioned
   with JS (see initMegaMenus in scripts.js) — same reasoning as the modal
   reparenting in initModals(): no ancestor's position/transform can then
   trap it away from spanning the full viewport width. */
.mega-panel{
  position:fixed; left:0; right:0; z-index:300; background:var(--surface);
  border-top:1px solid var(--border); border-bottom:1px solid var(--border);
  box-shadow:var(--shadow-lg); display:none; max-height:80vh; overflow-y:auto;
}
.mega-panel.show{ display:block; animation:megaPanelIn .22s cubic-bezier(.22,.85,.32,1); }
@keyframes megaPanelIn{ from{ opacity:0; transform:translateY(-10px); } to{ opacity:1; transform:none; } }
@media (prefers-reduced-motion:reduce){ .mega-panel.show{ animation:none; } }
/* Background spans the full width (like the header above it); the actual
   content lines up with the header/page content's own max-width instead of
   stretching edge-to-edge. */
.mega-panel-inner{
  max-width:1280px; margin:0 auto; display:grid; grid-template-columns:270px 1fr; min-height:230px;
}
/* Left: the campus's own photo (or its brand color as a flat fallback)
   with its name overlaid — right: the quick-links grid. */
.mega-panel-visual{
  position:relative; padding:24px; display:flex; flex-direction:column; justify-content:flex-end;
  /* This panel has no single campus to photograph (it's the "choose a
     campus" overview, not one campus's own page) — the same navy gradient
     used by the other brand-level decorative panels (hero bands, auth
     screens) stands in as its background instead of a photo. */
  background:linear-gradient(150deg, #061428 0%, var(--primary-600) 55%, var(--primary) 100%);
  background-size:cover; background-position:center; color:#fff; overflow:hidden;
}
.mega-panel-visual::before{
  content:""; position:absolute; inset:0; background:linear-gradient(200deg, rgba(0,0,0,0) 30%, rgba(0,0,0,.68) 100%);
}
.mega-panel-visual-icon{
  position:relative; z-index:1; width:40px; height:40px; border-radius:11px; margin-bottom:auto;
  background:rgba(255,255,255,.2); backdrop-filter:blur(4px); display:flex; align-items:center; justify-content:center;
}
.mega-panel-visual-icon svg{ width:18px; height:18px; }
.mega-panel-visual-title{ position:relative; z-index:1; font-size:20px; font-weight:800; margin:0 0 4px; color:#fff; }
.mega-panel-visual-sub{ position:relative; z-index:1; font-size:12px; color:rgba(255,255,255,.82); }

.mega-panel-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:14px; padding-right:32px; }
.mega-item{
  display:flex; align-items:center; gap:12px; padding:16px 18px; border-radius:14px; border:1px solid var(--border);
  background:var(--surface); transition:border-color .2s ease, background .2s ease, transform .15s ease, box-shadow .2s ease;
}
.mega-item:hover{ border-color:var(--accent, var(--primary)); background:var(--surface-2); transform:translateY(-2px); box-shadow:var(--shadow); }
.mega-item.active{ border-color:var(--accent, var(--primary)); background:var(--accent-100, var(--primary-100)); }
.mega-item-icon{
  width:40px; height:40px; border-radius:11px; flex-shrink:0; display:flex; align-items:center; justify-content:center;
  background:var(--accent-100, var(--primary-100)); color:var(--accent, var(--primary-text));
}
.mega-item-icon svg{ width:18px; height:18px; }
.mega-item-body{ min-width:0; display:flex; flex-direction:column; gap:2px; }
.mega-item-title{ font-size:13.5px; font-weight:700; color:var(--text); }
.mega-item-sub{ font-size:11.5px; color:var(--muted); }
@media(max-width:760px){
  .mega-panel-inner{ grid-template-columns:1fr; }
  .mega-panel-visual{ padding:20px 24px; min-height:110px; }
  .mega-panel-grid{ padding-right:0; }
}

/* Full-screen search overlay toggled by #publicSearchToggleBtn (see
   initPublicSearchToggle in scripts.js) — cloned from news.pes.edu's dark
   glassmorphic command-palette search: this panel is deliberately always
   dark-navy-glass regardless of the site's own light/dark toggle, same as
   the hero section. Adapted with a "quick filters" row of campuses instead
   of their tag/press-note filters (no tag taxonomy in this app). */
/* Pixel-matched to the "PES Support" search-overlay reference (search.html)
   — flex-column root + padding-top:8vh (not the old fixed margin-top on the
   panel) so the panel is always vertically anchored the same fraction of
   the viewport down, same mechanism as that reference's own .srch-overlay.
   Kept the opacity/visibility toggle (not display:none/block) for the same
   reason the reference does — it's what lets .search-overlay-panel's own
   transform/opacity transition (below) actually animate in on open. */
.search-overlay{
  position:fixed; inset:0; z-index:500; display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
  padding-top:8vh; opacity:0; visibility:hidden; transition:opacity .22s ease, visibility .22s ease;
}
.search-overlay.show{ opacity:1; visibility:visible; }
.search-overlay.show .search-overlay-panel{ transform:translateY(0) scale(1); opacity:1; }
.search-overlay-backdrop{ position:absolute; inset:0; background:rgba(0,8,24,.82); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px); }
body.search-overlay-open{ overflow:hidden; }
body.mm-open{ overflow:hidden; }
/* No card/box around this — just the field and the filter chips floating
   directly over the dimmed, blurred backdrop. */
.search-overlay-panel{
  position:relative; z-index:1; width:100%; max-width:660px; padding:0 20px 40px;
  transform:translateY(-18px) scale(.97); opacity:0; transition:transform .32s cubic-bezier(.34,1.56,.64,1), opacity .22s ease;
}
@media (prefers-reduced-motion:reduce){ .search-overlay-panel{ transition:none; } }
.search-overlay-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; }
.search-overlay-brand{ display:flex; align-items:center; gap:8px; font-size:11.5px; font-weight:800; letter-spacing:.07em; text-transform:uppercase; color:rgba(255,255,255,.35); }
.search-overlay-brand svg{ width:14px; height:14px; color:#ef7e1a; }
.search-overlay-close{
  display:flex; align-items:center; gap:8px; border:1px solid rgba(255,255,255,.16); background:rgba(255,255,255,.08);
  border-radius:9px; padding:6px 14px; cursor:pointer; color:rgba(255,255,255,.7);
}
.search-overlay-close:hover{ color:#fff; background:rgba(255,255,255,.18); }
.search-overlay-close svg{ width:14px; height:14px; }
.kbd{
  font-size:10.5px; font-weight:700; padding:2px 6px; border-radius:5px;
  background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.18); color:rgba(255,255,255,.75);
}
.search-overlay-close kbd{ background:rgba(255,255,255,.1); border-color:rgba(255,255,255,.18); color:inherit; }

.search-overlay-form{
  display:flex; align-items:center; gap:10px; background:#fff; border-radius:999px; overflow:hidden;
  padding:6px 6px 6px 22px; margin-bottom:22px; box-shadow:0 24px 64px rgba(0,0,0,.35), 0 4px 16px rgba(0,0,0,.2);
}
.search-overlay-form svg{ width:18px; height:18px; color:var(--muted); flex-shrink:0; }
/* input[type=search] renders its own native rounded box + border in some
   browsers unless fully reset — left as-is it visually splits into two
   boxes (a small bordered one nested inside the pill), pushing the search
   icon out into the gap to its left. */
.search-overlay-form input{
  flex:1; min-width:0; border:none; outline:none; background:transparent; -webkit-appearance:none; appearance:none;
  box-shadow:none; font-size:18px; font-family:inherit; color:#1a1a2e; padding:10px 14px;
}
.search-overlay-form input::-webkit-search-decoration,
.search-overlay-form input::-webkit-search-cancel-button,
.search-overlay-form input::-webkit-search-results-button,
.search-overlay-form input::-webkit-search-results-decoration{
  -webkit-appearance:none;
}
.search-overlay-submit{
  width:50px; height:50px; border-radius:50%; border:none; background:var(--primary); color:#fff;
  display:flex; align-items:center; justify-content:center; cursor:pointer; flex-shrink:0;
  transition:background .2s ease, transform .15s ease;
}
.search-overlay-submit:hover{ background:#ef7e1a; transform:scale(1.06); }
.search-overlay-submit svg{ width:18px; height:18px; }

.search-overlay-section-label{
  display:flex; align-items:center; gap:6px; font-size:11px; font-weight:800; letter-spacing:.6px; text-transform:uppercase;
  color:rgba(255,255,255,.4); margin-bottom:12px;
}
.search-overlay-section-label svg{ width:13px; height:13px; color:#ef7e1a; }
.search-overlay-filters{ display:flex; flex-wrap:wrap; gap:8px; }
.search-overlay-filter{
  display:inline-flex; align-items:center; gap:7px; padding:8px 14px; border-radius:999px;
  border:1px solid rgba(255,255,255,.14); background:rgba(255,255,255,.07);
  font-size:12.5px; font-weight:700; color:rgba(255,255,255,.85);
  transition:border-color .15s ease, background .15s ease, color .15s ease, transform .15s ease;
}
.search-overlay-filter svg{ width:13px; height:13px; flex-shrink:0; }
.search-overlay-filter:hover{ border-color:rgba(255,255,255,.35); background:rgba(255,255,255,.14); color:#fff; transform:translateY(-2px); }
/* Tag chips (Popular tags) read one notch quieter than the quick-filter
   chips above them — smaller/dimmer at rest, same hover brighten — matching
   the reference's own two-tier hierarchy between its .srch-chip and
   .srch-chip-tag. */
.search-overlay-filter.is-tag{ font-size:11.5px; color:rgba(255,255,255,.5); }
/* Quick-filter accent chips (Live now / Browse all events / Upcoming
   events) — same red/orange/navy recipe as the reference's own
   .srch-chip-red/-orange/-navy, ahead of the plain per-campus chips. */
.search-overlay-filter.accent-red{ background:rgba(220,38,38,.2); border-color:rgba(220,38,38,.3); color:#fca5a5; }
.search-overlay-filter.accent-red:hover{ background:rgba(220,38,38,.4); border-color:rgba(220,38,38,.4); color:#fff; }
.search-overlay-filter.accent-orange{ background:rgba(239,126,26,.2); border-color:rgba(239,126,26,.3); color:#fdba74; }
.search-overlay-filter.accent-orange:hover{ background:rgba(239,126,26,.4); border-color:rgba(239,126,26,.4); color:#fff; }
.search-overlay-filter.accent-navy{ background:rgba(97,185,240,.15); border-color:rgba(97,185,240,.28); color:#93c5fd; }
.search-overlay-filter.accent-navy:hover{ background:rgba(97,185,240,.3); border-color:rgba(97,185,240,.3); color:#fff; }
.search-overlay-hint{ text-align:center; font-size:11.5px; color:rgba(255,255,255,.35); margin-top:20px; }
.search-overlay-hint .kbd{ margin:0 2px; }
@media(max-width:640px){
  .search-overlay{ padding-top:5vh; }
  .search-overlay-panel{ padding:0 12px 24px; }
  .search-overlay-form input{ font-size:15px; padding:8px 10px; }
  .search-overlay-submit{ width:44px; height:44px; }
  .search-overlay-hint{ display:none; }
}

.public-header{ position:sticky; top:0; z-index:40; background:var(--surface); border-bottom:1px solid var(--border); width:100%; }
/* Both the utility bar and header sit UNDER .mega-backdrop's z-index:290
   (public.css) at their normal z-index, so once a mega-panel is open they
   were being visually dimmed/blurred by it AND swallowing clicks meant for
   the header — including the other nav triggers, making it impossible to
   switch from e.g. Campus to Department without closing the menu first.
   Raised above the backdrop here so the header stays crisp and fully
   clickable while a mega-panel is open, same as it looks on initial load.
   Desktop-only (matches .mega-backdrop's own @media(max-width:980px){
   display:none } in public.css) — below 980px there's no backdrop to sit
   above, and raising these here would instead incorrectly float them over
   #mobileMenu's full-screen drawer (z-index:200). */
@media (min-width:981px){
  .public-utility-bar{ position:relative; z-index:291; }
  .public-header{ z-index:291; }
}
.public-header-inner{
  width:100%; max-width:1280px; margin:0 auto; padding:10px 40px;
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:18px;
}
.public-nav-toggle{ display:none; }
.public-brand{ display:flex; align-items:center; gap:10px; }
.public-brand img.logo-full{ height:48px; width:auto; display:block; }
html[data-bs-theme="dark"] .public-brand img.logo-full-light{ display:none; }
html:not([data-bs-theme="dark"]) .public-brand img.logo-full-dark{ display:none; }
/* Round mark shown instead of the wordmark below 980px (see responsive
   section at the end of this file) — hidden on desktop by default. */
.public-brand img.logo-mark{ width:38px; height:38px; border-radius:10px; object-fit:contain; display:none; }
html[data-bs-theme="dark"] .public-brand img.logo-mark-light{ display:none; }
html:not([data-bs-theme="dark"]) .public-brand img.logo-mark-dark{ display:none; }
html[data-bs-theme="dark"] .mm-brand img.logo-full-light{ display:none; }
html:not([data-bs-theme="dark"]) .mm-brand img.logo-full-dark{ display:none; }

.public-nav{ display:flex; align-items:center; justify-content:center; gap:2px; flex-wrap:wrap; }
.public-nav-brand{ display:none; }

.drawer-close-btn{
  display:none; width:32px; height:32px; border-radius:8px; flex-shrink:0;
  border:1px solid var(--border); background:var(--surface-2); color:var(--muted);
  align-items:center; justify-content:center; cursor:pointer;
}
.drawer-close-btn:hover{ background:var(--surface); color:var(--text); }
.drawer-close-btn svg{ width:15px; height:15px; }
.public-nav-link{
  position:relative; display:flex; align-items:center; gap:5px; padding:8px 16px; border-radius:9px; border:none; background:transparent;
  font-size:13.3px; font-weight:600; color:var(--text); cursor:pointer; white-space:nowrap; font-family:inherit;
}
.public-nav-link:hover{ background:var(--surface-2); color:var(--primary-text); }
.public-nav-link svg.chev{ width:12px; height:12px; opacity:.6; transition:transform .2s ease; }
.public-nav-link.is-open svg.chev{ transform:rotate(180deg); }
.public-header-right{ display:flex; align-items:center; gap:10px; justify-self:end; }

/* Below 980px the horizontal nav becomes a left-hand off-canvas drawer,
   toggled by #publicNavToggleBtn (see initPublicNav in scripts.js) — mirrors
   the dashboard sidebar's own off-canvas pattern so the two navbars behave
   the same way on mobile instead of the public one just disappearing. */
@media (max-width:980px){
  /* Hamburger reads as "the" menu control, so it belongs at the true right
     edge of the header — last in visual order, past search and the theme
     toggle — rather than first in .public-header-right's DOM order. */
  .public-nav-toggle{ display:flex; order:3; }
  .public-brand img.logo-full{ display:none; }
  .public-brand img.logo-mark{ display:block; }
  .public-nav{
    position:fixed; top:0; left:0; bottom:0; z-index:250; width:280px; max-width:82vw;
    background:var(--surface); flex-direction:column; align-items:stretch; gap:2px;
    padding:20px 14px 20px; overflow-y:auto; box-shadow:var(--shadow-lg);
    transform:translateX(-100%); transition:transform .25s ease;
  }
  .public-nav.open{ transform:translateX(0); }
  .public-nav-brand{
    display:flex; align-items:center; justify-content:space-between; gap:10px;
    padding:0 12px 18px; margin-bottom:10px; border-bottom:1px solid var(--border);
  }
  .public-nav-brand img.logo-full{ height:30px; width:auto; display:block; }
  html[data-bs-theme="dark"] .public-nav-brand img.logo-full-light{ display:none; }
  html:not([data-bs-theme="dark"]) .public-nav-brand img.logo-full-dark{ display:none; }
  .public-nav-brand .drawer-close-btn{ display:flex; }
  .public-nav-link{ width:100%; justify-content:space-between; }
  .public-nav .menu-wrap{ width:100%; }

  /* Campus mega-panel becomes a plain inline accordion nested inside the
     drawer instead of the desktop's fixed, full-viewport dropdown — see
     initMegaMenus()'s isMobileDrawer() branch in scripts.js, which skips
     reparenting the panel to <body> and skips position:fixed placement
     below this same breakpoint, so it stays part of the drawer's own flow. */
  .public-nav .mega-panel{
    position:static; display:none; max-height:none; overflow:visible;
    background:transparent; border-bottom:none; box-shadow:none; z-index:auto;
  }
  .public-nav .mega-panel.show{ display:block; animation:none; }
  .public-nav .mega-panel-inner{ display:block; max-width:none; min-height:0; }
  .public-nav .mega-panel-visual{ display:none; }
  .public-nav .mega-panel-grid{ display:flex; flex-direction:column; gap:6px; padding-right:0; }
  .public-nav .mega-item{
    padding:11px 12px; border-radius:12px; border:none; background:var(--primary-100);
    transition:background .15s ease;
  }
  .public-nav .mega-item:hover{
    background:color-mix(in srgb, var(--primary) 16%, var(--primary-100)); transform:none; box-shadow:none;
  }
  .public-nav .mega-item-icon{ width:30px; height:30px; border-radius:8px; background:transparent; color:var(--primary-text); }
  .public-nav .mega-item-icon svg{ width:16px; height:16px; }
  .public-nav .mega-item-sub{ display:none; }
}
@media (prefers-reduced-motion:reduce){ .public-nav{ transition:none; } }

/* --------------------------- dropdown menus ---------------------------- */
.menu-wrap{ position:relative; }
.menu-panel{
  position:absolute; top:calc(100% + 8px); right:0; width:280px; background:var(--surface);
  border:1px solid var(--border); border-radius:14px; box-shadow:0 20px 45px rgba(0,20,45,.16);
  z-index:41; display:none; overflow:hidden;
}
.menu-panel.left{ right:auto; left:0; }
.menu-panel.show{ display:block; }
.menu-panel-head{ display:flex; align-items:center; justify-content:space-between; padding:12px 16px; border-bottom:1px solid var(--border); }
.menu-panel-head h4{ margin:0; font-size:14.5px; font-weight:800; color:var(--primary-text); }
.menu-panel-head-actions{ display:flex; align-items:center; gap:10px; }
#notificationsMenu{ width:340px; }
.menu-item{
  width:100%; display:flex; align-items:center; gap:10px; border:none; background:transparent;
  border-radius:9px; padding:9px 10px; cursor:pointer; color:var(--text); font-size:13px; font-weight:600; text-align:left;
}
.menu-item:hover{ background:var(--surface-2); }
.menu-item.danger{ color:var(--danger); }
.menu-item svg{ width:15px; height:15px; flex-shrink:0; }
.menu-body{ padding:8px; }
.menu-divider{ height:1px; background:var(--border); margin:6px 4px; }

/* Profile summary shown at the top of the account dropdown (#profileMenu) —
   the topbar chip itself hides the name/email on mobile to save space, so
   this repeats avatar + name + username inside the dropdown on every size. */
.menu-profile-head{ display:flex; align-items:center; gap:12px; padding:16px; border-bottom:1px solid var(--border); }
.menu-profile-head .avatar{ width:42px; height:42px; border-radius:50%; font-size:15px; }
.menu-profile-head-text{ min-width:0; }
.menu-profile-head-text .name{ font-size:14px; font-weight:700; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.menu-profile-head-text .username{ font-size:12px; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.notif-row{
  display:flex; align-items:flex-start; gap:14px; padding:16px 20px; cursor:pointer;
  border-top:1px solid var(--border); position:relative; transition:background .15s ease;
  animation:notifRowIn .3s ease both;
}
@keyframes notifRowIn{ from{ opacity:0; transform:translateX(6px); } to{ opacity:1; transform:none; } }
@media (prefers-reduced-motion:reduce){ .notif-row{ animation:none; } }
.notif-row:first-child{ border-top:none; }
.notif-row:hover{ background:var(--surface-2); }
.notif-row.unread{ background:color-mix(in srgb, var(--warning) 8%, var(--surface)); }
.notif-row.unread:hover{ background:color-mix(in srgb, var(--warning) 14%, var(--surface)); }
.notif-row.unread::before{
  content:""; position:absolute; left:0; top:0; bottom:0; width:3px;
  background:linear-gradient(180deg, var(--warning), color-mix(in srgb, var(--warning) 60%, var(--danger)));
}
.notif-icon{
  width:36px; height:36px; border-radius:50%; flex-shrink:0; display:flex; align-items:center; justify-content:center;
  background:var(--surface-2); color:var(--muted); transition:background .15s ease, color .15s ease;
}
.notif-icon svg{ width:16px; height:16px; }
.notif-row.unread .notif-icon{ background:var(--warning-100); color:var(--warning); }
.notif-body{ flex:1; min-width:0; }
.notif-msg{ font-size:12.8px; font-weight:600; color:var(--text); line-height:1.4; }
.notif-row.unread .notif-msg{ font-weight:800; }
.notif-desc{ font-size:12px; color:var(--muted); line-height:1.55; margin-top:4px; }
.notif-time{ font-size:11px; color:var(--muted-2); margin-top:5px; display:flex; align-items:center; gap:7px; flex-wrap:wrap; }
.notif-unread-pill{
  flex-shrink:0; font-size:9px; font-weight:800; text-transform:uppercase; letter-spacing:.5px;
  background:var(--warning); color:#fff; padding:2px 7px; border-radius:20px; line-height:1.5;
}
.notif-read-tick{ flex-shrink:0; display:inline-flex; color:var(--success); opacity:.65; }
.notif-read-tick svg{ width:12px; height:12px; }
.notif-group-head{
  display:flex; align-items:center; gap:8px; font-size:11.5px; font-weight:800; text-transform:uppercase;
  letter-spacing:.5px; color:var(--muted); margin-bottom:12px;
}
.notif-group-count{
  background:var(--surface-2); border:1px solid var(--border); color:var(--muted);
  font-size:10.5px; font-weight:800; padding:1px 8px; border-radius:20px; letter-spacing:0; text-transform:none;
}
/* Full notifications page only — wraps the click-to-read button plus a
   separate row-actions column (Edit/Delete), which can't nest inside the
   button itself since a <button> can't contain other <button> elements. */
.notif-row-outer{ display:flex; align-items:stretch; }
.notif-row-outer.bordered{ border-top:1px solid var(--border); }
.notif-row-outer.unread{ background:color-mix(in srgb, var(--warning) 8%, var(--surface)); border-left:3px solid var(--warning); }
.notif-row-outer.unread .notif-row{ padding-left:17px; }
.notif-row-actions{ display:flex; align-items:center; gap:6px; padding-right:16px; flex-shrink:0; }
.menu-footer-btn{
  width:100%; border:none; border-top:none; background:transparent; color:var(--primary-text);
  font-weight:700; font-size:12.5px; padding:12px 0 10px; cursor:pointer; text-align:center; display:block;
}
.menu-footer-btn:hover{ text-decoration:underline; }

/* -------------------------------- pages -------------------------------- */
.page-head{
  position:relative;
  display:flex; align-items:center; justify-content:space-between; gap:20px; flex-wrap:wrap;
  margin-bottom:32px; padding:2px 0 26px 22px; border-bottom:1px solid var(--border);
  animation:pageHeadIn .45s cubic-bezier(.22,.85,.32,1) both;
}
.page-head::before{
  content:""; position:absolute; left:0; top:2px; bottom:26px; width:4px; border-radius:4px;
  background:linear-gradient(180deg, var(--primary-text) 0%, var(--info) 100%);
}
@keyframes pageHeadIn{ from{ opacity:0; transform:translateY(-8px); } to{ opacity:1; transform:translateY(0); } }
@media (prefers-reduced-motion:reduce){ .page-head{ animation:none; } }
.breadcrumb{
  font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.06em;
  color:var(--muted-2); margin-bottom:9px;
}
.breadcrumb b{ color:var(--primary-text); font-weight:800; }
.page-head h1{
  font-family:'Sora',sans-serif; font-size:28px; font-weight:800; margin:0 0 7px; letter-spacing:-.02em;
  color:var(--text);
}
@supports ((background-clip:text) or (-webkit-background-clip:text)){
  .page-head h1{
    background:linear-gradient(120deg, var(--text) 40%, var(--primary-text) 150%);
    -webkit-background-clip:text; background-clip:text; color:transparent;
  }
}
.page-head .sub{ color:var(--muted); font-size:13.5px; }

/* Title + trailing actions header (webform builder, error log detail) — no
   background/card at all, just generous spacing and a thin colored accent
   bar echoing .page-head's own left bar, so it reads as part of the same
   design language without literally duplicating the panel treatment. The
   text block always keeps flex:1/min-width:0 so it's the one that shrinks
   first; trailing actions are grouped in .detail-header-actions so they
   wrap/stack as a unit instead of each button/badge wrapping independently. */
.detail-header{
  position:relative;
  display:flex; align-items:center; gap:24px; flex-wrap:wrap;
  margin-bottom:32px; padding:2px 0 26px 22px; border-bottom:1px solid var(--border);
  animation:detailHeaderIn .45s cubic-bezier(.22,.85,.32,1) both;
}
.detail-header::before{
  content:""; position:absolute; left:0; top:2px; bottom:26px; width:4px; border-radius:4px;
  background:linear-gradient(180deg, var(--primary-text) 0%, var(--info) 100%);
}
.detail-header.danger::before{ background:linear-gradient(180deg, var(--danger) 0%, var(--warning) 100%); }
@keyframes detailHeaderIn{ from{ opacity:0; transform:translateY(-8px); } to{ opacity:1; transform:translateY(0); } }
@media (prefers-reduced-motion:reduce){ .detail-header{ animation:none; } }
.detail-header > div:first-child{ flex:1; min-width:0; }
.detail-header-actions{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; flex-shrink:0; }

.detail-header .breadcrumb{ margin-bottom:9px; }

.detail-header-title-row{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:7px; }
.detail-header h1{
  font-family:'Sora',sans-serif; font-size:28px; font-weight:800; letter-spacing:-.02em; margin:0;
  color:var(--text);
}
@supports ((background-clip:text) or (-webkit-background-clip:text)){
  .detail-header h1{
    background:linear-gradient(120deg, var(--text) 35%, var(--primary-text) 160%);
    -webkit-background-clip:text; background-clip:text; color:transparent;
  }
}
.detail-header .text-sub{ color:var(--muted); font-size:13.5px; }

/* -------------------------------- buttons ------------------------------- */
.btn{
  display:inline-flex; align-items:center; gap:8px; border-radius:999px; border:1px solid transparent;
  padding:10px 16px; font-size:13.5px; font-weight:600; cursor:pointer; transition:all .15s ease;
}
.btn svg{ width:15px; height:15px; }
.btn-primary{ background:var(--primary); color:#fff; }
.btn-primary:hover{ background:var(--primary-600); }
.btn-accent{ background:var(--warning); color:#fff; box-shadow:0 6px 16px rgba(255,149,51,.28); }
.btn-accent:hover{ filter:brightness(0.95); }
/* Public webform submit button (and its builder live-preview mirror) — a
   distinct look from .btn-accent, which is used for admin-side actions
   (Export, Publish) elsewhere. */
.btn-form-submit{ background:linear-gradient(135deg, var(--primary), #001F4A); color:#fff; box-shadow:0 6px 16px color-mix(in srgb, var(--primary) 35%, transparent); }
.btn-form-submit:hover{ filter:brightness(1.08); }
.btn-outline{ background:var(--surface); border-color:var(--border); color:var(--text); }
.btn-outline:hover{ background:var(--surface-2); }
.btn-ghost{ background:transparent; color:var(--muted); border-color:transparent; }
.btn-ghost:hover{ color:var(--text); background:var(--surface-2); }
.btn-sm{ padding:7px 12px; font-size:12.5px; }
.btn-danger{ background:#fff; color:var(--danger); border:1px solid var(--danger-100); }
.btn-danger:hover{ background:var(--danger-100); }
.btn-danger-soft{ background:var(--danger-100); color:var(--danger); }
.btn:disabled{ opacity:.5; cursor:not-allowed; }
.btn-block{ width:100%; justify-content:center; }

/* --------------------------------- cards -------------------------------- */
.card{
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  box-shadow:var(--shadow);
}
.card-head{ padding:24px 28px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
.card-head h3{ margin:0; font-size:15.5px; }
.card-head .sub{ font-size:12px; color:var(--muted); margin-top:4px; }
.card-body{ padding:30px; transition:opacity .15s ease; }
/* Consistent padding for the templates' one-off "bare" cards (help center,
   webform builder detail boxes, change-password, error-log detail, FAQ
   manager panes, etc.) that don't use the .card-head/.card-body structure —
   previously each had its own ad-hoc inline padding (16/18/20/22/26px), so
   cards looked inconsistent from page to page. */
.compact-card{ padding:30px; }

.grid-stats{ display:grid; grid-template-columns:repeat(4,1fr); gap:18px; margin-bottom:24px; }
.settings-grid-stats-3{ grid-template-columns:repeat(3,1fr); }
/* Dashboard's Events/Guests hero stat cards (templates/dashboard.html) */
.stat-hero-row{ display:grid; grid-template-columns:1fr 1fr; gap:18px; align-items:stretch; }
.stat-card{
  display:flex; align-items:center; flex-wrap:wrap; gap:14px;
  padding:28px; position:relative; overflow:hidden; isolation:isolate;
  transition:transform .2s ease, box-shadow .2s ease;
}
.stat-card:hover{ transform:translateY(-4px); box-shadow:var(--shadow-lg); }
.stat-card::after{
  content:""; position:absolute; top:-34px; right:-34px; width:120px; height:120px; border-radius:50%; z-index:0;
  background:var(--stat-accent-100, var(--info-100)); opacity:.55;
}
.stat-card.brand{
  background:linear-gradient(135deg, var(--primary) 0%, var(--primary-600) 100%); border-color:var(--primary);
}
.stat-card.brand::after{ background:rgba(255,255,255,.1); }
.stat-top{ display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; position:relative; z-index:1; }
.stat-icon{
  width:44px; height:44px; border-radius:12px; display:flex; align-items:center; justify-content:center; flex-shrink:0;
  position:relative; z-index:1; order:1;
  background:var(--stat-accent-100, var(--info-100)); color:var(--stat-accent, var(--primary));
}
.stat-card.brand .stat-icon{ background:rgba(255,255,255,.18); color:#fff; }
.stat-icon svg{ width:21px; height:21px; }
.stat-change{ font-size:12px; font-weight:700; padding:3px 8px; border-radius:20px; display:flex; align-items:center; gap:3px; }
.stat-change.up{ color:var(--success); background:var(--success-100); }
.stat-change.down{ color:var(--danger); background:var(--danger-100); }
/* Icon+label cluster on the left (order 1/2), the value pushed to the far
   right via margin-left:auto (order 3) and printed noticeably bigger —
   was a plain vertical stack (icon, label, value top-to-bottom on the
   left) before. */
.stat-label{ color:var(--muted); font-size:12.5px; font-weight:600; position:relative; z-index:1; order:2; }
.stat-value{
  font-size:36px; font-weight:800; font-family:'Sora',sans-serif; letter-spacing:-.02em; line-height:1;
  position:relative; z-index:1; order:3; margin-left:auto; text-align:right;
}
.stat-card.brand .stat-label{ color:rgba(255,255,255,.7); }
.stat-card.brand .stat-value{ color:#fff; }

.grid-2col{ display:grid; grid-template-columns:2fr 1fr; gap:18px; margin-bottom:22px; align-items:stretch; }
/* 12-column split (Submissions = col 4, trend chart = col 8) for the
   dashboard's top row — same idea as .grid-2col, just narrow-side-first. */
.grid-2col-4-8{ display:grid; grid-template-columns:1fr 2fr; gap:18px; margin-bottom:22px; align-items:stretch; }
/* Dashboard's "last 4" admin preview cards (see _admin_preview_sections in
   app/views.py) — col-4 each (3 per row) by default, stretched to a common
   row height regardless of how many rows each one actually has (a card
   with 0-1 entries shouldn't be visibly shorter than its full siblings).
   Exactly 4 visible sections is the one count that tiles awkwardly as
   3-then-1, so that specific case switches to a 2-column (2x2) layout via
   .cols-2; exactly 5 leaves the last row's 2nd card widened to fill the
   row instead of trailing off into empty space via .count-5. */
.admin-preview-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:18px; margin-bottom:22px; align-items:stretch; }
.admin-preview-grid.cols-2{ grid-template-columns:repeat(2,1fr); }
.admin-preview-grid.count-5 > :last-child{ grid-column:span 2; }
.admin-preview-grid .card{ display:flex; flex-direction:column; }
/* flex:1 (not also justify-content:center) — real rows stay pinned to the
   top like a normal list even when the card's been stretched taller than
   its content needs; only the truly-empty "Nothing yet" placeholder (the
   sole flex item when there are zero rows) gets auto margins to center
   itself in that leftover space, since centering a lone message reads
   better than it sitting flush against the top with a tall empty card
   below it. */
.admin-preview-grid .recent-list{ flex:1; }
.admin-preview-grid .recent-list .table-empty{ margin:auto 0; }

/* ------------------------- axis-based bar chart (SVG) -------------------- */
.barchart-svg{ width:100%; height:auto; display:block; overflow:visible; }
.barchart-svg .gridline{ stroke:var(--border); stroke-width:1; }
.barchart-svg .axis-label{ font-size:10.5px; fill:var(--muted-2); font-family:'Inter',sans-serif; }
.barchart-svg .bar-label{ font-size:10.5px; fill:var(--muted); font-family:'Inter',sans-serif; }
.barchart-svg .bar-link{ cursor:pointer; }
.barchart-svg .bar-rect{ fill:var(--primary); transition:opacity .15s ease; }
.barchart-svg .bar-link:hover .bar-rect{ opacity:.8; }
.barchart-svg .bar-link:hover .bar-label{ fill:var(--text); font-weight:700; }
.barchart-svg .bar-value{ font-size:10.5px; font-weight:700; fill:var(--text); font-family:'Inter',sans-serif; text-anchor:middle; }

/* ------------------------- multi-line trend chart (SVG) ------------------- */
.trend-chart-svg{ width:100%; height:auto; display:block; overflow:visible; margin-bottom:18px; }
.trend-chart-svg .trend-gridline{ stroke:var(--border); stroke-width:1; }
.trend-chart-svg .axis-label{ font-size:10.5px; fill:var(--muted-2); font-family:'Inter',sans-serif; }
/* Undrawn (dashoffset:1400, no animation) until JS (initChartScrollPlay in
   app/scripts.js) adds .in-view once the chart actually scrolls into the
   viewport — previously the draw animation fired the instant the page
   loaded, so if the chart wasn't on-screen yet you'd missed it entirely by
   the time you scrolled down to it. */
.trend-chart-svg .trend-line-path{
  fill:none; stroke-width:1.75; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:1400; stroke-dashoffset:1400;
}
.trend-chart-svg.in-view .trend-line-path{ animation:trendLineDraw 5s ease forwards; }
.trend-chart-svg .trend-line-dot{ cursor:pointer; transition:r .15s ease; }
.trend-chart-svg .trend-line-dot:hover{ r:5; }
@keyframes trendLineDraw{ to{ stroke-dashoffset:0; } }
@media (prefers-reduced-motion:reduce){
  .trend-chart-svg .trend-line-path{ animation:none; stroke-dashoffset:0; }
}

/* Custom hover tooltip for .trend-line-dot (see initChartTooltips in
   app/scripts.js) — replaces the plain native SVG <title> tooltip, which
   can't be styled. Single reusable element, positioned with JS. */
.chart-tooltip{
  position:fixed; z-index:600; display:none; pointer-events:none;
  background:#0f1524; color:#fff; border-radius:10px; padding:9px 13px;
  box-shadow:0 12px 28px rgba(0,0,0,.35); font-size:12px; white-space:nowrap;
  transform:translate(-50%,-100%); margin-top:-12px;
}
.chart-tooltip.show{ display:block; }
.chart-tooltip::after{
  content:""; position:absolute; top:100%; left:50%; transform:translateX(-50%);
  border:6px solid transparent; border-top-color:#0f1524;
}
.chart-tooltip-date{ color:rgba(255,255,255,.55); font-size:10.5px; margin-bottom:2px; }
.chart-tooltip-row{ display:flex; align-items:center; gap:7px; font-weight:700; }
.chart-tooltip-dot{ width:8px; height:8px; border-radius:50%; flex-shrink:0; }

/* ------------------------------ donut / ring ------------------------------ */
.ring-chart{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:18px; flex:1; height:100%; }
.ring{
  width:160px; height:160px; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; position:relative;
}
.ring-svg{ width:100%; height:100%; transform:rotate(-90deg); overflow:visible; }
.ring-track{ fill:none; stroke:var(--border); stroke-width:22; }
.ring-arc{
  fill:none; stroke-width:22; stroke-linecap:butt; cursor:pointer;
  transition:stroke-width .15s ease, opacity .15s ease;
  transform-origin:80px 80px;
}
.ring-arc:hover{ stroke-width:26; opacity:.92; }
.ring-legend{ display:flex; flex-wrap:wrap; justify-content:center; gap:14px 20px; }
.ring-legend-row{ display:flex; align-items:center; gap:7px; font-size:12.5px; font-weight:600; color:var(--muted); }
.ring-legend-row .dotc{ width:9px; height:9px; border-radius:50%; flex-shrink:0; }

/* --------------------------------- tables -------------------------------- */
.table-wrap{ overflow-x:auto; }
table{ width:100%; border-collapse:collapse; }
th{
  text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--muted-2); font-weight:700; padding:13px 20px; white-space:nowrap;
  background:var(--surface-2);
}
tbody tr{ transition:background .12s ease; }
tbody tr:hover{ background:var(--surface-2); }
td{ padding:14px 20px; font-size:13.5px; border-top:1px solid var(--border); vertical-align:middle; }
tr td:first-child, tr th:first-child{ padding-left:20px; }
tr td:last-child, tr th:last-child{ padding-right:20px; }
input[type=checkbox]{ width:16px; height:16px; accent-color:var(--primary); cursor:pointer; flex-shrink:0; }
.row-title{ display:flex; align-items:center; gap:12px; }
.row-thumb{
  width:40px; height:40px; border-radius:9px; flex-shrink:0; background:var(--primary-100); color:var(--primary-text);
  display:flex; align-items:center; justify-content:center; font-weight:700; font-size:13px;
}
.row-title-text{ font-weight:600; font-size:13.5px; }
.row-sub{ font-size:12px; color:var(--muted); margin-top:3px; }
.row-actions{ display:flex; gap:6px; }
.icon-mini{
  width:30px; height:30px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  color:var(--muted); cursor:pointer; border:1px solid transparent; background:transparent;
}
.icon-mini:hover{ background:var(--surface-2); color:var(--text); }
.icon-mini.danger:hover{ background:var(--danger-100); color:var(--danger); }
.icon-mini svg{ width:15px; height:15px; }
.table-empty{ padding:40px 20px; text-align:center; color:var(--muted); }

/* --------------------------------- badges -------------------------------- */
.badge{
  display:inline-flex; align-items:center; gap:5px; padding:4px 10px; border-radius:20px;
  font-size:11.5px; font-weight:700; white-space:nowrap;
}
.badge .dotc{ width:6px; height:6px; border-radius:50%; background:currentColor; flex-shrink:0; }
.badge-primary{ background:var(--primary-100); color:var(--primary-text); }
.badge-success{ background:var(--success-100); color:var(--success); }
.badge-warning{ background:var(--warning-100); color:#B7860F; }
html[data-bs-theme="dark"] .badge-warning{ color:var(--warning); }
.badge-danger{ background:var(--danger-100); color:var(--danger); }
.badge-info{ background:var(--info-100); color:var(--info); }
.badge-neutral{ background:var(--surface-2); color:var(--muted); }

/* status pills used across submissions/webforms */
.status-new{ background:var(--warning-100); color:#C05F0F; }
html[data-bs-theme="dark"] .status-new{ color:var(--warning); }
.status-contacted{ background:var(--info-100); color:var(--info); }
.status-qualified{ background:var(--primary-100); color:var(--primary-text); }
.status-closed{ background:var(--success-100); color:var(--success); }
.status-rejected{ background:var(--danger-100); color:var(--danger); }
.status-published{ background:var(--success-100); color:var(--success); }
.status-draft{ background:var(--surface-2); color:var(--muted); }
.status-pending{ background:var(--warning-100); color:#B7860F; }
html[data-bs-theme="dark"] .status-pending{ color:var(--warning); }
.status-approved{ background:var(--success-100); color:var(--success); }

/* Calendar page — event date-based status (has this event's date range
   already passed, is it happening now, or is it still ahead?), distinct
   from Event.status (the pending/approved/rejected *approval* workflow
   above) — and the four Academic Calendar entry types. */
.badge-live{ background:var(--success-100); color:var(--success); }
.badge-upcoming{ background:var(--info-100); color:var(--info); }
.badge-calendar-closed{ background:var(--surface-2); color:var(--muted); }
.badge-holiday-mandatory{ background:var(--danger-100); color:var(--danger); }
.badge-holiday-festival{ background:var(--vip-100); color:var(--vip); }
.badge-exam{ background:var(--warning-100); color:#B7860F; }
html[data-bs-theme="dark"] .badge-exam{ color:var(--warning); }
.badge-academic-other{ background:var(--info-100); color:var(--info); }

/* --------------------------------- forms --------------------------------- */
.form-grid{ display:grid; grid-template-columns:1fr 1fr; gap:0px 18px; }
.field{ margin-bottom:18px; display:flex; flex-direction:column; gap:7px; }
.field.full{ grid-column:1/-1; }

/* A plain 2-up card grid (e.g. the Email/Phone Support cards on Help
   Center) — kept separate from .form-grid on purpose: .form-grid's 0
   row-gap only works there because each .field supplies its own
   margin-bottom, which a bare .card doesn't have, so reusing .form-grid
   for cards left them touching edge-to-edge once collapsed to one column. */
.card-grid-2{ display:grid; grid-template-columns:1fr 1fr; gap:18px; }
@media(max-width:640px){ .card-grid-2{ grid-template-columns:1fr; } }

/* FAQ manager two-pane layout (templates/faqs/list.html) and the field
   editor's own sub-grids (templates/webforms/_field_row.html) — pulled out
   of inline style="grid-template-columns:…" so they can actually collapse
   at tablet/mobile widths (an inline style would otherwise always beat the
   media-query rules above, regardless of specificity). */
.faq-manager-grid{ display:grid; grid-template-columns:320px 1fr; gap:0 18px; align-items:start; }
.field-row-grid{ display:grid; grid-template-columns:1.4fr 1fr; gap:10px; }
.field-row-detail-grid{ display:grid; grid-template-columns:1fr 1fr 130px auto; gap:10px; margin-top:10px; align-items:end; }

/* 12-column dynamic field layout, driven by WebFormField.width (1-12) — used
   by the public submission page, the builder's live preview and the
   webforms "view" modal so a field with width=4 next to one with width=8
   sit side by side in the same row, matching a Bootstrap-style col-N grid. */
.dynamic-form-grid{ display:grid; grid-template-columns:repeat(12, 1fr); gap:16px 14px; }
.dynamic-form-grid .field{ margin-bottom:0; min-width:0; }
/* Tablet: whatever span a field was given for desktop (e.g. span 3 = a
   Bootstrap-style col-3) doubles up to span 6, so a 4-across desktop row
   becomes 2-across here; mobile below always wins regardless (!important)
   and collapses everything to one full-width column per row. */
@media(max-width:900px){ .dynamic-form-grid .field{ grid-column:span 6 !important; } }
@media(max-width:640px){ .dynamic-form-grid .field{ grid-column:span 12 !important; } }
.field label{
  display:flex; font-size:13px; font-weight:600; margin-bottom:7px; color:var(--text);
  align-items:center; gap:6px;
}
.field .hint{ font-weight:400; color:var(--muted); font-size:11.5px; }

/* Info-icon hover tooltip — used next to field-builder labels to explain
   what to enter, instead of a permanent caption line under the row. */
.field-hint{
  display:inline-flex; align-items:center; justify-content:center; width:15px; height:15px;
  border-radius:50%; background:var(--surface-2); color:var(--muted-2); cursor:help; position:relative; flex-shrink:0;
}
.field-hint svg{ width:10px; height:10px; }
.field-hint:hover{ background:var(--primary-100); color:var(--primary-text); }
.field-hint::after{
  content:attr(data-tooltip); position:absolute; bottom:calc(100% + 9px); left:50%; transform:translateX(-50%);
  background:var(--text); color:var(--bg); font-size:11.5px; font-weight:500; line-height:1.45;
  padding:8px 10px; border-radius:8px; width:220px; white-space:normal; text-align:left;
  box-shadow:var(--shadow-lg); z-index:60; pointer-events:none; opacity:0; visibility:hidden;
  transition:opacity .12s ease;
}
.field-hint::before{
  content:""; position:absolute; bottom:calc(100% + 4px); left:50%; transform:translateX(-50%);
  border:5px solid transparent; border-top-color:var(--text); z-index:60; pointer-events:none;
  opacity:0; visibility:hidden; transition:opacity .12s ease;
}
.field-hint:hover::after, .field-hint:hover::before{ opacity:1; visibility:visible; }

/* ------------------------------ file/image upload ------------------------------ */
.upload-row{ display:flex; align-items:stretch; gap:14px; }
.upload-box{
  flex:1; min-width:0; display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; gap:5px; padding:26px 18px; border:1.5px dashed var(--border); border-radius:14px;
  background:var(--surface-2); cursor:pointer;
  transition:border-color .15s ease, background .15s ease, transform .15s ease;
}
.upload-box:hover{
  border-color:var(--primary-text); background:color-mix(in srgb, var(--primary-text) 7%, var(--surface-2));
  transform:translateY(-1px);
}
.upload-box input[type=file]{ display:none; }
.upload-box .upload-icon-wrap{
  width:40px; height:40px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:var(--primary-100); color:var(--primary-text); margin-bottom:4px; transition:transform .2s ease;
}
.upload-box:hover .upload-icon-wrap{ transform:translateY(-2px) scale(1.05); }
.upload-box.has-file .upload-icon-wrap{ background:var(--success-100); color:var(--success); }
.upload-box .upload-title{
  font-size:13.5px; font-weight:700; color:var(--text); text-transform:none; letter-spacing:normal;
}
.upload-box .upload-hint{
  font-size:11.5px; font-weight:500; color:var(--muted); text-transform:none; letter-spacing:normal;
}
.upload-thumb-wrap{
  position:relative; width:80px; height:80px; flex-shrink:0; border-radius:12px; overflow:visible;
  display:none;
}
.upload-thumb-wrap.show{ display:block; }
.upload-thumb-wrap img,
.upload-thumb-wrap .upload-thumb-fallback{
  width:100%; height:100%; border-radius:12px; object-fit:cover; border:1px solid var(--border); background:var(--surface-2);
  display:flex; align-items:center; justify-content:center; color:var(--muted);
}
.upload-thumb-close{
  position:absolute; top:-7px; right:-7px; width:20px; height:20px; border-radius:50%;
  background:var(--text); color:var(--bg); border:2px solid var(--surface); display:flex; align-items:center;
  justify-content:center; cursor:pointer; padding:0;
}
.upload-thumb-close svg{ width:10px; height:10px; }

/* Event form's gallery uploader (templates/events/form.html) — a single
   "Click to upload photos" .upload-box (multiple files at once) feeding a
   grid of thumbnail cards below it, each its own hidden formset row (see
   initGalleryUploader in static/js/app/event-form.js). */
.gallery-thumb-grid{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(110px, 1fr)); gap:16px;
  margin-top:18px;
}
.gallery-thumb-card{ position:relative; aspect-ratio:1; cursor:grab; }
.gallery-thumb-card:active{ cursor:grabbing; }
.gallery-thumb-card img{
  width:100%; height:100%; border-radius:12px; object-fit:cover; border:1px solid var(--border);
  background:var(--surface-2); display:block;
}
.gallery-thumb-card.dragging{ opacity:.4; }
.gallery-thumb-card.drag-over{ outline:2px dashed var(--primary-text); outline-offset:3px; }
.gallery-thumb-remove{
  position:absolute; top:-7px; right:-7px; width:20px; height:20px; border-radius:50%;
  background:var(--text); color:var(--bg); border:2px solid var(--surface); display:flex; align-items:center;
  justify-content:center; cursor:pointer; padding:0;
}
.gallery-thumb-remove svg{ width:10px; height:10px; }

/* Base input/select/textarea styling — intentionally NOT scoped to .field so
   every text/date/select control looks consistent wherever it's used
   (filter bars, inline table forms, etc.), not only inside a labeled field. */
input[type=text],input[type=url],input[type=email],input[type=tel],
input[type=password],input[type=date],input[type=time],input[type=number],input[type=search],
textarea,select{
  padding:12px 14px; border-radius:8px; border:1.5px solid var(--border);
  background:var(--surface-2); color:var(--text); font-size:13.5px; font-family:inherit; outline:none;
  transition:border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.field input[type=text],.field input[type=url],.field input[type=email],.field input[type=tel],
.field input[type=password],.field input[type=date],.field input[type=time],.field input[type=number],
.field textarea,.field select{ width:100%; }
input[type=color]{ height:42px; padding:3px; border-radius:8px; border:1.5px solid var(--border); background:var(--surface-2); }
input[type=file]{ font-size:12.5px; }
input:hover,textarea:hover,select:hover{ border-color:color-mix(in srgb, var(--primary) 35%, var(--border)); }
input:focus,textarea:focus,select:focus{
  border-color:var(--primary); background:var(--surface);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 15%, transparent);
}
textarea{ resize:vertical; min-height:80px; }
/* Explicit placeholder color/opacity — without this, every field's
   placeholder falls back to each browser's own default UA styling
   (Firefox in particular renders it at a much lower opacity than
   Chromium), which reads as "the placeholder text is missing/barely
   visible" purely from browser-to-browser inconsistency. */
::placeholder{ color:var(--muted); opacity:1; }

/* ---------------------------- custom select ------------------------------ */
/* Every <select> is progressively enhanced (see initCustomSelects in
   scripts.js) into this button+listbox pair, since a native <select>'s open
   options popup can't be restyled with CSS in any browser. */
.csel{ position:relative; display:inline-block; }
.field .csel{ display:block; width:100%; }
.csel-native{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); white-space:nowrap; border:0; opacity:0;
}
.csel-trigger{
  width:100%; display:inline-flex; align-items:center; justify-content:space-between; gap:10px;
  padding:12px 16px; border-radius:999px; border:1.5px solid var(--border);
  background:var(--surface-2); color:var(--text); font-size:13.5px; font-family:inherit;
  cursor:pointer; text-align:left; transition:border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.csel-trigger:hover{ border-color:color-mix(in srgb, var(--primary) 45%, var(--border)); }
.csel.open .csel-trigger, .csel-trigger:focus-visible{
  border-color:var(--primary); background:var(--surface); outline:none;
  box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 15%, transparent);
}
.csel-trigger:disabled{ opacity:.6; cursor:not-allowed; }
/* .badge/.status-* selects (e.g. the per-row status dropdown) keep their
   pill color instead of the default trigger's gray box — .csel-trigger and
   .badge/.status-* are equal CSS specificity, so without this the trigger's
   own background (declared later in this file) would otherwise win. */
.csel-trigger.badge{ width:auto; border:none; padding:4px 10px; }
.csel-trigger.status-new{ background:var(--warning-100); color:#C05F0F; }
html[data-bs-theme="dark"] .csel-trigger.status-new{ color:var(--warning); }
.csel-trigger.status-contacted{ background:var(--info-100); color:var(--info); }
.csel-trigger.status-qualified{ background:var(--primary-100); color:var(--primary-text); }
.csel-trigger.status-closed{ background:var(--success-100); color:var(--success); }
.csel-trigger.status-rejected{ background:var(--danger-100); color:var(--danger); }
.csel-value{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.csel-chevron{ display:flex; flex-shrink:0; color:var(--muted); transition:transform .2s ease; }
.csel.open .csel-chevron{ transform:rotate(180deg); }
.csel-menu{
  /* Reparented to <body> and positioned with inline top/left/width by
     initCustomSelects() in scripts.js — position:absolute relative to .csel
     would otherwise get clipped by any ancestor with overflow:hidden/auto
     (e.g. a scrollable .modal), so it's a "portal" element like .modal-backdrop.
     z-index must stay above .modal-backdrop's (295, see that rule's own
     comment for why it isn't 200) so a select inside an open modal still
     floats its options list above the modal instead of silently painting
     behind its backdrop layer — invisible, even though .csel-trigger's own
     open/focus state (a normal descendant of the modal) looks fine. */
  /* min-width is set inline by positionMenu() (see scripts.js) — a plain
     CSS min-width:100% here resolves against the *viewport* for a
     position:fixed element, not the trigger, letting the menu balloon out
     past the trigger/card whenever an option's text is wider than it. */
  position:fixed; z-index:296;
  background:var(--surface); border:1px solid var(--border); border-radius:12px;
  box-shadow:0 16px 40px rgba(0,20,45,.18); padding:6px; max-height:260px; overflow-y:auto;
  list-style:none; margin:0;
  opacity:0; transform:translateY(-6px) scale(.98); pointer-events:none;
  transition:opacity .16s ease, transform .16s ease;
}
.csel-menu.open{ opacity:1; transform:none; pointer-events:auto; }
.csel-option{
  padding:9px 12px; border-radius:8px; font-size:13.3px; color:var(--text); cursor:pointer;
  display:flex; align-items:center; justify-content:space-between; gap:10px;
}
.csel-option:hover, .csel-option.active{ background:var(--surface-2); }
.csel-option.selected{ color:var(--primary-text); font-weight:700; }
.csel-option.selected::after{
  content:""; width:7px; height:12px; flex-shrink:0;
  border-right:2px solid var(--primary); border-bottom:2px solid var(--primary); transform:rotate(45deg) translateY(-1px);
}
.csel-option.disabled{ color:var(--muted); cursor:not-allowed; opacity:.6; }
.field .errors{ color:var(--danger); font-size:12px; font-weight:600; }
.req{ color:var(--danger); }
.form-footer{
  display:flex; justify-content:space-between; align-items:center; margin-top:10px; padding-top:20px;
  border-top:1px solid var(--border); flex-wrap:wrap; gap:10px;
}
.helptext{ color:var(--muted); font-size:12px; margin-top:2px; }
/* .field-checkbox (partials/checkbox_field.html — Vip/Pesu staff-style
   boolean fields) had no spacing rule of its own at all, unlike .field's
   own margin-bottom:18px — consecutive checkbox fields sat flush against
   each other, and the last one sat flush against the form's own submit
   button right after it. */
.field-checkbox{ margin-bottom:18px; }
.non-field-errors{
  background:var(--danger-100); color:var(--danger); padding:10px 14px; border-radius:10px;
  font-size:13px; font-weight:600; margin-bottom:16px;
}

/* --------------------------------- chips --------------------------------- */
.chip-select{
  display:flex; flex-wrap:wrap; gap:8px; padding:10px; border:1px solid var(--border);
  border-radius:10px; background:var(--surface-2);
}
.chip-row{ display:flex; flex-wrap:wrap; gap:8px; margin-bottom:18px; }
.chip-opt{
  padding:7px 13px; border-radius:20px; font-size:12.5px; font-weight:600; cursor:pointer;
  border:1px solid var(--border); background:var(--surface); color:var(--muted); user-select:none;
  transition:all .15s ease; display:inline-flex; align-items:center; gap:6px;
}
.chip-opt.selected{ background:var(--primary); border-color:var(--primary); color:#fff; }
.chip-manage{
  display:inline-flex; align-items:center; gap:8px; padding:7px 8px 7px 13px; border-radius:20px;
  font-size:12.5px; font-weight:600; border:1px solid var(--border); background:var(--surface); color:var(--text);
}
.chip-manage .chip-del{
  width:18px; height:18px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  color:var(--muted); cursor:pointer; flex-shrink:0; border:none; background:transparent;
}
.chip-manage .chip-del:hover{ background:var(--danger-100); color:var(--danger); }
.chip-manage .chip-del svg{ width:11px; height:11px; }

/* --------------------------- permission matrix ---------------------------- */
/* Groups a permissions CheckboxSelectMultiple field into an Add/Change/
   Delete/View matrix (see main/_permission_matrix.html + the
   permission_matrix template filter) instead of a flat checkbox list, with
   select-all controls at the global, column and row level wired up in
   scripts.js via [data-perm-select-*] delegation. */
.perm-matrix{ border:1px solid var(--border); border-radius:12px; overflow:hidden; background:var(--surface); }
.perm-matrix-top{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:10px 12px; background:var(--surface-2); border-bottom:1px solid var(--border);
}
.perm-matrix-title{ font-size:11px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }
.perm-table-wrap{ overflow:auto; max-height:340px; }
.perm-table{ width:100%; border-collapse:collapse; font-size:12.5px; white-space:nowrap; }
.perm-table thead th{
  position:sticky; top:0; z-index:1; background:var(--surface-2); text-align:center;
  font-size:10.5px; font-weight:800; letter-spacing:.03em; text-transform:uppercase; color:var(--muted);
  padding:10px 8px 8px; border-bottom:1px solid var(--border);
}
.perm-table thead th.perm-col-class{ text-align:left; padding-left:14px; }
.perm-col-select{
  display:block; margin:5px auto 0; border:1px solid var(--border); background:var(--surface);
  border-radius:999px; padding:3px 10px; font-size:10px; font-weight:700; color:var(--muted); cursor:pointer;
  text-transform:none; letter-spacing:0; white-space:nowrap;
}
.perm-col-class .perm-col-select{ margin:5px 0 0; }
.perm-col-select:hover{ background:var(--primary-100); color:var(--primary-text); border-color:var(--primary); }
.perm-table tbody td{ padding:9px 8px; text-align:center; border-bottom:1px solid var(--border); }
.perm-table tbody tr:last-child td{ border-bottom:none; }
.perm-table tbody tr:hover{ background:var(--surface-2); }
.perm-row-label{
  text-align:left !important; padding-left:14px !important; font-weight:700; color:var(--text);
  display:flex; align-items:center; gap:8px;
}
.perm-row-select{
  width:20px; height:20px; border-radius:50%; border:1px solid var(--border); background:var(--surface);
  color:var(--muted); display:inline-flex; align-items:center; justify-content:center; cursor:pointer; flex-shrink:0;
}
.perm-row-select:hover{ background:var(--primary-100); color:var(--primary-text); border-color:var(--primary); }
.perm-row-select svg{ width:10px; height:10px; }
.perm-chk, .perm-chk-other{ display:inline-flex; align-items:center; justify-content:center; cursor:pointer; }
.perm-chk input, .perm-chk-other input{ width:16px; height:16px; cursor:pointer; accent-color:var(--primary); flex-shrink:0; }
.perm-cell-other{ text-align:left; }
.perm-chk-other{ gap:6px; justify-content:flex-start; font-size:11px; font-weight:600; color:var(--muted); white-space:normal; }
.perm-dash{ color:var(--border); }

/* --------------------------------- tabs ---------------------------------- */
/* align-items:center (not the flex default, stretch) — without it, a
   plain-text flex child with no padding of its own (like the public guests
   directory's "N guests found" count, appended after the tab buttons)
   stretches to the row's full height but keeps its text pinned to the top
   of that taller box instead of sitting centred alongside the padded
   .status-tab buttons next to it. */
.status-tabs{ display:flex; align-items:center; gap:6px; padding:5px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; flex-wrap:wrap; }
.status-tab{
  padding:8px 15px; border-radius:999px; font-size:12.5px; font-weight:700; color:var(--muted); cursor:pointer;
  display:flex; align-items:center; gap:6px; border:none; background:transparent;
}
.status-tab .cnt{ font-size:10.5px; background:var(--border); color:var(--muted); border-radius:20px; padding:1px 6px; }
.status-tab.active{ background:var(--surface); color:var(--text); box-shadow:var(--shadow); }
.status-tab.active .cnt{ background:var(--primary); color:#fff; }

/* -------------------------------- filters --------------------------------- */
.filter-bar{ padding:16px 18px; margin-bottom:16px; }
.filter-row{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; }
.filter-row + .filter-row{ margin-top:12px; }
.filter-search{ position:relative; flex:1 1 220px; min-width:0; }
.filter-search svg{ position:absolute; left:14px; top:14px; color:var(--muted); width:14px; height:14px; }
.filter-search input{ width:100%; border-radius:999px !important; padding-left:36px !important; }
/* Date-range fields in a filter row — the little "From"/"To" caption sits
   inline before the native date input. */
.filter-date{
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  font-size:12.5px; font-weight:600; color:var(--muted);
}
.filter-date input{ width:auto; }

/* -------------------------------- modal ----------------------------------- */
/* 295, not 200 — on desktop (>=981px) .public-header/.public-utility-bar
   raise themselves to z-index:291 (above) so they stay clickable over an
   open mega-menu, which put them above every modal too (any modal,
   anywhere on the site, including the gallery lightbox) at that width —
   the header rendered fully interactive on top of a dimmed/inert modal
   instead of the modal covering it like everywhere else. Still under
   .toast/.messages-stack (300/400) so those keep showing above an open
   modal same as before. */
.modal-backdrop{
  position:fixed; inset:0; background:rgba(6,15,32,.55); backdrop-filter:blur(4px);
  display:none; align-items:center; justify-content:center; z-index:295; padding:16px;
}
.modal-backdrop.show{ display:flex; animation:modalBackdropIn .2s ease both; }
@keyframes modalBackdropIn{ from{ opacity:0; } to{ opacity:1; } }
@media (prefers-reduced-motion:reduce){ .modal-backdrop.show{ animation:none; } }

.modal{
  background:var(--surface); border-radius:20px; width:92vw; max-width:560px; max-height:88vh;
  overflow-y:auto; overflow-x:hidden;
  box-shadow:0 40px 90px rgba(0,10,30,.45), 0 0 0 1px rgba(255,255,255,.06);
}
.modal-backdrop.show .modal{ animation:modalIn .32s cubic-bezier(.22,.85,.32,1) both; }
@keyframes modalIn{
  from{ opacity:0; transform:scale(.92) translateY(18px); }
  /* transform:none (not scale(1) translateY(0)) — animation-fill-mode:both
     on .modal above keeps whichever value is here applied indefinitely
     after the animation ends, and any non-"none" transform on an ancestor
     stops Chrome from rendering a native <select>'s options popup at all
     (it opens, focuses, flips the arrow, but the dropdown list never
     shows). Ending on the literal none value avoids leaving that residue
     while still animating identically. */
  to{ opacity:1; transform:none; }
}
@media (prefers-reduced-motion:reduce){ .modal-backdrop.show .modal{ animation:none; } }
.modal.wide{ max-width:560px; }
.modal.narrow{ max-width:440px; }
.modal.xwide{ max-width:760px; }

.modal-head{
  display:flex; align-items:center; justify-content:space-between; gap:12px; padding:22px 26px;
  position:sticky; top:0; z-index:2; border-radius:20px 20px 0 0; overflow:hidden;
  background:linear-gradient(135deg, var(--primary) 0%, var(--primary-600) 100%);
}
.modal-head::before{
  content:""; position:absolute; top:-60px; right:-40px; width:160px; height:160px; border-radius:50%;
  background:radial-gradient(circle, rgba(255,255,255,.16) 0%, transparent 70%); pointer-events:none;
}
.modal-head h3{ font-size:17px; font-weight:800; color:#fff; position:relative; z-index:1; }
.modal-close{
  border:none; background:rgba(255,255,255,.16); border-radius:50%; width:30px; height:30px; cursor:pointer;
  color:#fff; display:flex; align-items:center; justify-content:center; flex-shrink:0; position:relative; z-index:1;
  transition:background .15s ease, transform .2s ease;
}
.modal-close:hover{ background:rgba(255,255,255,.3); transform:rotate(90deg); }
.modal-body{ padding:30px; }

/* Custom confirm dialog (replaces the native confirm() — see #confirmDialog in
   main/dashboard_base.html + confirmDialog() in scripts.js). Reuses the .modal
   shell so it matches every other dashboard modal; only the body layout is
   bespoke. */
#confirmDialog .modal{ max-width:410px; }
#confirmDialog .modal-body{ padding:24px 26px 22px; }
.confirm-message{ margin:0; font-size:14px; line-height:1.6; color:var(--text); }
.confirm-actions{ display:flex; justify-content:flex-end; gap:10px; margin-top:24px; }
.confirm-actions .btn{ min-width:104px; justify-content:center; }
@media (max-width:560px){
  .confirm-actions{ flex-direction:column-reverse; }
  .confirm-actions .btn{ width:100%; }
}

/* -------------------------------- flash messages ---------------------------- */
.messages-stack{ position:fixed; top:16px; right:16px; z-index:400; display:flex; flex-direction:column; gap:10px; max-width:360px; }
.alert{
  display:flex; align-items:flex-start; gap:10px; padding:12px 14px; border-radius:12px;
  font-size:13px; font-weight:600; box-shadow:var(--shadow-lg); background:var(--surface); border:1px solid var(--border);
}
.alert-success{ background:var(--success-100); color:var(--success); border-color:transparent; }
.alert-danger{ background:var(--danger-100); color:var(--danger); border-color:transparent; }
.alert-warning{ background:var(--warning-100); color:#B7860F; border-color:transparent; }
html[data-bs-theme="dark"] .alert-warning{ color:var(--warning); }
.alert-info,.alert-debug{ background:var(--info-100); color:var(--info); border-color:transparent; }
.alert-close{ margin-left:auto; background:none; border:none; cursor:pointer; color:inherit; opacity:.7; flex-shrink:0; }
.alert-close:hover{ opacity:1; }

/* -------------------------------- toast ------------------------------------ */
.toast{
  position:fixed; bottom:24px; right:24px; background:var(--text); color:var(--bg);
  padding:14px 18px; border-radius:10px; font-size:13.5px; font-weight:600;
  box-shadow:var(--shadow-lg); display:flex; align-items:center; gap:10px; z-index:300;
  transform:translateY(30px); opacity:0; transition:all .3s ease; pointer-events:none;
}
.toast.show{ transform:translateY(0); opacity:1; }
.toast svg{ width:16px; height:16px; color:var(--success); flex-shrink:0; }

/* ---------------------------------- back to top ------------------------------ */
.back-to-top{
  position:fixed; right:24px; bottom:32px; z-index:120; width:44px; height:44px; border-radius:50%;
  border:1px solid var(--border); background:var(--surface); color:var(--text);
  display:flex; align-items:center; justify-content:center; cursor:pointer; box-shadow:var(--shadow-lg);
  opacity:0; transform:translateY(10px); pointer-events:none;
  transition:opacity .2s ease, transform .2s ease, background .15s ease;
}
.back-to-top.show{ opacity:1; transform:none; pointer-events:auto; }
.back-to-top:hover{ background:var(--surface-2); }
.back-to-top svg{ width:18px; height:18px; }
@media (prefers-reduced-motion:reduce){ .back-to-top{ transition:opacity .2s ease; } }
@media (max-width:1040px){ .back-to-top{ bottom:calc(130px + env(safe-area-inset-bottom, 0px)); } }
@media (max-width:640px){ .back-to-top{ right:16px; width:40px; height:40px; } }

/* ------------------------------- switch/toggle ------------------------------ */
.switch{ position:relative; width:42px; height:24px; flex-shrink:0; display:inline-block; }
.switch input{ opacity:0; width:0; height:0; }
.slider{ position:absolute; inset:0; background:var(--border); border-radius:20px; cursor:pointer; transition:.2s; }
.slider:before{ content:""; position:absolute; width:18px; height:18px; left:3px; top:3px; background:#fff; border-radius:50%; transition:.2s; box-shadow:0 1px 3px rgba(0,0,0,.25); }
.switch input:checked + .slider{ background:var(--warning); }
.switch input:checked + .slider:before{ transform:translateX(18px); }
.switch input:disabled + .slider{ opacity:.5; cursor:not-allowed; }

/* -------------------------------- empty state -------------------------------- */
.empty-page{ display:flex; flex-direction:column; align-items:center; justify-content:center; padding:70px 20px; text-align:center; color:var(--muted); }
.empty-page svg{ width:52px; height:52px; margin-bottom:16px; opacity:.5; }
.empty-page h3{ color:var(--text); margin:0 0 6px; }
.empty-page p{ margin:0; font-size:13.5px; max-width:340px; }

/* -------------------------------- misc utils -------------------------------- */
.flex{ display:flex; }
.flex-col{ display:flex; flex-direction:column; }
.items-center{ align-items:center; }
.justify-between{ justify-content:space-between; }
.gap-6{ gap:6px; } .gap-8{ gap:8px; } .gap-10{ gap:10px; } .gap-14{ gap:14px; } .gap-18{ gap:18px; }
.mb-0{ margin-bottom:0 !important; }
.text-muted{ color:var(--muted); }
.text-sub{ font-size:12.5px; color:var(--muted); }
.w-full{ width:100%; }
.divider{ height:1px; background:var(--border); margin:14px 0; }

/* -------------------------------- auth page -------------------------------- */
.auth-shell{ min-height:100vh; display:flex; align-items:center; justify-content:center; background:var(--bg); padding:20px; }
.auth-card{ width:420px; max-width:100%; padding:40px 36px; text-align:center; }
.auth-card img.logo-full{ height:40px; width:auto; margin:0 auto 18px; display:block; object-fit:contain; }
html[data-bs-theme="dark"] .auth-card img.logo-full-light{ display:none; }
html:not([data-bs-theme="dark"]) .auth-card img.logo-full-dark{ display:none; }
.auth-card h1{ font-size:20px; margin-bottom:6px; }
.auth-card .sub{ font-size:13.5px; color:var(--muted); margin-bottom:26px; }
.auth-card form{ text-align:left; }
.auth-remember{ display:flex; align-items:center; justify-content:space-between; margin:-6px 0 18px; font-size:12.5px; }
.auth-remember label{ display:flex; align-items:center; gap:7px; color:var(--muted); font-weight:600; cursor:pointer; }

/* Sign in / forgot / reset / verify-otp pages opt into this richer look via
   an extra class — plain .auth-shell stays untouched since public_form.html
   (webform submission page) reuses it too and shouldn't get a dark banner.
   Deliberately light/airy (not the site-footer's dark gradient) so the two
   don't blend into one panel when a footer sits directly below the shell. */
.auth-shell-branded{
  position:relative; overflow:hidden; isolation:isolate;
  background:
    radial-gradient(ellipse 760px 520px at 10% 8%, color-mix(in srgb, var(--primary) 16%, transparent) 0%, transparent 62%),
    radial-gradient(ellipse 640px 520px at 92% 94%, color-mix(in srgb, var(--warning) 16%, transparent) 0%, transparent 62%),
    var(--bg);
}
.auth-shell-branded::before{
  content:""; position:absolute; inset:0; z-index:0; opacity:.7; pointer-events:none;
  background-image:radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--text) 10%, transparent) 1px, transparent 0);
  background-size:26px 26px;
}
.auth-blob{
  position:absolute; border-radius:50%; filter:blur(80px); opacity:.28; z-index:0; pointer-events:none;
  animation:authBlobFloat 16s ease-in-out infinite alternate;
}
.auth-blob.b1{ width:340px; height:340px; background:#5ea3ff; top:-120px; left:-80px; }
.auth-blob.b2{ width:280px; height:280px; background:#ffb648; bottom:-100px; right:-60px; animation-duration:20s; animation-delay:-6s; }
.auth-blob.b3{ width:220px; height:220px; background:#7fe8c0; top:38%; right:6%; animation-duration:14s; animation-delay:-10s; }
@keyframes authBlobFloat{ 0%{ transform:translate(0,0) scale(1); } 100%{ transform:translate(26px,-20px) scale(1.1); } }
@media (prefers-reduced-motion:reduce){ .auth-blob{ animation:none; } }
@keyframes authCardIn{ from{ opacity:0; transform:translateY(20px) scale(.96); } to{ opacity:1; transform:none; } }

/* ------------------- sign-in / forgot-password swap card ------------------ */
/* Both forms live in the DOM at once (accounts/templates/user_auth.html);
   .auth-swap-link clicks just toggle .auth-right-active on #authSwapCard
   (see initAuthSwap in scripts.js) so the accent panel + forms slide across
   without a page reload. Falls back to a normal link navigation (server
   renders the right state via url_name) if JS never runs. */
.auth-card-split{
  position:relative; width:760px; max-width:100%; min-height:480px;
  display:flex; padding:0; overflow:hidden;
  animation:authCardIn .5s cubic-bezier(.22,.85,.32,1) both;
}
.auth-shell-branded .auth-card-split,
.auth-shell-branded .auth-static-split{
  box-shadow:0 30px 70px -10px rgba(20,30,60,.25), 0 8px 24px rgba(20,30,60,.12);
}
.auth-form-panel{
  position:absolute; top:0; left:0; width:50%; height:100%; padding:48px 46px;
  display:flex; flex-direction:column; justify-content:center; text-align:left;
  transition:transform .6s cubic-bezier(.65,0,.35,1), opacity .45s ease;
}
.auth-form-panel img.logo-full{ height:32px; width:auto; margin-bottom:18px; display:block; object-fit:contain; }
html[data-bs-theme="dark"] .auth-form-panel img.logo-full-light{ display:none; }
html:not([data-bs-theme="dark"]) .auth-form-panel img.logo-full-dark{ display:none; }
.auth-form-panel h1{ font-size:20px; margin-bottom:6px; }
.auth-form-panel .sub{ font-size:13.5px; color:var(--muted); margin-bottom:24px; }
.auth-panel-signin{ z-index:2; }
.auth-panel-forgot{ z-index:1; opacity:0; pointer-events:none; }
.auth-card-split.auth-right-active .auth-panel-signin{ transform:translateX(100%); opacity:0; pointer-events:none; z-index:1; }
.auth-card-split.auth-right-active .auth-panel-forgot{ transform:translateX(100%); opacity:1; pointer-events:auto; z-index:5; }

.auth-accent{
  position:absolute; top:0; left:50%; width:50%; height:100%; overflow:hidden; z-index:10;
  background:linear-gradient(150deg, #061428 0%, var(--primary-600) 55%, var(--primary) 100%);
  transition:transform .6s cubic-bezier(.65,0,.35,1);
}
.auth-accent::before{
  content:""; position:absolute; inset:0; z-index:0; opacity:.5; pointer-events:none;
  background-image:radial-gradient(circle at 1px 1px, rgba(255,255,255,.14) 1px, transparent 0);
  background-size:26px 26px;
}
.auth-accent-shape{ position:absolute; border-radius:50%; filter:blur(60px); opacity:.35; z-index:0; pointer-events:none; animation:authBlobFloat 16s ease-in-out infinite alternate; }
.auth-accent-shape.s1{ width:220px; height:220px; background:#5ea3ff; top:-70px; left:15%; }
.auth-accent-shape.s2{ width:180px; height:180px; background:#7fe8c0; bottom:-60px; right:5%; animation-duration:20s; animation-delay:-6s; }
.auth-card-split.auth-right-active .auth-accent{ transform:translateX(-100%); }

.auth-accent-panel{
  position:absolute; inset:0; z-index:1; padding:44px 38px;
  display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center;
  color:#fff; transition:opacity .35s ease, transform .45s ease;
}
.auth-accent-panel svg{ width:38px; height:38px; margin-bottom:16px; opacity:.9; }
.auth-accent-panel h2{ font-family:'Sora',sans-serif; font-size:19px; margin-bottom:10px; color:#fff; }
.auth-accent-panel p{ font-size:13px; line-height:1.6; opacity:.85; margin-bottom:22px; }
.auth-accent-panel .btn{ background:transparent; border:1.5px solid rgba(255,255,255,.7); color:#fff; }
.auth-accent-panel .btn:hover{ background:rgba(255,255,255,.12); border-color:#fff; }
.auth-accent-signin{ opacity:1; transform:none; transition-delay:.2s; }
.auth-accent-forgot{ opacity:0; transform:translateY(10px); pointer-events:none; }
.auth-card-split.auth-right-active .auth-accent-signin{ opacity:0; transform:translateY(-10px); pointer-events:none; transition-delay:0s; }
.auth-card-split.auth-right-active .auth-accent-forgot{ opacity:1; transform:none; pointer-events:auto; transition-delay:.2s; }

@media (prefers-reduced-motion:reduce){
  .auth-card-split{ animation:none; }
  .auth-form-panel, .auth-accent, .auth-accent-panel, .auth-accent-shape{ transition:none; animation:none; }
}
@media (max-width:680px){
  .auth-card-split{ display:block; width:420px; min-height:0; }
  .auth-accent{ display:none; }
  .auth-form-panel{ position:static; width:100%; padding:40px 32px; transform:none !important; opacity:1 !important; pointer-events:auto !important; transition:none; }
  .auth-panel-forgot{ display:none; }
  .auth-card-split.auth-right-active .auth-panel-signin{ display:none; }
  .auth-card-split.auth-right-active .auth-panel-forgot{ display:flex; }
}

/* ------------- static split card (reset-password / verify-otp) ------------ */
/* Same two-tone look as .auth-card-split but no swap partner, so it's a
   plain flex layout instead of the absolutely-positioned sliding version. */
.auth-static-split{
  width:760px; max-width:100%; display:flex; overflow:hidden; padding:0;
  animation:authCardIn .5s cubic-bezier(.22,.85,.32,1) both;
}
.auth-static-form{ width:50%; padding:48px 46px; display:flex; flex-direction:column; justify-content:center; text-align:left; }
.auth-static-form img.logo-full{ height:32px; width:auto; margin-bottom:18px; display:block; object-fit:contain; }
html[data-bs-theme="dark"] .auth-static-form img.logo-full-light{ display:none; }
html:not([data-bs-theme="dark"]) .auth-static-form img.logo-full-dark{ display:none; }
.auth-static-form h1{ font-size:20px; margin-bottom:6px; }
.auth-static-form .sub{ font-size:13.5px; color:var(--muted); margin-bottom:24px; }
.auth-static-accent{
  width:50%; position:relative; overflow:hidden; isolation:isolate;
  background:linear-gradient(150deg, #061428 0%, var(--primary-600) 55%, var(--primary) 100%);
  display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center;
  padding:44px 38px; color:#fff;
}
.auth-static-accent::before{
  content:""; position:absolute; inset:0; z-index:0; opacity:.5; pointer-events:none;
  background-image:radial-gradient(circle at 1px 1px, rgba(255,255,255,.14) 1px, transparent 0);
  background-size:26px 26px;
}
.auth-static-accent > *{ position:relative; z-index:1; }
.auth-static-accent svg{ width:38px; height:38px; margin-bottom:16px; opacity:.9; }
.auth-static-accent h2{ font-family:'Sora',sans-serif; font-size:19px; margin-bottom:10px; color:#fff; }
.auth-static-accent p{ font-size:13px; line-height:1.6; opacity:.85; }

@media (prefers-reduced-motion:reduce){ .auth-static-split{ animation:none; } .auth-static-accent .auth-accent-shape{ animation:none; } }
@media (max-width:680px){
  .auth-static-split{ display:block; width:420px; }
  .auth-static-accent{ display:none; }
  .auth-static-form{ width:100%; }
}

/* --------------------------- public webform hero --------------------------- */
/* Header banner on the public submission page (webforms/public_form.html):
   the form's own image with its name/description overlaid on it, if it has
   one — else the campus color as background with the same name/description
   printed directly on it. When a campus image exists, it appears as a small
   round badge (image case: overlapping the bottom of the banner; text case:
   sitting inline above the title). */
.form-hero{ position:relative; width:100%; min-height:150px; display:flex; align-items:center; justify-content:center; }
.form-hero-img{ width:100%; height:220px; object-fit:cover; display:block; }
.form-hero-overlay{
  position:absolute; left:0; right:0; bottom:0; padding:20px 34px 50px; z-index:1;
  background:linear-gradient(180deg, transparent 0%, rgba(0,0,0,.5) 45%, rgba(0,0,0,.88) 100%);
}
.form-hero-overlay h1{
  font-family:'Sora',sans-serif; font-size:21px; font-weight:800; margin:0 0 6px;
  text-shadow:0 1px 4px rgba(0,0,0,.5);
}
.form-hero-overlay .form-hero-desc{ text-shadow:0 1px 3px rgba(0,0,0,.5); }
.form-hero-badge{
  position:absolute; right:24px; bottom:-40px; width:84px; height:84px; border-radius:18px; overflow:hidden;
  display:flex; align-items:center; justify-content:center; border:3px solid var(--surface); box-shadow:var(--shadow); z-index:2;
}
.form-hero-badge img{ width:100%; height:100%; object-fit:contain; }
.form-hero-body{ padding:32px 34px; }
.form-hero-body.has-badge{ padding-top:60px; }

/* Text-only hero — shown instead of an image when the webform has none: just
   the campus color as background with the webform's own text_color (set in
   the builder) so the title/description stay legible on it, no extra badge. */
.form-hero-text{ flex-direction:column; align-items:flex-start; justify-content:center; padding:30px 34px; }
.form-hero-text h1{ font-family:'Sora',sans-serif; font-size:22px; font-weight:800; margin:0 0 8px; }
.form-hero-desc{ font-size:14px; line-height:1.55; opacity:.9; }

/* Shared with the homepage hero blobs (static/css/pages/style.css) — defined
   here too so the footer's floating blobs still animate on pages that don't
   load the homepage's stylesheet (login, public webforms, error pages). */
@keyframes heroBlobFloat{
  0%{ transform:translate(0,0) scale(1); }
  100%{ transform:translate(30px,-24px) scale(1.12); }
}

/* ------------------------------- site footer -------------------------------- */
.site-footer{
  position:relative; overflow:hidden; isolation:isolate;
  background:linear-gradient(160deg, #061428 0%, var(--primary-600) 55%, var(--primary) 100%);
  color:rgba(255,255,255,.82);
}
.site-footer::before{
  content:""; position:absolute; inset:0; z-index:0; opacity:.5;
  background-image:radial-gradient(circle at 1px 1px, rgba(255,255,255,.12) 1px, transparent 0);
  background-size:22px 22px;
}
.footer-blob{
  position:absolute; border-radius:50%; filter:blur(80px); opacity:.28; z-index:0;
  animation:heroBlobFloat 18s ease-in-out infinite alternate;
}
.footer-blob.f1{ width:360px; height:360px; background:#ef7e1a; top:-140px; left:-80px; }
.footer-blob.f2{ width:320px; height:320px; background:#004080; bottom:-160px; right:-60px; animation-duration:22s; animation-delay:-6s; }

.site-footer-inner{
  position:relative; z-index:1; max-width:1280px; margin:0 auto; padding:56px 40px 32px;
  display:grid; grid-template-columns:1.4fr 1fr 1fr 1fr; gap:28px;
}
.site-footer-col{ display:flex; flex-direction:column; gap:10px; font-size:13px; }
.site-footer-brand{ font-family:'Playfair Display',serif; font-weight:800; font-size:16px; color:#fff; margin-bottom:2px; }
.site-footer-col p{ margin:0; color:rgba(255,255,255,.62); line-height:1.6; }
/* <button> (a mobile accordion toggle below 520px) but styled to read exactly
   as the old <span> heading on desktop. */
.site-footer-head{
  font-family:inherit; font-size:11.5px; font-weight:800; text-transform:uppercase; letter-spacing:.6px;
  color:rgba(255,255,255,.4); margin-bottom:2px;
  background:none; border:0; padding:0; text-align:left; cursor:default;
}
.site-footer-caret{ display:none; }
.site-footer-links-inner{ display:flex; flex-direction:column; gap:10px; }
.site-footer-col a{ color:rgba(255,255,255,.82); transition:color .2s ease, transform .2s ease; width:fit-content; }
.site-footer-col a:hover{ color:#ff9a45; transform:translateX(3px); }
.site-footer-app{ display:flex; align-items:center; gap:8px; }
.site-footer-social{
  position:relative; z-index:1; max-width:1280px; margin:0 auto; padding:24px 40px 28px;
  display:flex; align-items:center; gap:18px; flex-wrap:wrap;
  border-top:1px solid rgba(255,255,255,.1);
}
.site-footer-social-label{
  font-size:11.5px; font-weight:800; text-transform:uppercase; letter-spacing:.6px;
  color:rgba(255,255,255,.45); margin-right:auto;
}
.site-footer-social-icons{ display:flex; align-items:center; gap:10px; }
.site-footer-social a{
  width:34px; height:34px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.14); color:rgba(255,255,255,.7);
  transition:color .2s ease, background .2s ease, border-color .2s ease, transform .2s ease, box-shadow .2s ease;
}
.site-footer-social a svg{ display:block; }
.site-footer-social a:hover{
  color:#fff; border-color:#ff9a45; background:rgba(255,154,69,.16);
  transform:translateY(-2px); box-shadow:0 8px 18px rgba(255,154,69,.22);
}
.site-footer-bottom{
  position:relative; z-index:1; text-align:center; padding:18px 40px; font-size:11.5px; color:rgba(255,255,255,.5);
  border-top:1px solid rgba(255,255,255,.12);
}
.site-footer-bottom a{ color:rgba(255,255,255,.85); font-weight:700; text-decoration:underline; }
.site-footer-bottom a:hover{ color:#ff9a45; }
@media (prefers-reduced-motion:reduce){ .footer-blob{ animation:none; } }
@media (max-width:820px){
  .site-footer-inner{ grid-template-columns:1fr 1fr; }
  .public-header-inner{ padding:12px 16px; gap:14px; }
}
@media (max-width:520px){
  .site-footer-inner{ grid-template-columns:1fr; padding:32px 18px 8px; gap:0; }
  .site-footer-social{ padding:20px 18px 22px; gap:14px; }
  .site-footer-social-label{ margin-right:0; }
  .site-footer-bottom{ padding:16px 18px; }

  /* Link columns collapse into tap-to-open accordions; the intro column
     (logo + blurb) stays open. */
  .site-footer-col{ gap:0; }
  .site-footer-col--intro{ gap:10px; padding-bottom:20px; }
  .site-footer-col:not(.site-footer-col--intro){ border-top:1px solid rgba(255,255,255,.12); }

  .site-footer-head{
    display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%;
    padding:16px 0; margin-bottom:0; cursor:pointer;
    font-family:'Playfair Display',serif; font-size:15px; font-weight:700;
    text-transform:none; letter-spacing:0; color:#fff;
  }
  .site-footer-caret{ display:block; color:#ff9a45; flex-shrink:0; transition:transform .22s ease; }
  .site-footer-head[aria-expanded="true"] .site-footer-caret{ transform:rotate(180deg); }

  .site-footer-links{ display:grid; grid-template-rows:0fr; transition:grid-template-rows .25s ease; }
  .site-footer-head[aria-expanded="true"] + .site-footer-links{ grid-template-rows:1fr; }
  .site-footer-links-inner{ overflow:hidden; gap:0; }
  .site-footer-links-inner a{ padding:9px 0; }
  .site-footer-head[aria-expanded="true"] + .site-footer-links .site-footer-links-inner{ padding-bottom:12px; }
}
@media (prefers-reduced-motion:reduce){
  .site-footer-links{ transition:none; }
}

/* =====================================================================
   RESPONSIVE OVERRIDES — kept together at the very end of this file on
   purpose. CSS resolves same-specificity ties by source order, and almost
   every rule below shares its selector's specificity with that selector's
   own base definition earlier in this file (e.g. .grid-stats, .grid-2col,
   .stat-card, .modal, .page-head…). If these overrides lived earlier in
   the file (as an earlier pass of this file mistakenly had them, right
   after the layout section), the later base rule would always win the
   cascade regardless of viewport — silently no-opping the whole block on
   every screen size. Keep ANY new responsive rule added in the future
   down here too, after its target's base rule, or it will suffer the same
   fate.
   ===================================================================== */
@media(max-width:1100px){
  .grid-stats{ grid-template-columns:repeat(2,1fr); }
  .hero-grid{ grid-template-columns:1fr; }
  .grid-2col{ grid-template-columns:1fr; }
  .grid-2col-4-8{ grid-template-columns:1fr; }
  .admin-preview-grid, .admin-preview-grid.cols-2{ grid-template-columns:repeat(2,1fr); }
  .form-grid{ grid-template-columns:1fr; }
  .schedule-row{ grid-template-columns:1fr 1fr; }
  .mini-grid{ grid-template-columns:1fr; }
  .settings-grid-stats-3{ grid-template-columns:repeat(2,1fr) !important; }
  .faq-manager-grid{ grid-template-columns:220px 1fr !important; }
}
@media(max-width:820px){
  .sidebar{ transform:translateX(-100%); }
  .sidebar.open{ transform:translateX(0); }
  .main{ margin-left:0; }
  .sidebar.collapsed ~ .main{ margin-left:0; }
  /* Scoped to a direct .topbar child so this doesn't also hide the search
     form reused inside #mobileSearchModal (same .search-box class, but
     nested in a modal, not the topbar). */
  .topbar > .search-box{ display:none; }
  .sidebar-brand{ justify-content:space-between; }
  .sidebar-brand .drawer-close-btn{ display:flex; }
  .grid-stats{ grid-template-columns:1fr; }
  .stat-hero-row{ grid-template-columns:1fr; }
  /* Matches .grid-stats above — the sidebar's already gone off-canvas by
     this width, so admin-preview-grid's 2-up layout (still active from the
     wider @media(max-width:1100px) rule) is just as cramped as the stat
     cards would be; both should go full-width together, not wait for the
     narrower @media(max-width:640px) rule further down. */
  .admin-preview-grid, .admin-preview-grid.cols-2{ grid-template-columns:1fr; }
  /* The count-5 layout's "widen the last card to span 2 columns" trick
     (see the base rule) only makes sense against the desktop 3-column grid
     — in a single-column mobile grid it would just force an unwanted
     implicit 2nd column for that one card, so it's turned off here too. */
  .admin-preview-grid.count-5 > :last-child{ grid-column:auto; }
  .settings-grid-stats-3{ grid-template-columns:1fr !important; }
  .content-shell{ margin:0 12px 12px; border-radius:20px; }
  .content{ padding:22px 18px 44px; }
  /* Row-gap is 0 in the base rule (fine for the desktop side-by-side
     layout, where there's only ever one row) — once this collapses to a
     single column the two panels would otherwise stack with zero space
     between them, so the FAQs panel's header touches the last category
     card right above it. */
  .faq-manager-grid{ grid-template-columns:1fr !important; gap:28px 18px !important; }
  .field-row-detail-grid{ grid-template-columns:1fr 1fr !important; }
}
@media(max-width:640px){
  .admin-preview-grid, .admin-preview-grid.cols-2{ grid-template-columns:1fr; }

  /* Shrink every icon/button in the topbar clusters (dashboard topbar AND
     the public header — logo, hamburger, theme toggle, quick actions, bell,
     avatar) so the whole row comfortably fits a phone screen instead of
     crowding/overflowing it. */
  .topbar{ padding:0 12px; gap:8px; }
  .topbar-right{ gap:3px; }
  /* .public-header-inner is a 3-column CSS grid (brand | header-right |
     hamburger) — .public-nav is taken out of grid flow at this width
     (position:fixed, the off-canvas drawer), so those are the only 3 grid
     items left, and this gap:0 is the actual column-gap BETWEEN them. The
     8px added to .public-header-right below only closes the search↔toggle
     gap INSIDE that div — it's a completely different gap from this one,
     which is why the toggle still sat flush against the hamburger even
     after that fix. Compensated the same way .public-brand already does on
     its own side (margin-right, not the shared grid gap, so it doesn't
     also widen the logo↔search gap unintentionally). */
  .public-header-inner{ padding:10px 12px; gap:0px; }
  .public-brand{ margin-right:10px; }
  .public-nav-toggle{ margin-left:8px; }
  /* 3px read as the search circle/theme-toggle/hamburger touching each
     other — bumped up alongside making the theme-toggle's own buttons the
     same 32px as .icon-btn/.nav-search-trigger right below, so all three
     controls in this cluster are visually one consistent size with real
     breathing room between them, not a mix of 32px/26px crowded together. */
  .public-header-right{ gap:8px; }
  .icon-btn{ width:32px; height:32px; border-radius:50%; }
  .icon-btn svg{ width:15px; height:15px; }
  .public-brand{ gap:6px; }
  .public-brand img.logo-mark{ width:38px; height:auto; border-radius:8px; }
  .theme-toggle{ width:32px; height:32px; border-radius:50%; }
  .theme-toggle button{ border-radius:50%; }
  .theme-toggle button svg{ width:15px; height:15px; }
  .avatar{ width:28px; height:28px; border-radius:50%; font-size:11.5px; }
  .topbar-divider{ display:none; }
  .dot{ width:6px; height:6px; top:6px; right:7px; }

  .public-utility-inner{ padding:7px 12px; gap:8px; }
  .utility-links a:not(.utility-admissions-btn){ display:none; }
  .utility-links{ gap:12px; }
  .utility-divider{ display:none; }
  .utility-back span{ display:none; }
  .utility-admissions-btn{ padding:6px 12px; }

  .page-head{ flex-direction:column; align-items:stretch; gap:14px; margin-bottom:22px; padding:2px 0 20px 16px; }
  .page-head h1{ font-size:22px; }
  .flex{ flex-wrap:wrap; }

  /* .compact-card marks the templates' one-off "bare" cards that carry their
     own inline padding directly on .card (not the .card-head/.card-body
     structured ones, which would break — the head's border would no longer
     span edge-to-edge if .card itself gained padding too). */
  .compact-card{ padding:14px !important; }
  .form-hero-body{ padding:22px 18px; }
  .form-hero-body.has-badge{ padding-top:48px; }
  .form-hero-overlay{ padding:16px 18px 40px; }
  .form-hero-text{ padding:22px 18px; }
  .card-head{ padding:14px 16px; }
  .card-body{ padding:14px; }
  .stat-card{ padding:16px; }
  .settings-card{ padding:2px 14px !important; }
  .filter-bar{ padding:12px 14px; }
  .empty-page{ padding:44px 16px; }

  .modal{ border-radius:16px; }
  .modal-head{ padding:16px 18px; }
  .modal-body{ padding:16px; }

  .notif-row-outer{ flex-wrap:wrap; }

  .form-grid{ gap:0; }
  .field-row-grid, .field-row-detail-grid{ grid-template-columns:1fr !important; }

  th, td{ padding:10px 12px; }
  .filter-row{ gap:8px; }
  .status-tabs{ width:100%; }
}
@media(max-width:480px){
  .content{ padding:16px 12px 36px; }
  .content-shell{ margin:0 8px 8px; border-radius:16px; }
  .grid-stats{ gap:12px; }
  .stat-value{ font-size:23px; }
  .modal-backdrop{ padding:10px; }
  .modal{ width:100%; }
}

/* .menu-panel (quick actions / notifications / profile dropdowns in the
   topbar) is normally position:absolute, anchored via right:0 to whichever
   icon-btn triggered it (see .menu-wrap). On a narrow phone the trigger
   sits close to the screen's right edge, so a 280–340px-wide panel
   anchored there overflows off the LEFT edge of the viewport and gets
   clipped — hence text like "Quick actions" reading as "k actions". Below
   640px it switches to position:fixed, which re-anchors it to the
   viewport itself instead of the trigger, so it can be pinned to a safe
   margin on both sides regardless of which icon opened it. */
@media(max-width:640px){
  .menu-panel{ position:fixed; top:calc(var(--topbar-height) + 8px); left:12px; right:12px; width:auto; max-width:none; }
  #notificationsMenu{ width:auto; }
}
/* .public-nav .menu-panel (campus dropdowns inside the mobile nav drawer)
   already gets its own static, full-width treatment — more specific than
   the rule above, so it isn't affected by it. */

@media(max-width:640px){
  .detail-header{ flex-direction:column; align-items:stretch; gap:14px; padding:2px 0 20px 16px; margin-bottom:24px; }
  .detail-header > div:first-child{ flex:none; }
  .detail-header-actions{ width:100%; }
}
