/* =========================================================
   TALK-A-ROO — Web Portal (Teacher + Admin)
   Design tokens
   ========================================================= */
@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@500;600;700&family=Nunito:wght@400;500;600;700;800&display=swap');

:root{
  /* --- color --- */
  --bg: #F7FCFF;
  --surface: #FFFFFF;
  --surface-sunken: #EAF6FD;
  --ink: #1F2A44;
  --ink-soft: #33425F;
  --muted: #4E5D78;
  --border: #DCE9F2;

  /* This now matches the redesigned public homepage's palette (see
     public/index.html), so the portal and the marketing site read as one
     product instead of two unrelated designs. */
  --accent: #1C8447;         /* brand green — primary CTA, same as the homepage's Download button */
  --accent-dark: #135C32;
  --accent-soft: #E6F6EC;

  --indigo: #1F2A44;         /* brand navy — admin / hero surfaces */
  --indigo-dark: #16203A;    /* darkest navy, for large surfaces like the sidebar */
  --indigo-soft: #DDF3FE;    /* light sky tint, for a status/tag on a navy or white bg */

  --blue: #1E7FCF;           /* secondary accent — links, focus rings */
  --blue-dark: #155E9C;

  --teal: #15A194;
  --teal-soft: #E3F7F5;

  --amber: #C9971F;
  --amber-soft: #FFF6D6;

  --danger: #EF4444;
  --danger-soft: #FEE2E2;

  --success: #22C55E;        /* deliberately a different green from --accent, so a
                                 "success" state still reads apart from a primary button */
  --success-soft: #DCFCE7;

  --skeleton-shine: #E9EAF4; /* loading-shimmer highlight stop, themed so it doesn't
                                 flash bright light-gray across a dark-mode card */

  /* --- type --- */
  --font-display: 'Fredoka', system-ui, sans-serif;
  --font-body: 'Nunito', system-ui, sans-serif;
  --font-mono: 'Nunito', system-ui, sans-serif; /* kept as a token so existing var(--font-mono)
                                                    call sites don't need to be found and edited —
                                                    it now just resolves to the body font. */

  /* --- shape / motion --- */
  --radius-sm: 12px;
  --radius-md: 18px;
  --radius-lg: 26px;
  --shadow-sm: 0 1px 2px rgba(31,42,68,0.06);
  --shadow-md: 0 8px 24px rgba(31,42,68,0.08);
  --shadow-lg: 0 16px 48px rgba(31,42,68,0.14);
  --ease: cubic-bezier(.4,0,.2,1);
}

/* =========================================================
   Dark mode — toggled via a "theme-toggle-btn" in each dashboard
   page's sidebar profile (ui.js's wireThemeToggle/applyTheme), saved to
   localStorage so it persists across the whole portal (login pages
   included) without a per-account setting. Only the "light surface"
   tokens are overridden here — the sidebar, auth hero, toasts, etc. were
   already dark navy by design in light mode (var(--indigo-dark) and
   friends), so they intentionally stay exactly as they are.
   ========================================================= */
:root[data-theme="dark"]{
  --bg: #0E1526;
  --surface: #182238;
  --surface-sunken: #101A2C;
  --ink: #F1F5FB;
  --ink-soft: #C8D3E6;
  --muted: #93A2BC;
  --border: #2A3752;

  --accent-dark: #55CC8A;    /* lighter than the light-mode value so text using it
                                 stays readable on a dark --accent-soft tint */
  --accent-soft: rgba(47,175,105,0.16);

  --indigo-soft: rgba(122,178,255,0.16);
  --teal-soft: rgba(21,161,148,0.2);
  --amber-soft: rgba(201,151,31,0.2);
  --danger-soft: rgba(239,68,68,0.18);
  --success-soft: rgba(34,197,94,0.18);

  --skeleton-shine: #263355;

  --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
  --shadow-md: 0 8px 24px rgba(0,0,0,0.45);
  --shadow-lg: 0 16px 48px rgba(0,0,0,0.6);
}

*{ box-sizing: border-box; }
html{ -webkit-font-smoothing: antialiased; }
body{
  margin:0;
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--ink);
  line-height: 1.5;
  transition: background-color .2s var(--ease), color .2s var(--ease);
}
h1,h2,h3,h4,.font-display{ font-family: var(--font-display); font-weight: 700; letter-spacing: -0.01em; }
a{ color: inherit; }
button{ font-family: inherit; }
::selection{ background: var(--accent-soft); color: var(--accent-dark); }

