/* Kitsune Nexus org-facing app - mobile-first from the ground up. Unlike
   the admin panel's persistent desktop sidebar, navigation here is a
   fixed top app bar (current context + hamburger menu), matching how
   the marketing site's mobile nav already works - proven pattern for a
   small-viewport-first surface, rather than adapting a sidebar down. */

:root {
  --color-bg: #f7f6f2;
  --color-surface: #eeebe4;
  --color-surface-2: #e3dfd5;
  --color-border: #d8d6cd;
  --color-text: #2c2c2a;
  --color-text-secondary: #6b6a63;
  --color-accent: #C35817;
  --color-accent-text: #ffffff;
  --color-success: #3a6b46;
  --color-warning: #9c6b1a;
  --color-danger: #b91c1c;

  --badge-success-bg: #dcfce7; --badge-success-text: #15803d;
  --badge-warning-bg: #fef3c7; --badge-warning-text: #b45309;
  --badge-danger-bg: #fee2e2;  --badge-danger-text: #b91c1c;
  --badge-teal-bg: #ccfbf1;    --badge-teal-text: #0f766e;
  --badge-indigo-bg: #e0e7ff;  --badge-indigo-text: #3730a3;
}

[data-theme="dark"] {
  --color-bg: #1c1c1a;
  --color-surface: #262623;
  --color-surface-2: #333330;
  --color-border: #46453f;
  --color-text: #ece9e0;
  --color-text-secondary: #a6a49a;
  --color-accent: #C35817;
  --color-accent-text: #ffffff;
  --color-success: #7fb08a;
  --color-warning: #d9a94a;
  --color-danger: #e57373;

  --badge-success-bg: #14361f; --badge-success-text: #7fb08a;
  --badge-warning-bg: #3a2e13; --badge-warning-text: #d9a94a;
  --badge-danger-bg: #3a1414;  --badge-danger-text: #e57373;
  --badge-teal-bg: #0f2e2b;    --badge-teal-text: #5eead4;
  --badge-indigo-bg: #1e1b4b;  --badge-indigo-text: #a5b4fc;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg: #1c1c1a;
    --color-surface: #262623;
    --color-surface-2: #333330;
    --color-border: #46453f;
    --color-text: #ece9e0;
    --color-text-secondary: #a6a49a;
    --color-success: #7fb08a;
    --color-warning: #d9a94a;
    --color-danger: #e57373;
  }
}

* { box-sizing: border-box; }

/* Custom scrollbars (WebKit/Blink only) - same treatment as the rest of
   the platform, theme-variable-driven so it adapts automatically. */
::-webkit-scrollbar { height: 10px; width: 10px; }
::-webkit-scrollbar-track { background: var(--color-surface-2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.15); border-radius: 5px; }
::-webkit-scrollbar-thumb { background: var(--color-accent); border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { filter: brightness(0.85); }
::-webkit-scrollbar-corner { background: rgba(0, 0, 0, 0); }

html, body {
  margin: 0;
  padding: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 16px; /* never smaller - iOS Safari auto-zooms on input focus below 16px */
  line-height: 1.5;
  height: 100%;
}
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

h1, h2, h3 { margin: 0 0 0.5rem; line-height: 1.25; }
p { margin: 0 0 0.75rem; }

.subtitle { color: var(--color-text-secondary); font-weight: 600; }

/* ---------------------------------------------------------------------
   Top app bar - fixed, mobile-first. Shows the current context (org /
   location name once those exist) and a hamburger toggling the nav
   drawer. Content padding-top offsets the bar's fixed height.
--------------------------------------------------------------------- */
.app-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 3.5rem;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1rem;
  z-index: 10;
}

/* Guardian ("Family Portal") header - deliberately visually distinct
   from the staff app-bar above: a normal-flow header (not fixed) with
   an accent-coloured border and tag, rather than the staff app's
   neutral fixed bar - confirmed deliberately that this frontend should
   look different, not just be permission-gated views of the same UI. */
.guardian-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  background: var(--color-surface);
  border-bottom: 3px solid var(--color-accent);
  flex-wrap: wrap;
  gap: 0.75rem;
}
.guardian-header-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 600;
  font-size: 1.05rem;
}
.guardian-header-logo { height: 32px; width: 32px; }
.guardian-header-tag {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-accent);
  margin-left: 0.4rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--color-accent);
  border-radius: 999px;
}
.guardian-header-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.guardian-body .app-main {
  padding-top: 2rem;
}
.app-bar-title {
  font-weight: 700;
  font-size: 1.05rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-bar-title .accent { color: var(--color-accent); }
.hamburger-btn {
  background: none;
  border: none;
  color: var(--color-text);
  font-size: 1.4rem;
  padding: 0.4rem;
  cursor: pointer;
  line-height: 1;
}

.nav-drawer {
  position: fixed;
  top: 3.5rem; left: 0; right: 0; bottom: 0;
  background: var(--color-bg);
  z-index: 9;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 1rem;
  transform: translateX(-100%);
  transition: transform 0.2s ease;
  display: flex;
  flex-direction: column;
}
.nav-drawer.open { transform: translateX(0); }
.nav-brand {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.4rem 1.25rem;
  margin-bottom: 0.5rem;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.nav-brand-logo { width: 28px; height: auto; flex-shrink: 0; }
/* The organization's own uploaded logo, not the app's - can't assume
   a square/icon-friendly aspect ratio the way the fixed app logo can,
   so both dimensions are capped and object-fit keeps it proportional
   rather than distorting a wide or tall upload to fit a fixed width.
   Shared wherever a small, inline org logo appears (nav-brand,
   choose-connection's own list), not scoped to the nav specifically. */
.org-logo-sm { max-width: 32px; max-height: 32px; width: auto; height: auto; object-fit: contain; flex-shrink: 0; }
.nav-brand-title {
  font-weight: 700;
  font-size: 1.05rem;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
}
.nav-brand-title .accent { color: var(--color-accent); }
.nav-switcher {
  padding: 0 0.5rem 0.75rem;
  flex-shrink: 0;
}
.nav-switcher-select {
  width: 100%;
  padding: 0.5rem 2rem 0.5rem 0.6rem;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background-color: var(--color-surface-2);
  color: var(--color-text);
  font-size: 0.85rem;
  font-weight: 600;
  /* Native select arrows are part of browser chrome, not the text
     content - they don't reliably respect padding-right the way text
     does, which is why simply adding more padding didn't move it.
     Disabling the native arrow and drawing a custom one via
     background-image gives real, consistent control over its position. */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%238a8a8a' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 10px 10px;
}
.nav-links {
  flex: 1 1 auto;
  overflow-y: auto;
}
.nav-links a {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 0.5rem;
  color: var(--color-text);
  text-decoration: none;
  font-weight: 600;
  border-bottom: 1px solid var(--color-border);
}
.nav-links .nav-icon { width: 20px; flex-shrink: 0; text-align: center; }

.nav-section-toggle {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.9rem 0.5rem;
  background: none;
  border: none;
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text);
  font-weight: 600;
  font-size: 1rem;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}
.nav-section-caret {
  margin-left: auto;
  font-size: 0.7rem;
  color: var(--color-text-secondary);
  transition: transform 0.15s ease;
  flex-shrink: 0;
}
.nav-section.open .nav-section-caret { transform: rotate(180deg); }
.nav-section-items {
  display: flex;
  flex-direction: column;
  background: var(--color-surface-2);
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease;
}
.nav-section.open .nav-section-items {
  /* display:none/flex can't be animated directly (display isn't an
     animatable property) - max-height is the standard technique for a
     smooth accordion transition instead. 500px is a deliberately
     generous fixed ceiling, not a measurement of actual content - real
     content only ever needs a fraction of it (currently one item per
     section), so this comfortably covers headroom for more items later
     without needing to recalculate. */
  max-height: 500px;
}
.nav-section-items a {
  padding-left: 2.75rem; /* indented to show nesting under its parent section */
}
.nav-bottom {
  margin-top: auto;
  padding-top: 0.75rem;
  flex-shrink: 0;
}
.nav-bottom button {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.75rem;
}

.app-main {
  padding: 4.5rem 1rem 2rem;
  max-width: 640px;
  margin: 0 auto;
  width: 100%;
  flex: 1;
  display: flex;
  flex-direction: column;
}
@media (min-width: 900px) {
  .app-main { max-width: 720px; }
}
.app-main-wide {
  max-width: 1100px;
}
/* Genuinely the full remaining width next to the sidebar, not just a
   wider capped column like every other app-main variant above - no
   max-width at all, so .app-main's own existing width:100% simply
   fills whatever space is actually available. */
.app-main-full {
  max-width: none;
}
/* Staff page specifically - single-column layout (stacked .surface
   panels), so 25% wider is simply the container's own max-width * 1.25
   at each breakpoint, unlike app-main-detail's own per-column math
   (640->800, 720->900). */
.app-main-staff {
  max-width: 800px;
}
@media (min-width: 900px) {
  .app-main-staff { max-width: 900px; }
}
/* Student detail page specifically, not the shared app-main-wide -
   computed so each detail-layout column (flex:1, sharing a fixed
   1.25rem/20px gap and .app-main's own 32px horizontal padding, neither
   of which scale with the container) grows genuinely 25% wider, not
   just the overall container. Naively multiplying 1100 by 1.25 would
   overshoot the per-column increase, since that fixed overhead becomes
   a smaller proportion of a larger total. Old column width:
   (1100 - 32 - 20) / 2 = 524px. Target: 524 * 1.25 = 655px per column,
   so total max-width = 655*2 + 20 + 32 = 1362px. */
.app-main-detail {
  max-width: 1362px;
}
.app-main-profile {
  /* Two fixed 600px columns + 1.25rem (20px) gap = 1220px minimum
     content width, plus .app-main's own 32px horizontal padding
     (border-box, so padding eats into max-width) = 1252px minimum.
     1280px leaves a little breathing room rather than an exact-fit
     edge case. */
  max-width: 1280px;
}

.app-footer {
  padding: 1rem;
  border-top: 1px solid var(--color-border);
  text-align: center;
  font-size: 0.8rem;
  color: var(--color-text-secondary);
  flex-shrink: 0;
}
.app-footer .accent { color: var(--color-accent); font-weight: 600; }

/* ---------------------------------------------------------------------
   Auth pages (login, invite acceptance, MFA enrollment) - a separate,
   minimal layout from the authenticated shell above (no nav makes sense
   before you're logged in). .auth-body itself needs no extra rules -
   the shared body{} rule above already makes it a full-height flex
   column. .auth-center-area is what actually centers the card, both
   horizontally and vertically, deliberately as its own nested flex
   container rather than trying to center content AND pin the footer to
   the bottom within one shared flex context - flex "auto" margins and
   justify-content can interact in surprising ways when combined, so
   this keeps the two concerns (centering vs. bottom-pinning) genuinely
   separate instead of relying on getting that interaction right.
--------------------------------------------------------------------- */
.auth-center-area {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2rem 1.25rem;
  width: 100%;
}
.auth-center-area > * {
  max-width: 420px;
  width: 100%;
}
.auth-theme-toggle {
  position: fixed;
  top: 1rem;
  right: 1rem;
  width: auto;
  padding: 0.6rem 0.8rem;
  z-index: 10;
}

/* ---------------------------------------------------------------------
   Desktop: the mobile drawer becomes a persistent sidebar instead of an
   overlay - the app bar (whose only job was branding + the hamburger)
   is no longer needed once the sidebar is always visible with its own
   branding header, matching the admin panel's sidebar convention for
   consistency across both services. Below 900px, everything above this
   block is untouched - same drawer/hamburger behavior as before.
--------------------------------------------------------------------- */
@media (min-width: 900px) {
  .app-bar { display: none; }
  .nav-drawer {
    top: 0;
    right: auto;
    width: 280px;
    background: var(--color-surface);
    border-right: 1px solid var(--color-border);
    transform: none;
    padding: 1.5rem 1rem;
    z-index: 5;
  }
  .app-body:not(.guardian-body) .app-main {
    width: calc(100% - 280px);
    margin-left: 280px;
    padding-top: 2rem;
  }
  /* Scoped to .app-body specifically (base.html's <body> class) - the
     auth pages' footer (under .auth-body, no sidebar at all) must not
     pick up this offset. Spans the full remaining width next to the
     sidebar, rather than being squeezed into app-main's own narrower
     max-width like it was before. */
  .app-body .app-footer {
    width: calc(100% - 280px);
    margin-left: 280px;
  }
}

.surface {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}

.btn {
  background: var(--color-accent);
  color: var(--color-accent-text);
  border: none;
  border-radius: 8px;
  padding: 0.75rem 1.25rem;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  min-height: 44px; /* Apple's HIG minimum comfortable tap target */
}
.btn-danger {
  background: var(--color-danger);
  color: #fff;
}
.theme-toggle {
  background: var(--color-surface-2);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.75rem 1.25rem;
  font-size: 1rem;
  cursor: pointer;
  min-height: 44px;
}

label { display: block; font-weight: 600; margin-bottom: 0.3rem; font-size: 0.9rem; }
input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="tel"], input[type="date"], input[type="time"], input[type="url"], select, textarea {
  width: 100%;
  padding: 0.75rem 0.85rem;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
  margin-bottom: 1rem;
  font-family: inherit;
  font-size: 1rem; /* matches html/body - avoids iOS auto-zoom on focus */
  line-height: 1.5; /* explicit, not left to each element's own default - a select's own default can differ from a text input's, and either one left unset risks too little room for descenders (g, y, p, q, j) once height below is fixed rather than a min-height floor */
  min-height: 44px;
  height: 50px; /* 24px padding + 2px border + 24px line-height (1.5 * 16px) - computed precisely, not guessed, so nothing needs more room than this actually provides */
}
textarea { min-height: 88px; resize: vertical; }