@media (prefers-reduced-motion: reduce){
  *{ animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

:focus-visible{
  outline: 2.5px solid var(--blue);
  outline-offset: 2px;
  border-radius: 6px;
}

/* =========================================================
   Signature element — waveform mark
   A speech-input waveform doubles as the brand mark and as a
   live "listening" indicator across login + dashboard states.
   ========================================================= */
.wave-mark{
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 22px;
}
.wave-mark span{
  width: 3.5px;
  border-radius: 2px;
  background: currentColor;
  animation: wave 1.1s ease-in-out infinite;
}
.wave-mark span:nth-child(1){ height: 40%; animation-delay: -1.0s; }
.wave-mark span:nth-child(2){ height: 100%; animation-delay: -0.7s; }
.wave-mark span:nth-child(3){ height: 65%; animation-delay: -0.5s; }
.wave-mark span:nth-child(4){ height: 90%; animation-delay: -0.3s; }
.wave-mark span:nth-child(5){ height: 50%; animation-delay: -0.1s; }
@keyframes wave{
  0%,100%{ transform: scaleY(0.5); }
  50%{ transform: scaleY(1); }
}

.brand{
  display:flex; align-items:center; gap:10px;
  font-family: var(--font-display);
  font-weight: 800; font-size: 20px;
}
.brand .wave-mark{ color: var(--accent); }
.brand small{
  display:block; font-family: var(--font-body); font-weight: 500;
  font-size: 11px; color: var(--muted); letter-spacing: 0.02em;
}

/* =========================================================
   Auth / login shell
   ========================================================= */
.auth-shell{
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.1fr 1fr;
}
.auth-hero{
  background:
    radial-gradient(680px 420px at 15% -10%, rgba(125,209,245,0.35), transparent 60%),
    linear-gradient(155deg, var(--indigo-dark), var(--indigo) 60%, #0F1830);
  color: #fff;
  padding: 56px;
  display:flex; flex-direction:column; justify-content:space-between;
  position: relative;
  overflow:hidden;
}
.auth-hero::after{
  content:"";
  position:absolute; inset:auto -20% -30% auto;
  width:520px; height:520px; border-radius:50%;
  background: radial-gradient(circle, rgba(255,255,255,0.08), transparent 70%);
}
.auth-hero .brand{ color:#fff; }
.auth-hero .brand small{ color: rgba(255,255,255,0.65); }
.auth-hero h1{
  font-size: 40px; line-height: 1.15; margin: 40px 0 14px;
  max-width: 480px;
}
.auth-hero p.lead{
  font-size: 16px; color: rgba(255,255,255,0.78); max-width: 420px; margin:0 0 28px;
}
.hero-points{ list-style:none; padding:0; margin:0; display:grid; gap:14px; }
.hero-points li{
  display:flex; align-items:flex-start; gap:12px;
  font-size: 14.5px; color: rgba(255,255,255,0.92);
}
.hero-points .dot{
  flex: none; width: 26px; height:26px; border-radius:8px;
  background: rgba(255,255,255,0.14);
  display:flex; align-items:center; justify-content:center;
  font-size: 13px; margin-top:1px;
}
.hero-foot{ font-size:12.5px; color: rgba(255,255,255,0.5); }

.hero-kangaroo-wrap{
  position:absolute; right:16px; bottom:46px;
  text-align:center;
}
.hero-kangaroo{
  width:250px; max-width:48vw; display:block;
  transform-origin: bottom center;
  animation: hero-kangaroo-hop 1.1s ease-in-out infinite;
}
.hero-kangaroo-shadow{
  width:140px; height:22px; margin:6px auto 0; border-radius:50%;
  background: rgba(0,0,0,0.28); filter: blur(3px);
  animation: hero-kangaroo-shadow 1.1s ease-in-out infinite;
}
@keyframes hero-kangaroo-hop{
  0%, 100% { transform: translateY(0) scale(1,1); }
  15%      { transform: translateY(0) scale(1.1,0.86); }
  45%      { transform: translateY(-80px) scale(0.94,1.06); }
  75%      { transform: translateY(0) scale(1.1,0.86); }
}
@keyframes hero-kangaroo-shadow{
  0%, 100% { transform: scale(1); opacity:0.28; }
  45%      { transform: scale(0.55); opacity:0.12; }
}
@media (max-width: 1200px){
  .hero-kangaroo-wrap{ right:8px; bottom:16px; }
  .hero-kangaroo{ width:110px; }
  .hero-kangaroo-shadow{ width:64px; height:12px; }
  @keyframes hero-kangaroo-hop{
    0%, 100% { transform: translateY(0) scale(1,1); }
    15%      { transform: translateY(0) scale(1.1,0.86); }
    45%      { transform: translateY(-32px) scale(0.94,1.06); }
    75%      { transform: translateY(0) scale(1.1,0.86); }
  }
}
@media (prefers-reduced-motion: reduce){
  .hero-kangaroo, .hero-kangaroo-shadow{ animation: none; }
}
/* Must come after every .auth-hero/.hero-* rule above — same selector
   specificity means source order decides, and this has to win. */
@media (max-width: 900px){
  .auth-shell{ grid-template-columns: 1fr; }
  /* Keep the hero panel (and its text) visible on mobile instead of
     hiding it — the kangaroo drops out of absolute positioning into
     normal flow here, so it just takes up its own space below the
     checklist and pushes the page taller/scrollable, never overlapping. */
  .hero-kangaroo-wrap{
    position: static; margin: 24px 0 0;
    display: flex; flex-direction: column; align-items: center;
  }
}

.auth-form-wrap{
  position:relative;
  display:flex; align-items:center; justify-content:center;
  padding: 40px 32px;
}
.back-home-link{
  position:absolute; top:28px; right:32px;
  font-size:18px; font-weight:700;
}
.auth-card{ width:100%; max-width: 380px; }
.auth-card .eyebrow{
  font-size:12.5px; font-weight:600; color: var(--accent-dark);
  text-transform: uppercase; letter-spacing: 0.06em; margin-bottom:8px;
}
.auth-card h2{ font-size: 26px; margin: 0 0 6px; }
.auth-card p.sub{ color: var(--muted); font-size: 14px; margin: 0 0 26px; }

.field{ margin-bottom: 16px; }
.field label{
  display:block; font-size:13px; font-weight:600; color: var(--ink-soft);
  margin-bottom:6px;
}
.field input, .field select{
  width:100%; padding: 12px 14px; font-size:14.5px;
  border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.field input:focus, .field select:focus{
  border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); outline:none;
}
.password-field{ position:relative; }
.password-field input{ padding-right: 44px; }
.password-toggle{
  position:absolute; top:50%; right:6px; transform:translateY(-50%);
  width:32px; height:32px; display:flex; align-items:center; justify-content:center;
  background:none; border:none; cursor:pointer; font-size:16px; border-radius:8px;
  color: var(--ink-soft);
}
.password-toggle:hover{ background: var(--surface-sunken); }
.password-toggle .icon-eye-off{ display:none; }
.password-toggle.showing .icon-eye{ display:none; }
.password-toggle.showing .icon-eye-off{ display:block; }
.field-row{ display:flex; align-items:center; justify-content:space-between; margin: -4px 0 18px; }
.check{ display:flex; align-items:center; gap:8px; font-size:13px; color: var(--ink-soft); }
.link{ font-size:13px; color: var(--blue-dark); font-weight:600; text-decoration:none; }
a.link:link, a.link:visited{ text-decoration: none; }
.link:hover{ text-decoration: underline; }

.btn{
  appearance:none; border:none; cursor:pointer;
  font-family: var(--font-body); font-weight: 700; font-size: 14.5px;
  border-radius: 999px; /* fully-rounded, matching the homepage's pill buttons —
                            deliberately not var(--radius-sm), which is also used
                            by inputs and other non-pill elements */
  padding: 13px 20px;
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  text-decoration: none;
  transition: transform .12s var(--ease), box-shadow .12s var(--ease), background .12s var(--ease);
}
a.btn:link, a.btn:visited{ text-decoration: none; }
.btn:active{ transform: translateY(1px); }
.btn:disabled{ cursor: not-allowed; }
.btn-block{ width:100%; }
.btn-primary{ background: var(--accent); color:#fff; box-shadow: 0 6px 16px rgba(28,132,71,0.35); }
.btn-primary:hover{ background: var(--accent-dark); }
.btn-primary:disabled{ background: var(--accent-soft); color: var(--accent-dark); box-shadow: none; }
.btn-primary:disabled:hover{ background: var(--accent-soft); }
.btn-indigo{ background: var(--indigo); color:#fff; box-shadow: 0 6px 16px rgba(30,127,207,0.35); }
.btn-indigo:hover{ background: var(--indigo-dark); }
.btn-ghost{ background: transparent; color: var(--ink-soft); border: 1.5px solid var(--border); }
.btn-ghost:hover{ background: var(--surface-sunken); }
.btn-sm{ padding: 8px 14px; font-size: 13px; }

.error-banner{
  display:none;
  background: var(--danger-soft); color: var(--danger);
  border: 1px solid rgba(226,75,57,0.25);
  padding: 11px 14px; border-radius: var(--radius-sm);
  font-size: 13.5px; font-weight: 600; margin-bottom: 16px;
}
.error-banner.show{ display:block; }

.hint-box{
  margin-top: 18px; padding: 12px 14px;
  background: var(--surface-sunken); border-radius: var(--radius-sm);
  font-size: 12.5px; color: var(--ink-soft); display:flex; gap:8px; align-items:flex-start;
}
.hint-box > span:first-child{ flex-shrink:0; }
.hint-box > span:last-child{ flex:1; min-width:0; }
.hint-box code{ background: var(--surface); padding:1px 6px; border-radius:5px; border:1px solid var(--border); }

.role-switch{
  margin-top: 22px; text-align:center; font-size: 13px; color: var(--muted);
}

/* =========================================================
   App shell (dashboards)
   ========================================================= */
.app-shell{
  display:grid;
  grid-template-columns: 248px 1fr;
  min-height: 100vh;
}
.mobile-topbar{
  display:none;
  align-items:center; justify-content:space-between;
  padding: 14px 18px;
  background: var(--indigo-dark);
  color:#fff;
  position: sticky; top:0; z-index: 38;
  /* .app-shell is a grid, and grid items stretch to fill their row's
     height by default — without this, the topbar's box can end up much
     taller than its own content, leaving the hamburger vertically
     centered in that empty space instead of pinned to a slim strip. */
  align-self: start;
  height: auto;
}
.mobile-topbar .brand{ color:#fff; }
.mobile-topbar .brand small{ color: rgba(255,255,255,0.55); }
.hamburger-btn{
  background: rgba(255,255,255,0.1); border:none; color:#fff;
  cursor:pointer; display:flex; align-items:center; justify-content:center;
  padding: 8px 12px; border-radius: 10px;
}
.hamburger-btn:hover{ background: rgba(255,255,255,0.18); }
.sidebar-overlay{
  display:none;
  position:fixed; inset:0; background: rgba(20,19,43,0.5);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  z-index: 39;
}
.sidebar-overlay.show{ display:block; }

/* Tables that might not fit narrow screens scroll horizontally
   instead of squeezing columns unreadably thin. */
.table-scroll{ overflow-x: auto; -webkit-overflow-scrolling: touch; }
@media (max-width: 700px){
  .table-scroll table{ min-width: 640px; }
}
.scroll-hint{ display:none; }
@media (max-width: 880px){
  /* Wide student tables scroll sideways on phones/tablets. Cells stay on
     one line (only the free-text Remarks column wraps, at a readable
     width) — otherwise the browser squeezes every column and the rows
     grow enormously tall. */
  .table-scroll-y th, .table-scroll-y td{ white-space: nowrap; }
  .table-scroll-y td:last-child{ white-space: normal; min-width: 240px; }

  /* Edge shadows appear on whichever side still has hidden columns, so the
     sideways scroll is discoverable (touch screens hide scrollbars). The
     "local" layers cover the shadows once the scroll reaches that edge. */
  .table-scroll{
    background:
      linear-gradient(to right, var(--surface) 30%, rgba(255,255,255,0)) 0 0 / 40px 100% no-repeat local,
      linear-gradient(to left, var(--surface) 30%, rgba(255,255,255,0)) 100% 0 / 40px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%, rgba(20,19,43,0.22), rgba(20,19,43,0)) 0 0 / 14px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%, rgba(20,19,43,0.22), rgba(20,19,43,0)) 100% 0 / 14px 100% no-repeat scroll;
  }
  .scroll-hint{ display:block; margin: 0 0 8px; font-size: 11.5px; color: var(--muted); }
}
.sidebar{
  background: var(--indigo-dark);
  color: #fff;
  padding: 22px 16px;
  display:flex; flex-direction:column;
}
.sidebar .brand{ color:#fff; padding: 6px 10px 26px; }
.sidebar .brand small{ color: rgba(255,255,255,0.55); }
.side-user{
  display:flex; align-items:center; gap:10px;
  padding: 10px; border-radius: var(--radius-sm);
  background: rgba(255,255,255,0.06); margin-bottom: 20px;
  position: relative;
}
.avatar{
  width:36px; height:36px; border-radius:50%;
  background: var(--accent);
  display:flex; align-items:center; justify-content:center;
  font-weight:700; font-size:13px; color:#fff; flex:none;
}
.side-user .name{ font-size:13.5px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.side-user .role{ font-size:11.5px; color: rgba(255,255,255,0.55); }

/* Clicking the name/role area (not the avatar itself, and not the theme
   toggle button — see ui.js's wireProfileDropdown/wireThemeToggle, both
   stopPropagation so they don't fight each other) opens the dropdown with
   Back to Homepage / Logout, which used to be two standalone buttons
   pinned to the bottom of the sidebar. */
.side-user-body{ display:flex; align-items:center; gap:8px; flex:1; min-width:0; cursor:pointer; }
.side-user-info{ flex:1; min-width:0; }
.theme-toggle-btn{
  flex:none; width:26px; height:26px; border-radius:50%;
  background: rgba(255,255,255,0.1); border:none; color:#fff;
  display:flex; align-items:center; justify-content:center; cursor:pointer;
  transition: background .12s var(--ease);
}
.theme-toggle-btn:hover{ background: rgba(255,255,255,0.22); }
.side-user-body #editNicknameBtn{ margin-left: 6px; margin-right: 12px; }
.side-user-chevron{ flex:none; opacity:0.55; display:flex; transition: transform .15s var(--ease); }
.side-user.open .side-user-chevron{ transform: rotate(180deg); }

.profile-dropdown{
  display:none;
  position:absolute; top: calc(100% + 6px); left:0; right:0; z-index: 45;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg); padding: 6px;
}
.profile-dropdown.show{ display:block; }
.profile-dropdown-item{
  display:flex; align-items:center; gap:10px; width:100%;
  padding: 10px 12px; border-radius: 9px; border:none; background:none;
  font-family: var(--font-body); font-size:13.5px; font-weight:600; color: var(--ink-soft);
  text-decoration:none; cursor:pointer; text-align:left;
}
.profile-dropdown-item:hover{ background: var(--surface-sunken); color: var(--ink); }
.profile-dropdown-item--danger{ color: var(--danger); }
.profile-dropdown-item--danger:hover{ background: var(--danger-soft); color: var(--danger); }

.nav-group{ margin-bottom: 18px; }
.nav-label{
  font-size: 11px; text-transform:uppercase; letter-spacing:0.06em;
  color: rgba(255,255,255,0.4); padding: 6px 10px; font-weight:700;
}
.nav-link{
  display:flex; align-items:center; gap:10px;
  padding: 10px 12px; border-radius: var(--radius-sm);
  font-size: 14px; font-weight:600; color: rgba(255,255,255,0.8);
  text-decoration:none; margin-bottom:2px; white-space:nowrap; overflow:hidden;
  transition: background .12s var(--ease), font-size .15s ease, gap .15s ease;
}
.nav-link .ic{ width:18px; text-align:center; opacity:0.85; }
.nav-link:hover{ background: rgba(255,255,255,0.08); color:#fff; }
/* Each nav item's active state is a translucent tint of the accent color
   already associated with that section elsewhere in the dashboard (see
   each page's own stat-card icon-tint) — recessed/blended like the STI
   reference rather than a loud solid block, but still clearly tied to
   that specific page's own color instead of one neutral tone for all of
   them. --indigo is deliberately skipped here even though it's the
   Sections stat card's tint elsewhere — it's nearly the same shade as
   the sidebar's own --indigo-dark background, so it would be invisible
   as a highlight here; --blue stands in for it. */
.nav-link.active{ color:#fff; }
.nav-link.active .ic{ opacity:1; }

.nav-link[href="/portal/admin-dashboard.html"].active{ background: rgba(21,161,148,0.32); }
.nav-link[href="/portal/admin-sections.html"].active{ background: rgba(30,127,207,0.32); }
.nav-link[href="/portal/admin-content.html"].active{ background: rgba(201,151,31,0.32); }
.nav-link[href="/portal/admin-users.html"].active{ background: rgba(28,132,71,0.32); }

.nav-link[href="/portal/teacher-dashboard.html"].active{ background: rgba(30,127,207,0.32); }
.nav-link[href="/portal/teacher-students.html"].active{ background: rgba(28,132,71,0.32); }
.nav-link[href="/portal/teacher-notifications.html"].active{ background: rgba(239,68,68,0.32); }

/* min-width:0 overrides the browser default (min-width:auto) that grid/flex
   items get automatically — without it, a wide child (like the 640px-min
   table below) refuses to let its ancestor shrink past that content size,
   which drags .main, then .app-shell, then the whole page wide instead of
   the overflow staying contained to .table-scroll where it belongs. This is
   the same pattern SAP Fiori/AWS Console use to keep one wide data table
   from breaking a responsive shell around it. */
/* width:100% is needed alongside margin-inline:auto — a grid item with
   auto margins stops stretching and would shrink to its content. The
   1760px cap fills a 1080p screen and keeps content centred, not
   stranded on the left, on ultra-wide monitors. */
.main{ padding: 28px 34px 60px; width: 100%; max-width: 1760px; margin-inline: auto; min-width: 0; }
.topbar{
  display:flex; align-items:flex-start; justify-content:space-between;
  margin-bottom: 24px; gap: 20px; flex-wrap: wrap;
}
.topbar h1{ font-size: 24px; margin:0 0 4px; }
.topbar .sub{ color: var(--muted); font-size: 13.5px; }
.topbar-actions{ display:flex; gap:10px; align-items:center; }
.search-box{
  display:flex; align-items:center; gap:8px;
  background: var(--surface); border:1.5px solid var(--border);
  border-radius: 100px; padding: 8px 14px; font-size:13.5px; color: var(--muted);
  min-width: 220px;
}
.search-box input{ border:none; outline:none; background:transparent; font-size:13.5px; width:100%; }

.grid{ display:grid; gap: 18px; }
.grid-stats{ grid-template-columns: repeat(4, 1fr); }
.grid-2-1{ grid-template-columns: 2fr 1fr; align-items:start; }
.grid-2{ grid-template-columns: 1fr 1fr; }
@media (max-width: 1000px){
  .grid-stats{ grid-template-columns: repeat(2,1fr); }
  .grid-2-1{ grid-template-columns: 1fr; }
  .grid-2{ grid-template-columns: 1fr; }
}

.card{
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 20px;
  box-shadow: var(--shadow-sm);
  min-width: 0; /* let this shrink inside any grid/flex parent — see .main note above */
}
.card-head{
  display:flex; align-items:center; justify-content:space-between;
  margin-bottom: 14px;
}
.card-head h3{ font-size: 15px; margin:0; display:flex; align-items:center; gap:8px; }
.card-head .see-all{ font-size: 12.5px; font-weight:700; color: var(--accent-dark); text-decoration:none; }

.stat-card .stat-icon{
  width:38px; height:38px; border-radius: 10px;
  display:flex; align-items:center; justify-content:center;
  font-size:16px; margin-bottom: 14px;
}
.stat-card .stat-value{ font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 28px; font-weight:700; line-height:1; }
.stat-card .stat-label{ font-size: 12.5px; color: var(--muted); margin-top:6px; }

.stat-card--link{ display:block; color:inherit; text-decoration:none; cursor:pointer; transition: transform .15s var(--ease), box-shadow .15s var(--ease); }
.stat-card--link:hover{ transform: translateY(-2px); box-shadow: var(--shadow-md); }

.donut-wrap{ display:flex; align-items:center; gap:22px; flex-wrap:wrap; }
.donut{ position:relative; width:150px; height:150px; flex:none; }
.donut svg{ width:100%; height:100%; display:block; }
.donut-center{ position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; }
.donut-center b{ font-family: var(--font-display); font-size:22px; line-height:1; }
.donut-center span{ font-size:11px; color:var(--muted); margin-top:4px; }
.donut-legend{ display:grid; gap:10px; flex:1; min-width:180px; margin:0; padding:0; list-style:none; }
.donut-legend li{ display:flex; align-items:center; gap:10px; font-size:13.5px; }
.donut-legend .swatch{ width:10px; height:10px; border-radius:50%; flex:none; }
.donut-legend .count{ margin-left:auto; font-weight:800; font-variant-numeric: tabular-nums; }
.donut-legend .pct{ color:var(--muted); font-size:12px; width:42px; text-align:right; font-variant-numeric: tabular-nums; }
.stat-card .stat-delta{ font-size:12px; font-weight:700; margin-top:8px; display:inline-flex; align-items:center; gap:4px; }
.delta-up{ color: var(--success); }
.delta-down{ color: var(--danger); }

.icon-tint-coral{ background: var(--accent-soft); color: var(--accent-dark); }
.icon-tint-teal{ background: var(--teal-soft); color: var(--teal); }
.icon-tint-amber{ background: var(--amber-soft); color: var(--amber); }
.icon-tint-indigo{ background: var(--indigo-soft); color: var(--indigo); }

table{ width:100%; border-collapse: collapse; font-size: 13.5px; }
thead th{
  text-align:left; font-size: 11.5px; text-transform:uppercase; letter-spacing:0.04em;
  color: var(--muted); font-weight:700; padding: 0 10px 10px; border-bottom: 1.5px solid var(--border);
}
tbody td{ padding: 12px 10px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr:last-child td{ border-bottom:none; }
tbody tr{ transition: background .12s var(--ease); }
tbody tr.clickable{ cursor:pointer; }
tbody tr.clickable:hover{ background: var(--surface-sunken); }

.cell-name{ display:flex; align-items:center; gap:10px; font-weight:600; }
.cell-avatar{
  width:30px; height:30px; border-radius:50%; flex:none;
  display:flex; align-items:center; justify-content:center;
  font-size:11.5px; font-weight:700; color:#fff;
}

.badge{
  display:inline-flex; align-items:center; gap:5px;
  padding: 4px 10px; border-radius: 100px;
  font-size: 11.5px; font-weight:700;
}
.badge-active{ background: var(--success-soft); color: var(--success); }
.badge-done{ background: var(--indigo-soft); color: var(--indigo); }
.badge-idle{ background: var(--surface-sunken); color: var(--muted); }
.badge::before{ content:""; width:6px; height:6px; border-radius:50%; background: currentColor; }

.score-pill{
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight:700; font-size: 12.5px;
  padding: 3px 9px; border-radius: 7px;
}
.score-high{ background: var(--success-soft); color: var(--success); }
.score-mid{ background: var(--amber-soft); color: var(--amber); }
.score-low{ background: var(--danger-soft); color: var(--danger); }

.bar-row{ margin-bottom: 12px; }
.bar-row:last-child{ margin-bottom:0; }
.bar-row .bar-label{
  display:flex; justify-content:space-between; font-size:12.5px; font-weight:600;
  margin-bottom: 6px; color: var(--ink-soft);
}
.bar-track{ height: 8px; border-radius: 100px; background: var(--surface-sunken); overflow:hidden; }
.bar-fill{ height:100%; border-radius:100px; background: var(--accent); transition: width .5s var(--ease); }

/* Last-7-days accuracy chart on a student's detail popup — one column
   per day, bar height = that day's real pronunciation accuracy, status-
   colored (same thresholds as .score-high/mid/low). A day with no
   practice renders as a dashed "empty" mark instead of a 0%-height bar,
   so "didn't practice" never reads as "practiced and failed." */
.daily-chart{ display:flex; align-items:flex-end; gap:6px; margin:10px 0 4px; }
.daily-chart-col{ flex:1; display:flex; flex-direction:column; align-items:center; min-width:0; }
.daily-chart-value{ font-size:10.5px; font-weight:700; color:var(--ink-soft); margin-bottom:4px; height:13px; white-space:nowrap; }
.daily-chart-track{ width:100%; max-width:26px; height:60px; display:flex; align-items:flex-end; }
.daily-chart-bar{ width:100%; border-radius:4px 4px 0 0; min-height:4px; }
.daily-chart-bar--empty{ width:100%; height:4px; border-radius:4px; border:1.5px dashed var(--border); box-sizing:border-box; }
.daily-chart-day{ font-size:10px; color:var(--muted); font-weight:600; margin-top:6px; }

.weekly-summary{
  background: var(--surface-sunken); border-radius: var(--radius-sm);
  padding: 12px 14px; font-size: 12.5px; line-height:1.5; color: var(--ink-soft);
  margin: 4px 0 14px;
}

.leader-row{
  display:flex; align-items:center; gap:10px; padding: 9px 0;
  border-bottom: 1px solid var(--border); font-size: 13px;
}
.leader-row:last-child{ border-bottom:none; }

.alert-row{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding: 12px 10px; margin: 0 -10px; border-bottom: 1px solid var(--border);
  border-radius: var(--radius-sm); cursor:pointer; transition: background .12s var(--ease);
}
.alert-row:hover{ background: var(--surface-sunken); }
.alert-row:last-child{ border-bottom:none; }
.alert-row .meta{ font-size:11.5px; color:var(--muted); font-weight:500; margin-top:2px; }
.leader-rank{
  width:22px; height:22px; border-radius:6px; background: var(--surface-sunken);
  display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:800; color: var(--muted); flex:none;
}
.leader-row .lname{ flex:1; font-weight:600; }
.leader-row .lscore{ font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight:700; color: var(--success); font-size:12.5px; }

.note-card{
  background: linear-gradient(155deg, var(--indigo-dark), var(--indigo));
  color:#fff; border-radius: var(--radius-md); padding: 18px;
}
.note-card h4{ font-size:13.5px; margin:0 0 10px; display:flex; align-items:center; gap:7px; }
.note-card p{ font-size: 12.5px; color: rgba(255,255,255,0.78); margin:0; line-height:1.55; }
.note-card textarea{
  width:100%; resize:vertical; min-height:74px;
  background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.25);
  border-radius: 10px; padding: 10px 12px; margin-bottom:10px;
  color:#fff; font-family: var(--font-body); font-size: 12.5px; line-height:1.55;
}
.note-card textarea::placeholder{ color: rgba(255,255,255,0.55); }
.note-card textarea:focus{ outline:none; border-color: rgba(255,255,255,0.55); }

.section-card{
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 16px 18px; margin-bottom: 12px; background: var(--surface);
  transition: box-shadow .15s var(--ease), border-color .15s var(--ease);
}
.section-card:hover{ box-shadow: var(--shadow-md); border-color: transparent; }
.section-top{ display:flex; align-items:center; justify-content:space-between; }
.section-name{ font-weight:700; font-size:15px; display:flex; align-items:center; gap:8px; }
.section-meta{ font-size:12px; color:var(--muted); margin-top:2px; }
.section-stats{ display:flex; flex-wrap:wrap; gap:14px 22px; margin-top:12px; }
.section-stats .s{ text-align:left; }
.section-stats .s b{ display:block; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size:16px; }
.section-stats .s span{ font-size:11px; color:var(--muted); }
@media (max-width: 420px){
  .section-stats{ gap:12px 20px; }
  .section-stats .s{ min-width: calc(50% - 10px); }
}

.empty-state{
  text-align:center; padding: 46px 20px; color: var(--muted);
}
.empty-state .ic{ font-size: 30px; margin-bottom: 10px; }
.empty-state h4{ color: var(--ink); font-size:15px; margin:0 0 4px; }
.empty-state p{ font-size:13px; margin:0; }

.pagination-bar{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border);
}
.pagination-status{ font-size:12.5px; color: var(--muted); font-weight:600; }

.otp-row{ display:flex; gap:8px; justify-content:center; }
.otp-box{
  width:44px; height:52px; padding:0; text-align:center;
  font-size:22px; font-weight:700; font-family: var(--font-body);
  border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.otp-box:focus{
  border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); outline:none;
}
@media (max-width: 420px){
  .otp-box{ width:38px; height:46px; font-size:19px; }
  .otp-row{ gap:6px; }
}

.skeleton{
  background: linear-gradient(90deg, var(--surface-sunken) 25%, var(--skeleton-shine) 37%, var(--surface-sunken) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease infinite;
  border-radius: 8px;
}
@keyframes shimmer{ 0%{ background-position: 100% 50%; } 100%{ background-position: 0 50%; } }

.toast{
  position: fixed; bottom: 22px; right: 22px; left: 22px; margin-left: auto;
  max-width: calc(100vw - 44px); width: fit-content;
  background: var(--indigo-dark); color:#fff; padding: 13px 18px;
  border-radius: var(--radius-sm); font-size: 13.5px; font-weight:600;
  box-shadow: var(--shadow-lg);
  transform: translateY(20px); opacity:0; pointer-events:none;
  transition: all .25s var(--ease);
  z-index: 60;
}
.toast.show{ transform: translateY(0); opacity:1; }

/* Dashboard loading overlay — the same paw loader as the homepage (see ui.js). */
.page-loader{
  position: fixed; inset: 0; z-index: 200;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
  background: var(--bg);
  transition: opacity .4s var(--ease), visibility .4s var(--ease);
}
.page-loader.is-hidden{ opacity: 0; visibility: hidden; pointer-events: none; }
.page-loader__paw{ width: 120px; height: auto; transform-origin: center bottom; animation: paw-bounce .9s ease-in-out infinite; }
.page-loader__text{ margin: 0; font-family: var(--font-display); font-weight: 600; font-size: 17px; letter-spacing: .5px; color: var(--ink); }
body.is-loading{ overflow: hidden; }
@keyframes paw-bounce{
  0%, 100% { transform: translateY(0) scale(1, 1); }
  50%      { transform: translateY(-12px) scale(1.02, .98); }
}
@media (prefers-reduced-motion: reduce){ .page-loader__paw{ animation: none; } }

.modal-overlay{
  position:fixed; inset:0; background: rgba(20,19,43,0.45);
  display:none; align-items:center; justify-content:center; z-index: 50; padding:20px;
}
.modal-overlay.show{ display:flex; }
.modal{
  background: var(--surface); color: var(--ink);
  border-radius: var(--radius-md); padding: 24px;
  width:100%; max-width: 420px; box-shadow: var(--shadow-lg);
}
.modal h3{ margin:0 0 14px; font-size:17px; }
.modal-actions{ display:flex; gap:10px; margin-top: 18px; justify-content:flex-end; }

/* =========================================================
   Mobile app-shell collapse — deliberately the LAST rules in
   this file. Every dashboard page (admin + teacher) shares this
   stylesheet, and this exact "media query placed before the base
   rule it overrides" mistake already broke the auth pages once
   today — putting the override last removes any doubt about
   cascade order ever again, for every property, on every page.
   ========================================================= */
@media (max-width: 880px){
  .app-shell{ grid-template-columns: 1fr; }
  .mobile-topbar{ display:flex; }
  .sidebar{
    position: fixed; top: 78px; left: 16px; z-index: 40;
    width: 260px; max-width: calc(100vw - 32px);
    max-height: calc(100vh - 96px);
    border-radius: 20px;
    box-shadow: 0 20px 50px rgba(0,0,0,0.35);
    transform: translateX(-130%);
    transition: transform .25s var(--ease);
    overflow-y: auto;
  }
  .sidebar.open{ transform: translateX(0); }
  /* Tighter side padding on phones gives tables and cards more width. */
  .main{ padding: 20px 16px 48px; }

  /* Profile dropdown on phones: the desktop row height (~34px) is under the
     44px minimum touch target, and the rows sit close enough that a tap
     meant for one often lands on the next. Taller rows, a divider above
     Logout so the two actions read as separate, and a press-state so a tap
     visibly registers before the menu closes. */
  .profile-dropdown{ padding: 6px; }
  .profile-dropdown-item{ min-height: 48px; padding: 12px 14px; font-size: 15px; gap: 12px; border-radius: 10px; }
  .profile-dropdown-item:active{ background: var(--surface-sunken); }
  .profile-dropdown-item--danger{ margin-top: 6px; border-top: 1px solid var(--border); border-radius: 0 0 10px 10px; }
  .profile-dropdown-item--danger:active{ background: var(--danger-soft); }

  /* The small icon buttons beside the name (edit nickname, dark-mode toggle)
     are only 26px visually — extend their hit area 8px in every direction
     with a transparent pseudo-element, so a slightly off tap still lands on
     the intended button instead of the profile body. */
  .side-user-body .theme-toggle-btn,
  .side-user-body #editNicknameBtn{ position: relative; }
  .side-user-body .theme-toggle-btn::after,
  .side-user-body #editNicknameBtn::after{ content: ""; position: absolute; inset: -8px; }
}

/* =========================================================
   Desktop sidebar auto-collapse (>=881px, the exact complement of
   the 880px mobile breakpoint above so the two never overlap). The
   sidebar rests as a slim icon rail and expands to show labels
   while the cursor is over it — ui.js toggles .sidebar-expanded on
   .app-shell via mouseenter/mouseleave on .sidebar — collapsing
   again once the cursor leaves, VS Code auto-hide style. Every
   rule here targets ":not(.sidebar-expanded)" and relies on higher
   selector specificity (not source order) to win over the base
   .app-shell/.nav-link/.btn rules above, so this block's position
   in the file doesn't matter the way the mobile block's does.
   ========================================================= */
@media (min-width: 881px){
  .app-shell{ transition: grid-template-columns .22s var(--ease); }
  .app-shell:not(.sidebar-expanded){ grid-template-columns: 76px 1fr; }

  .app-shell:not(.sidebar-expanded) .sidebar .brand{ justify-content: center; }
  .app-shell:not(.sidebar-expanded) .sidebar .brand > div{ display: none; }

  .app-shell:not(.sidebar-expanded) .sidebar .nav-label{ display: none; }
  .app-shell:not(.sidebar-expanded) .sidebar .nav-link{
    font-size: 0; gap: 0; justify-content: center; padding-left: 0; padding-right: 0;
  }

  /* Profile block floats in the top-right corner of the viewport instead
     of living inside the sidebar — position:fixed here positions relative
     to the viewport (nothing above it sets transform/filter/etc., so no
     ancestor creates a containing block that would hijack that), and the
     dropdown panel below it still anchors correctly since :fixed is a
     positioned element too. Given an explicit opaque background here
     (instead of the translucent white tint that worked over the sidebar's
     own dark navy) so it stays readable floating over any page content in
     either light or dark mode. .main's extra top padding below reserves
     the room so no page content ever sits underneath it. */
  .side-user{
    position: fixed; top: 20px; right: 28px; z-index: 46;
    margin-bottom: 0;
    background: var(--indigo-dark);
    box-shadow: var(--shadow-lg);
  }
  .main{ padding-top: 96px; }

  /* Pin the sidebar to the viewport so its nav links stay reachable
     however far the page is scrolled. Without this the grid stretches
     the sidebar to the full page height. The scrollbar is hidden, but
     the sidebar can still scroll on a very short window so nothing is
     ever unreachable. */
  .sidebar{
    position: sticky; top: 0; align-self: start;
    height: 100vh; overflow-y: auto; scrollbar-width: none;
  }
  .sidebar::-webkit-scrollbar{ display: none; }

  /* A long table or list scrolls inside its own card instead of
     stretching the whole page; a table's header row stays put.
     --scroll-offset is the space the page needs above and below the
     scrolling area (topbar, card chrome, padding) — override it inline
     where the content starts higher or lower on the page. max() keeps
     a usable height on a very short window. */
  .table-scroll-y, .list-scroll-y{
    max-height: max(320px, calc(100vh - var(--scroll-offset, 380px)));
    overflow-y: auto;
  }
  .table-scroll-y thead th{ position: sticky; top: 0; z-index: 1; background: var(--surface); box-shadow: 0 1.5px 0 var(--border); }
}