.actions { display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1rem; flex-wrap: wrap; }
.error { color: var(--color-danger); font-size: 0.9rem; }

.badge {
  display: inline-block;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  border: 1px solid currentColor;
}
[data-theme="dark"] .badge { border: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .badge { border: none; }
}
.badge-success { background: var(--badge-success-bg); color: var(--badge-success-text); }
.badge-warning { background: var(--badge-warning-bg); color: var(--badge-warning-text); }
.badge-danger  { background: var(--badge-danger-bg);  color: var(--badge-danger-text); }

/* Toast notifications - mobile default is top-center (clear of any
   on-screen keyboard when a form field is focused, which is exactly
   when most of these fire, and matches iOS's own system-banner
   convention most people already have a mental model for). Desktop
   switches to bottom-right at the same 900px breakpoint used
   everywhere else in this app - out of the way of page content and the
   nav's own location/organization switchers up top. */
#toast-container {
  position: fixed;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  pointer-events: none;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 2rem);
  max-width: 420px;
}
@media (min-width: 900px) {
  #toast-container {
    top: 1.5rem;
    left: auto;
    transform: none;
    bottom: auto;
    right: 1.5rem;
    width: auto;
  }
}
.toast {
  pointer-events: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-accent);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.9rem;
  max-width: 420px;
  animation: toast-in-mobile 0.25s ease-out;
}
@media (min-width: 900px) {
  .toast {
    animation: toast-in-desktop 0.25s ease-out;
  }
}
.toast.toast-hide {
  animation: toast-out 0.2s ease-in forwards;
}
@keyframes toast-in-mobile {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes toast-in-desktop {
  from { opacity: 0; transform: translateX(20px); }
  to { opacity: 1; transform: translateX(0); }
}
@keyframes toast-out {
  from { opacity: 1; }
  to { opacity: 0; }
}
.toast-success { border-left-color: var(--color-success); }
.toast-error   { border-left-color: var(--color-danger); }
.toast-warning { border-left-color: var(--color-warning); }
.toast-info    { border-left-color: var(--color-accent); }
.toast-icon { flex-shrink: 0; margin-top: 0.15rem; }
.toast-success .toast-icon { color: var(--color-success); }
.toast-error .toast-icon   { color: var(--color-danger); }
.toast-warning .toast-icon { color: var(--color-warning); }
.toast-info .toast-icon    { color: var(--color-accent); }
.toast-message { flex: 1; word-break: break-word; }
.toast-close {
  cursor: pointer;
  background: none;
  border: none;
  color: var(--color-text-secondary);
  padding: 0;
  font-size: 1.5rem;
  line-height: 1;
  flex-shrink: 0;
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
}

/* Info tooltip - hover on desktop, tap-to-toggle on mobile (native
   title attributes don't work on touch devices at all, so this needs
   its own click handling rather than relying on :hover alone). */
.info-tooltip {
  position: relative;
  display: inline-block;
  cursor: pointer;
  color: var(--color-text-secondary);
  margin-left: 0.3rem;
}
.info-tooltip .tooltip-bubble {
  display: none;
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--color-text);
  white-space: normal;
  width: 220px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 10;
}
.info-tooltip.open .tooltip-bubble,
.info-tooltip:hover .tooltip-bubble {
  display: block;
}

/* Belt visual - adapted from Kitsune Nexus Tournament's own rank
   rendering (same shapes, tweaked for two colour slots instead of
   three). */
.belt-visual {
  width: 80px;
  height: 24px;
  border-radius: 3px;
  display: inline-block;
  position: relative;
  overflow: hidden;
  border: 1px solid var(--color-border);
  vertical-align: middle;
  flex-shrink: 0;
}
.belt-half-top, .belt-half-bottom {
  position: absolute;
  left: 0;
  right: 0;
  height: 50%;
}
.belt-half-top { top: 0; }
.belt-half-bottom { bottom: 0; }
.belt-stripe-v {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 5px;
}
.belt-stripe-h {
  position: absolute;
  left: 0;
  right: 0;
  height: 3px;
}
#ranks-tbody tr.dragging {
  opacity: 0.4;
}
#ranks-tbody.hide-inactive tr[data-status="inactive"] {
  display: none;
}

/* Colour input - default browser chrome (an inset border around the
   swatch itself, on top of the input's own border) looks jarring
   against this app's own surfaces, especially in dark mode. Removing
   the swatch's own border/radius via the vendor pseudo-elements so the
   outer, theme-coloured border is the only one visible. */
.color-input {
  width: 100%;
  height: 40px;
  padding: 3px;
  cursor: pointer;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  -webkit-appearance: none;
  appearance: none;
}
.color-input::-webkit-color-swatch-wrapper { padding: 0; border-radius: 4px; }
.color-input::-webkit-color-swatch { border: none; border-radius: 4px; }
.color-input::-moz-color-swatch { border: none; border-radius: 4px; }

.badge-teal    { background: var(--badge-teal-bg);    color: var(--badge-teal-text); }
.badge-indigo  { background: var(--badge-indigo-bg);  color: var(--badge-indigo-text); }

/* Switch (toggle) component - same shape as the admin panel's, for
   things like the per-role MFA-required setting. */
.switch { position: relative; display: inline-block; width: 46px; height: 26px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch-slider {
  position: absolute; cursor: pointer; inset: 0;
  background: var(--color-border);
  transition: 0.15s;
  border-radius: 999px;
}
.switch-slider::before {
  content: "";
  position: absolute;
  height: 20px; width: 20px;
  left: 3px; bottom: 3px;
  background: white;
  transition: 0.15s;
  border-radius: 50%;
}
.switch input:checked + .switch-slider { background: var(--color-accent); }
.switch input:checked + .switch-slider::before { transform: translateX(20px); }

dialog.modal {
  border: none;
  border-radius: 12px;
  padding: 1.25rem;
  background: var(--color-surface);
  color: var(--color-text);
  max-width: 420px;
  width: 90%;
}
dialog.modal::backdrop { background: rgba(0, 0, 0, 0.5); }

/* Wider variant for forms with enough fields to benefit from a 2-column
   layout - a separate class, not a change to .modal itself, since most
   modals in this app are short single-column forms that shouldn't get
   wider by default. */
dialog.modal.modal-wide { max-width: 880px; }
/* Wider still than modal-wide - specifically for the Assign Skills
   modal's own multi-column skill grid, which genuinely needs the
   extra room to show 3-4 columns comfortably rather than cramping
   down to fewer. */
dialog.modal.modal-large { max-width: 1100px; }
.progress-bar {
  width: 100%;
  height: 10px;
  background: var(--color-surface-2);
  border-radius: 999px;
  overflow: hidden;
}
.progress-bar-fill {
  height: 100%;
  background: var(--color-accent);
  border-radius: 999px;
}

/* Reusable vertical progress-timeline component - a circle ("node")
   per row connected by a vertical line ("pipe") to the row above and
   below, colored to show how far progress has actually reached.
   Deliberately generic beyond Rank history - built to be reused for
   Modules within a Path too. Each row's own line segments and node get
   the "filled" modifier independently, so a genuinely partial fill
   (some earned, some not yet) renders correctly, not just an
   all-or-nothing state. */
.progress-pipe-cell {
  position: relative;
  width: 2.25rem;
  padding: 0 !important;
}
.progress-pipe-line-top, .progress-pipe-line-bottom {
  position: absolute;
  left: 50%;
  width: 3px;
  transform: translateX(-50%);
  background: var(--color-border);
}
.progress-pipe-line-top { top: 0; height: 50%; }
.progress-pipe-line-bottom { bottom: 0; height: 50%; }
.progress-pipe-row.first .progress-pipe-line-top { display: none; }
.progress-pipe-row.last .progress-pipe-line-bottom { display: none; }
.progress-pipe-node {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--color-border);
  border: 3px solid var(--color-surface);
  z-index: 1;
}
.progress-pipe-row.filled .progress-pipe-node {
  background: var(--color-accent);
}
.progress-pipe-row.filled .progress-pipe-line-top {
  background: var(--color-accent);
}
.progress-pipe-row.line-below-filled .progress-pipe-line-bottom {
  background: var(--color-accent);
}
.sp-module-node {
  width: 9px;
  height: 9px;
}

.sp-pipe-container {
  position: relative;
  padding-left: 1.35rem;
}
.sp-pipe-line {
  position: absolute;
  width: 3px;
  background: var(--color-accent);
  display: none;
  z-index: 0;
  transition: height 0.25s ease, top 0.25s ease;
  transform: translateX(-50%);
}
.sp-path-panel, .sp-module-panel {
  position: relative;
  z-index: 1;
}
.sp-pipe-node-inline {
  position: absolute;
  left: -1.35rem;
  top: 0;
  transform: translate(-50%, -50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--color-accent);
  border: 3px solid var(--color-surface);
  z-index: 2;
}
.sp-module-panel .sp-pipe-node-inline {
  width: 9px;
  height: 9px;
  top: 50%;
}
.sp-module-panel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--color-text);
  cursor: pointer;
}
.sp-module-panel:hover {
  background: var(--color-surface-2);
}

.skills-table {
  width: 100%;
  border-collapse: collapse;
}
.skills-table th {
  text-align: left;
  font-size: 0.8rem;
  color: var(--color-text-secondary);
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
}
.skills-table td {
  padding: 0.65rem 1rem;
  border-bottom: 1px solid var(--color-border);
}
.skills-table tr:last-child td {
  border-bottom: none;
}

.module-reorder-table {
  width: 100%;
  border-collapse: collapse;
  max-height: 30vh;
  margin-bottom: 0.5rem;
}
.module-reorder-table th {
  text-align: left;
  font-size: 0.8rem;
  color: var(--color-text-secondary);
  padding: 0.4rem;
  border-bottom: 1px solid var(--color-border);
}
.module-reorder-table td {
  padding: 0.5rem 0.4rem;
  border-bottom: 1px solid var(--color-border);
}
.module-reorder-table tr.dragging {
  opacity: 0.4;
}
.module-reorder-handle {
  cursor: grab;
  color: var(--color-text-secondary);
  width: 2rem;
}
.module-reorder-table tr[draggable] {
  cursor: default;
}
.module-picker {
  position: relative;
}
input.module-picker-input {
  margin-bottom: 0;
}
.module-picker-dropdown {
  margin: 0;
  inset: auto;
  max-height: calc(5 * 2.5rem);
  overflow-y: auto;
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.module-picker-option {
  height: 2.5rem;
  display: flex;
  align-items: center;
  padding: 0 0.75rem;
  cursor: pointer;
}
.module-picker-option:hover {
  background: var(--color-surface-2);
}

.assign-skills-scroll {
  max-height: 60vh;
  overflow-y: auto;
  margin: 0.5rem 0 1rem;
}
.assign-skills-category {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.75rem 1rem;
  margin-bottom: 0.75rem;
}
.assign-skills-category summary {
  cursor: pointer;
  font-weight: 600;
  padding: 0.25rem 0;
}
.assign-skills-category[open] summary {
  margin-bottom: 0.5rem;
}
.assign-skills-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.25rem 1.5rem;
}
@media (min-width: 700px) {
  .assign-skills-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 950px) {
  .assign-skills-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
.assign-skills-grid .toggle-row {
  border-bottom: none;
  padding: 0.35rem 0;
}
.form-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 1.5rem;
}
@media (max-width: 640px) {
  .form-grid-2 { grid-template-columns: 1fr; }
}

.toggle-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--color-border);
}
.toggle-row:last-child { border-bottom: none; }

.location-card { margin-bottom: 1rem; }
.location-card-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding-bottom: 1rem;
  margin-bottom: 0.25rem;
  border-bottom: 1px solid var(--color-border);
}
/* For a panel with nothing below its own header at all - the border/
   padding otherwise meant to separate it from further content below
   just becomes a stray line with nothing left to separate. */
.location-card-header-only {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: none;
}
.location-card-info { flex: 1; min-width: 180px; }
.location-card-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-left: auto; }

.location-rooms-toggle {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.75rem 0;
  background: none;
  border: none;
  color: var(--color-text);
  font-weight: 600;
  font-size: 0.9rem;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}
/* When a location-rooms-section is wrapped alone in its own .surface
   (student_detail.html's panels, unlike locations.html/roles.html's
   own use of this component nested under other header content
   already providing top spacing) the toggle's own top padding would
   stack on top of .surface's, doubling the gap above the heading text
   compared to Details' own tight spacing. */
.surface > .location-rooms-section:first-child .location-rooms-toggle {
  padding-top: 0;
}
.location-rooms-caret {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--color-text-secondary);
  transition: transform 0.15s ease;
}
.location-rooms-section.open .location-rooms-caret { transform: rotate(180deg); }
.location-rooms-panel {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease;
}
.location-rooms-section.open .location-rooms-panel {
  /* A deliberately generous fixed ceiling, not a measurement of actual
     content - same reasoning as the nav accordion's own panels: real
     content only ever needs a fraction of this, so it comfortably
     covers headroom for more rooms later without needing to
     recalculate. display:none/flex can't be animated directly, so
     max-height is the standard technique for a smooth expand/collapse
     instead of an instant snap. */
  max-height: 900px;
}
.profile-layout {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* Student detail page's main+sidebar layout - equal-width columns
   (confirmed directly, over the initially-built narrower sidebar).
   Still its own, more moderate breakpoint than profile.html's 1550px,
   since this layout needs less total width than profile.html's two
   fixed 600px columns do. */
.detail-layout {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
@media (min-width: 1100px) {
  .detail-layout {
    flex-direction: row;
    align-items: flex-start;
  }
  .detail-main {
    flex: 1;
    min-width: 0;
  }
  .detail-sidebar {
    flex: 1;
    min-width: 0;
  }
}
/* Deliberately its OWN breakpoint, not tied to the nav's 900px
   drawer-to-sidebar switch (confirmed as a two-step transition, not
   one) - two fixed 600px columns need real room that isn't available
   the moment the nav merely expands. See .app-main-profile's own
   comment for the max-width math this number is built on; this adds
   back the 280px nav width the content area loses at 900px+. */
@media (min-width: 1550px) {
  .profile-layout {
    flex-direction: row;
    align-items: flex-start;
  }
  .profile-main {
    width: 600px;
    flex-shrink: 0;
  }
  .profile-sidebar {
    width: 600px;
    flex-shrink: 0;
  }
}

.location-rooms-panel-inner {
  padding-bottom: 1rem;
}
.location-rooms-panel-header {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.75rem;
}
.room-panel-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}
.room-panel {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-2);
}

/* ===========================================================================
   Custom date/time pickers - deliberately built entirely on the CSS
   variables above (--color-surface, --color-border, --color-accent, etc.),
   never a hardcoded color. Native <input type="date">/<input type="time">
   popups are rendered by the OS/browser and CSS cannot reach inside them
   at all - styling the input field itself was never going to touch the
   actual calendar. Building these from scratch on the existing variables
   is what makes them automatically respect light/dark mode today, and any
   future per-organization theme override later - a theme only ever needs
   to redefine the variables above; nothing here needs to know a new theme
   exists. See org_app/static/js/pickers.js for the JS half.
   =========================================================================== */

/* Every type="number" field in this app is a plain non-negative
   integer (ages, durations, counts) - none need the native spinner
   arrows, which can only be shown or hidden via CSS, never restyled
   into something that matches the theme (same fundamental limitation
   as the date/time picker popups above). Removed entirely rather than
   left in their default, unthemeable browser appearance. Actual digit-
   only input restriction is enforced in pickers.js, not just left to
   the browser's own looser type="number" validation (which still
   allows typing "e", "-", "+", "." even though none of this app's
   number fields want them). */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"] {
  -moz-appearance: textfield;
}

.picker-input {
  cursor: pointer;
  background-image: none !important; /* the browser's own calendar icon on the underlying input; the picker draws its own trigger icon instead */
}

.picker-trigger-wrap { position: relative; }
.picker-trigger-icon {
  position: absolute;
  right: 0.85rem;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--color-text-secondary);
  font-size: 0.95rem;
}

.picker-popover {
  position: absolute;
  z-index: 300;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  padding: 0.85rem;
  color: var(--color-text);
  font-size: 0.9rem;
  min-width: 260px;
}

.picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.35rem;
  margin-bottom: 0.6rem;
}
.picker-title { font-weight: 600; }
.picker-month-select {
  width: auto !important;
  padding: 0.2rem 0.4rem !important;
  margin: 0 !important;
  min-height: unset !important;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: 0.85rem;
  font-family: inherit;
  flex: 1;
}
.picker-year-select {
  width: auto !important;
  padding: 0.2rem 0.4rem !important;
  margin: 0 !important;
  min-height: unset !important;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: 0.85rem;
  font-family: inherit;
}
.picker-today-btn {
  display: block;
  width: 100%;
  margin-top: 0.6rem;
  padding: 0.5rem;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-accent);
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: inherit;
  text-align: center;
}
.picker-today-btn:hover { background: var(--color-surface-2); }
.picker-today-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.picker-today-btn:disabled:hover { background: transparent; }
.picker-nav {
  background: transparent;
  border: none;
  color: var(--color-text);
  cursor: pointer;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  font-size: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.picker-nav:hover { background: var(--color-surface-2); }

.picker-weekdays, .picker-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  text-align: center;
}
.picker-weekdays {
  color: var(--color-text-secondary);
  font-size: 0.78rem;
  margin-bottom: 0.3rem;
}
.picker-day {
  background: transparent;
  border: none;
  color: var(--color-text);
  cursor: pointer;
  padding: 0.45rem 0;
  border-radius: 8px;
  font-size: 0.88rem;
}
.picker-day:hover { background: var(--color-surface-2); }
.picker-day.picker-day-outside { color: var(--color-text-secondary); opacity: 0.5; }
.picker-day.picker-day-today { font-weight: 700; color: var(--color-accent); }
.picker-day.picker-day-selected { background: var(--color-accent); color: var(--color-accent-text); }
.picker-day.picker-day-selected:hover { background: var(--color-accent); }
.picker-day:disabled { opacity: 0.3; cursor: not-allowed; }
.picker-day:disabled:hover { background: transparent; }

.picker-popover-time { padding: 0.85rem; min-width: unset; }
.picker-time-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.picker-time-select {
  width: auto !important;
  padding: 0.5rem 0.4rem !important;
  margin: 0 !important;
  min-height: unset !important;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-size: 0.9rem;
  font-family: inherit;
}
.picker-time-done {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
  padding: 0.5rem;
  border-radius: 8px;
  border: none;
  background: var(--color-accent);
  color: var(--color-accent-text);
  cursor: pointer;
  font-size: 0.9rem;
  font-family: inherit;
}

/* Searchable student select - a plain text input + a filtered results
   dropdown, replacing a plain <select> anywhere a school's full
   student roster (potentially hundreds) would otherwise all render as
   options at once. See base.html's own initStudentSearch() for the
   actual filtering behavior. */
.student-search { position: relative; }
input.student-search-input { margin-bottom: 0; }
.student-search-results {
  z-index: 20;
  max-height: 220px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.student-search-result-item {
  padding: 0.5rem 0.7rem;
  cursor: pointer;
  font-size: 0.9rem;
}
.student-search-result-item:hover,
.student-search-result-item.active {
  background: var(--color-surface-2);
}
.student-search-no-results {
  padding: 0.5rem 0.7rem;
  font-size: 0.85rem;
  color: var(--color-text-secondary);
}

/* Curriculum page's own sub-nav tabs - Programs/Paths/Modules/
   Categories/Skills. No prior tab pattern existed elsewhere in this
   app to reuse, so this is a new, lightweight one. */
.curriculum-tabs {
  display: flex;
  gap: 0.5rem;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 1.25rem;
  overflow-x: auto;
}
.curriculum-tab {
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  padding: 0.6rem 0.9rem;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--color-text-secondary);
  cursor: pointer;
  white-space: nowrap;
}
.curriculum-tab.active {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

/* Module content editor - a deliberately small, custom toolbar (not
   Quill's own default one), matching the goal of keeping this as
   simple to use as possible. Not wrapped in a .surface panel -
   confirmed directly the editor should occupy the available space
   directly, not be constrained inside a card.

   .app-main is already display:flex; flex-direction:column; flex:1;
   stretched to fill the viewport by body's own flex setup - this
   wrap extends that same flex chain down through the editor itself,
   rather than a fixed height/min-height that would leave empty space
   below on most screens. min-height:0 on each flex parent in the
   chain matters here - flex items default to min-height:auto, which
   would otherwise stop a nested flex child from actually shrinking/
   growing to fill its own parent correctly. */
.module-editor-page {
  width: 100%;
  flex: 1;
  display: flex;
  gap: 1rem;
  min-height: 0;
  align-items: stretch;
}
.module-editor-wrap {
  width: 100%;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.module-attachments-panel {
  width: 280px;
  flex-shrink: 0;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 1rem;
  overflow-y: auto;
}
@media (max-width: 900px) {
  .module-editor-page {
    flex-direction: column;
  }
  .module-attachments-panel {
    width: 100%;
  }
}
.module-attachment-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--color-border);
}
.module-attachment-row:last-child {
  border-bottom: none;
}
.module-attachment-row > i {
  font-size: 1.1rem;
  color: var(--color-text-muted, var(--color-text));
  flex-shrink: 0;
}
.module-attachment-info {
  flex: 1;
  min-width: 0;
}
.module-attachment-name {
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.module-attachment-meta {
  font-size: 0.75rem;
  color: var(--color-text-muted, var(--color-text));
  opacity: 0.7;
}
.module-attachment-progress-outer {
  background: var(--color-surface-2);
  border-radius: 4px;
  height: 6px;
  overflow: hidden;
  margin-top: 0.3rem;
}
.module-attachment-progress-inner {
  background: var(--color-accent);
  height: 100%;
  transition: width 0.15s ease;
}
.module-attachment-row button {
  flex-shrink: 0;
  min-width: 36px;
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem;
}
#module-toolbar {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  padding: 0.6rem;
  border: 1px solid var(--color-border);
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  background: var(--color-surface-2);
  flex-shrink: 0;
}
/* Quill's own quill.snow.css forces a generic, tiny 28x24px floated
   box on every <button> inside its toolbar, including these custom
   ones - not just its own native format buttons - which is exactly
   why the icons were rendering outside that box's own bounds. This
   overrides that entirely: a real, comfortable tap target with the
   icon actually centered inside it. */
#module-toolbar button {
  float: none;
  width: auto;
  height: auto;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 0.5rem;
  cursor: pointer;
  color: var(--color-text);
}
#module-toolbar button:hover,
#module-toolbar button.ql-active {
  background: var(--color-surface);
  border-color: var(--color-border);
}
#module-toolbar button i {
  font-size: 1.1rem;
  line-height: 1;
  color: inherit;
}
/* Quill's own icons are SVG paths with a hardcoded stroke/fill color
   (#444/#fff, designed for a fixed light background) - invisible
   against this app's own dark theme. Overridden to this app's own
   theme-aware text color so the controls are actually visible in
   both themes, not just readable by accident in one. */
#module-toolbar .ql-stroke {
  stroke: var(--color-text);
}
#module-toolbar .ql-fill {
  fill: var(--color-text);
}
#module-toolbar .ql-picker-label {
  color: var(--color-text);
}
#module-toolbar .ql-picker-options {
  background: var(--color-surface);
  border-color: var(--color-border) !important;
}
#module-toolbar .ql-picker-item {
  color: var(--color-text);
}
#module-toolbar select {
  margin-bottom: 0;
  width: auto;
  min-height: 44px;
  padding: 0.5rem 0.6rem;
}
#module-editor {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 300px;
  border-radius: 0 0 8px 8px;
}
#module-editor .ql-editor {
  flex: 1;
  font-size: 1rem;
}
.module-video-embed {
  position: relative;
  width: 100%;
  max-width: 560px;
  aspect-ratio: 16 / 9;
  margin: 0.75rem 0;
}
.module-video-embed iframe {
  width: 100%;
  height: 100%;
  border-radius: 8px;
}
.module-attachment-embed {
  display: block;
  margin: 0.75rem 0;
}
.module-content-image,
.module-content-video {
  max-width: 100%;
  border-radius: 8px;
}

/* Student Portal - a genuinely separate shell from the staff side's
   own .app-bar/.nav-drawer (hamburger-triggered, mobile-first). This
   is deliberately two persistent layouts instead: a left sidebar on
   desktop, content to the right; a bottom tab bar on mobile/tablet,
   content full-width above it. Same 900px breakpoint the staff side
   already uses elsewhere, for consistency. */
.sp-body {
  margin: 0;
  min-height: 100vh;
  background: var(--color-bg);
  color: var(--color-text);
}
.sp-layout {
  display: flex;
  min-height: 100vh;
}
.sp-sidebar {
  display: none;
}
.sp-content {
  flex: 1;
  min-width: 0;
  padding: 1.25rem;
  padding-bottom: 5rem; /* clears the fixed bottom nav on mobile */
  max-width: 900px;
  margin: 0 auto;
  width: 100%;
  box-sizing: border-box;
}
.sp-topbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
}
.sp-topbar-logo {
  max-height: 28px;
  max-width: 100px;
  object-fit: contain;
}
.sp-topbar-titles {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.sp-topbar-title {
  font-weight: 600;
  font-size: 0.95rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-topbar-subtitle {
  font-size: 0.75rem;
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-hamburger-btn {
  background: none;
  border: none;
  color: var(--color-text);
  font-size: 1.2rem;
  padding: 0.3rem;
  cursor: pointer;
  line-height: 1;
  margin-left: auto;
}
.sp-menu-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
  z-index: 55;
}
.sp-menu-backdrop.open {
  display: block;
}
.sp-menu {
  position: fixed;
  top: 3.2rem;
  right: 0.75rem;
  min-width: 200px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  padding: 0.4rem;
  display: none;
  flex-direction: column;
  gap: 0.15rem;
  z-index: 56;
}
.sp-menu.open {
  display: flex;
}
.sp-menu-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  border-radius: 6px;
  font-size: 0.85rem;
  color: var(--color-text);
}
.sp-menu-item:hover {
  background: var(--color-surface-2);
}
.sp-bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  z-index: 50;
}
.sp-nav-item {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.6rem 0.25rem;
  color: var(--color-text-secondary);
  text-decoration: none;
  font-size: 0.7rem;
}
.sp-bottom-nav .sp-nav-item + .sp-nav-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6rem;
  bottom: 0.6rem;
  width: 1px;
  background: var(--color-border);
}
.sp-nav-item i {
  font-size: 1.1rem;
}
.sp-nav-item.active {
  color: var(--color-accent);
}

@media (min-width: 1366px) {
  .sp-topbar {
    display: none;
  }
  .sp-bottom-nav {
    display: none;
  }
  .sp-sidebar {
    display: flex;
    flex-direction: column;
    width: 230px;
    flex-shrink: 0;
    background: var(--color-surface);
    border-right: 1px solid var(--color-border);
    padding: 1.25rem 0.75rem;
    box-sizing: border-box;
  }
  .sp-sidebar-brand {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0 0.5rem 1.25rem;
    margin-bottom: 0.75rem;
    border-bottom: 1px solid var(--color-border);
  }
  .sp-sidebar-logo {
    max-height: 32px;
    max-width: 130px;
    object-fit: contain;
  }
  .sp-sidebar-title {
    font-weight: 600;
    font-size: 0.9rem;
  }
  .sp-sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    flex: 1;
  }
  .sp-sidebar .sp-nav-item {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: 0.55rem;
    padding: 0.45rem 0.65rem;
    border-radius: 6px;
    font-size: 0.85rem;
  }
  .sp-sidebar .sp-nav-item i {
    font-size: 0.85rem;
    width: 1rem;
    text-align: center;
  }
  .sp-sidebar .sp-nav-item:hover {
    background: var(--color-surface-2);
  }
  .sp-sidebar .sp-nav-item.active {
    background: var(--color-surface-2);
    font-weight: 600;
  }
  .sp-sidebar-footer {
    border-top: 1px solid var(--color-border);
    padding-top: 0.75rem;
    margin-top: 0.75rem;
  }
  .sp-content {
    padding: 2rem;
    padding-bottom: 2rem;
  }
}
