/**
 * "My Classes" dashboard v2 — 2026-08 redesign.
 *
 * All classes are prefixed with `dv2-` so nothing here can leak into
 * (or be leaked into from) the shared app.css tokens. Colors reuse the
 * existing project variables where possible; the 3 accent colours from
 * the mockup (deep-navy, sunshine-yellow, forest-green, coral) are
 * mapped locally here so we don't touch the global palette.
 */

/* Local design tokens — scoped, do NOT bleed to the rest of the app.
 *
 * `.dv2-popover` is listed here too because the alert-bell popovers are
 * rendered through a portal into <body>, i.e. OUTSIDE `.dashboard-v2`.
 * Without its own copy of the tokens every `var(--dv2-*)` inside the
 * popover resolves to nothing — which silently turned the "Open"
 * buttons into white-on-white ghosts.
 *
 * `.dv2-bells` is listed for the same reason: the School dashboard
 * reuses the bells on a page that is NOT `.dashboard-v2`, so the red
 * count badge (`background: var(--dv2-red)`) came out transparent and
 * its white digit was invisible. Amber badges hid the bug because
 * their colour is a literal, not a token. */
.dashboard-v2,
.dv2-popover,
.dv2-bells {
  --dv2-navy: var(--color-brand-indigo-500, #0f0069);
  --dv2-yellow: #fdd504;
  --dv2-green: #0e503f;
  --dv2-coral: #e0562f;
  --dv2-gray: #8b929a;
  --dv2-gray-bg: #f1f2ef;
  --dv2-line: #e2e4e0;
  --dv2-red: #c0392b;
  --dv2-red-bg: #fde7e5;
  --dv2-amber: #8a6d00;
  --dv2-amber-bg: #fdf3d3;
  --dv2-blue: #1368ce;
  --dv2-blue-bg: #e4eefc;
  --dv2-green-bg: #e6efec;
}

/* ===== Banner-driven theme =====
 *
 * When the teacher picks a banner photo, `lib/bannerPalette` measures
 * the photo's dominant colour and writes a matching set of `--dv2-t-*`
 * ("t" for theme) properties onto <body>, plus the `.dv2-themed` class.
 *
 * The block below is the only thing that connects the two: it re-points
 * the shipped tokens at the measured ones. Two consequences we want:
 *
 *   - Nothing changes for a teacher on the default banner. The class is
 *     absent, so the defaults above stand untouched.
 *   - Every existing `var(--dv2-navy)` in this file (12 of them) picks
 *     up the new colour for free — no per-rule duplication.
 *
 * Specificity is (0,2,0) here versus (0,1,0) above, so this wins
 * regardless of source order. The `var(--x, fallback)` on each line is a
 * belt-and-braces guard: if JS ever set the class without the values,
 * we land back on the shipped palette rather than on `unset`.
 *
 * `.dv2-popover` is included because the alert-bell popovers are
 * portalled to <body>, outside `.dashboard-v2`.
 */
body.dv2-themed .dashboard-v2,
body.dv2-themed .dv2-popover {
  --dv2-navy: var(--dv2-t-navy, #0f0069);
  --dv2-green: var(--dv2-t-green, #0e503f);
  --dv2-yellow: var(--dv2-t-yellow, #fdd504);
  --dv2-line: var(--dv2-t-line, #e2e4e0);
  --dv2-gray: var(--dv2-t-gray, #8b929a);
  --dv2-gray-bg: var(--dv2-t-gray-bg, #f1f2ef);
  --dv2-green-bg: var(--dv2-t-green-bg, #e6efec);
  /* The "today" column in the week calendar ships as a lavender wash,
     which is the one leftover that visibly clashes with a warm theme. */
  --dv2-today-bg: var(--dv2-t-today-bg, #f7f6ff);

  /* Stat-card foregrounds. These are computed from the real WCAG
     contrast ratio against each card's new background, which is why
     they cannot just be hard-coded to white/black here. */
  --dv2-stat-green-text: var(--dv2-t-green-text, #fff);
  --dv2-stat-navy-text: var(--dv2-t-navy-text, #fff);
  --dv2-stat-yellow-text: var(--dv2-t-yellow-text, #3a2e00);
  --dv2-stat-light-text: var(--dv2-t-light-text, #14171a);
}

/* Tint the whole page behind the dashboard. `.app-content` is
   transparent, so `.app-main` is the surface that actually paints. */
body.dv2-themed .app-main {
  background: var(--dv2-t-page-bg, var(--color-neutral-50));
}

/* Layout of the dashboard column itself — deliberately NOT shared with
   the popover above, which lays itself out. */
.dashboard-v2 {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* ===== Alert bells row ===== */
.dv2-bells {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
}
/* When rendered inside the dark purple hero, dock top-right and
   switch the bells to a translucent-white variant so they read
   correctly on the gradient. Kept as a modifier so the default
   (light background) styling is untouched.
   NOTE: selector uses the double-class `.dv2-bells.dv2-bells--on-hero`
   to bump specificity (0,2,0) so it wins against the later
   `.dv2-hero > *` rule which sets `position: relative` on all
   hero children (needed for z-index over the dark-photo scrim).
   Without the extra class, source-order caused the bells to fall
   back into the normal document flow and land in the top-LEFT of
   the hero, overlapping the "MY CLASSES" tag. */
.dv2-bells.dv2-bells--on-hero {
  position: absolute;
  top: 16px;
  right: 20px;
  padding: 0;
  z-index: 2;
}
/* Base = solid white so the bells always read as "clean, active
   controls". The count badge (colored red/amber) is the only signal
   that there's something new — no count = no badge = no alarm. */
.dv2-bells--on-hero .dv2-bell {
  border-color: #fff;
  background: #fff;
  color: var(--dv2-navy);
}
.dv2-bells--on-hero .dv2-bell:hover {
  background: #f4f1ff;
}
.dv2-bells--on-hero .dv2-bell.has-alert {
  border-color: #fca5a5;
  background: #fff4f3;
}
.dv2-bells--on-hero .dv2-bell.has-alert.amber {
  border-color: #fcd34d;
  background: #fff8e5;
}
/* The count badge on hero needs a dark border so it pops against
   both the badge colour AND the white bell tile. */
.dv2-bells--on-hero .dv2-bell-count {
  border-color: #1e1b70;
}
.dv2-bell {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  border: 1.5px solid var(--dv2-line);
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.dv2-bell:hover:not(:disabled) {
  background: var(--dv2-gray-bg);
}
.dv2-bell.has-alert {
  border-color: #f3b8b0;
  background: var(--dv2-red-bg);
}
.dv2-bell.has-alert.amber {
  border-color: #e5cf7b;
  background: var(--dv2-amber-bg);
}
.dv2-bell:disabled {
  opacity: 0.5;
  cursor: default;
}
.dv2-bell-count {
  position: absolute;
  top: -6px;
  right: -6px;
  background: var(--dv2-red);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid #faf9f6;
}
.dv2-bell-count.amber {
  background: #c98a00;
}

/* ===== Popover (opens under a bell) ===== */
.dv2-popover-wrap {
  position: relative;
}
/* Rendered through a React portal into <body> (see AlertBells.tsx), so
   it is `fixed` and gets its `top` / `right` / `max-height` as inline
   styles measured from the bell it belongs to. Living at body level is
   what guarantees no page element can clip it or paint over it. */
.dv2-popover {
  position: fixed;
  width: min(380px, calc(100vw - 24px));
  background: #fff;
  border: 1px solid var(--dv2-line);
  border-radius: 14px;
  /* Deliberately heavy: the list floats over dense dashboard content
     (KPI cards, the week calendar), so it needs a clear lift-off
     shadow to never look like it merges with what is behind it. */
  box-shadow:
    0 24px 56px rgba(15, 0, 105, 0.3),
    0 4px 12px rgba(15, 0, 105, 0.14);
  padding: 6px;
  /* Above every dashboard layer, below the help FAB (9000) and the
     VerifyEmail blocker (100000). */
  z-index: 2000;
  /* A teacher can easily have a dozen overdue students. `max-height` is
     also set inline to the exact space left under the bell; this is the
     fallback for the very first frame. */
  max-height: 70vh;
  overflow-y: auto;
  animation: dv2-popover-in 0.14s ease-out;
}
.dv2-popover-head {
  font-size: 11px;
  font-weight: 700;
  color: var(--dv2-gray);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 10px 12px 6px;
}
.dv2-popover-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px;
  border-radius: 10px;
}
.dv2-popover-item:hover {
  background: var(--dv2-gray-bg);
}
.dv2-popover-icon {
  width: 32px;
  height: 32px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  flex-shrink: 0;
}
.dv2-popover-icon.red { background: var(--dv2-red-bg); color: var(--dv2-red); }
.dv2-popover-icon.amber { background: var(--dv2-amber-bg); color: var(--dv2-amber); }
.dv2-popover-text {
  flex: 1;
  min-width: 0;
}
.dv2-popover-title {
  font-size: 13px;
  font-weight: 600;
  color: #14171a;
}
.dv2-popover-meta {
  font-size: 11px;
  color: var(--dv2-gray);
  margin-top: 1px;
}
.dv2-popover-action {
  font-size: 11px;
  font-weight: 700;
  padding: 7px 12px;
  border-radius: 14px;
  border: none;
  background: var(--dv2-navy);
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
}
.dv2-popover-action:hover {
  filter: brightness(1.08);
}
.dv2-popover-empty {
  padding: 14px 16px;
  font-size: 12px;
  color: var(--dv2-gray);
}

/* ===== Hero banner ===== */
.dv2-hero {
  /* position:relative so the alert bells (dv2-bells--on-hero) AND
     the BannerCustomizer paintbrush trigger can dock to the top-
     right / bottom-right corners via absolute positioning. */
  position: relative;
  background: linear-gradient(120deg, var(--dv2-navy), #241493);
  border-radius: 20px;
  padding: 28px 32px;
  color: #fff;
  /* NO `overflow: hidden` here, on purpose. The alert-bell popovers
     and the setup-checklist popover are absolutely positioned INSIDE
     this card and are taller than it, so clipping the hero silently
     chopped their lists off (badge said "5 overdue payments" but only
     the first row was visible). A background-image is already clipped
     to the rounded border box by default, so nothing here needed the
     clipping. */
}

/* ---- Photo-backed hero variant ------------------------------------
   The `--photo-bg` modifier is applied whenever the teacher picked a
   preset image or uploaded a custom photo. Inline `heroStyle` supplies
   the actual `background-image` + `background-position`.

   Readability is handled by a semi-opaque "glass" card behind the copy
   (`.dv2-hero-text`), NOT by a full-card scrim + drop-shadows. Same
   conclusion the School dashboard reached (see the long note above
   `.dash-identity-text` in ui-system.css): on a mid-tone photo a
   blurred shadow bleeds around each letter and the eye reads the text
   as grey. A card gives real contrast and leaves the artwork vivid. */

/* Lift every direct child above the background (bells, text card,
   customizer trigger). Doing it on `> *` avoids listing every class
   name and keeps future additions working automatically. */
.dv2-hero > * {
  position: relative;
  z-index: 1;
}

/* The glass card. Deliberately scoped to `--photo-bg`: on the shipped
   navy gradient the white copy is already high-contrast, and a card
   there would just be visual noise. */
.dv2-hero--photo-bg .dv2-hero-text {
  /* `fit-content` so the card hugs the words instead of spanning the
     whole banner and hiding the picture. The right-hand cap keeps it
     clear of the absolutely-positioned alert bells. */
  width: fit-content;
  max-width: min(100%, 560px);
  padding: 14px 18px;
  border-radius: 16px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  /* Small self-margin so the card doesn't touch the hero's own
     padding, which reads more intentional. */
  margin: 2px 0;
}
/* Dark artwork → dark glass, white copy. */
.dv2-hero--dark-bg .dv2-hero-text {
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Light artwork → light glass, dark copy. The inline
   `heroStyle.color` (the preset's textColor) already handles the
   letters; we only retune the translucent chips that were designed
   for the navy gradient and would vanish on white. */
.dv2-hero--photo-bg:not(.dv2-hero--dark-bg) .dv2-hero-text {
  background: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(0, 0, 0, 0.05);
}
.dv2-hero--photo-bg:not(.dv2-hero--dark-bg) .dv2-hero-tag {
  background: rgba(0, 0, 0, 0.12);
  color: inherit;
}
.dv2-hero--photo-bg:not(.dv2-hero--dark-bg) .dv2-hero-sub {
  opacity: 0.9;
}
/* The "Finish setup" strip ships white-on-translucent-white, which is
   invisible on the light glass card. Flip it to dark-on-translucent-
   black there. */
.dv2-hero--photo-bg:not(.dv2-hero--dark-bg) .dv2-hero-strip {
  background: rgba(0, 0, 0, 0.06);
  border-color: rgba(0, 0, 0, 0.12);
  color: inherit;
}
.dv2-hero--photo-bg:not(.dv2-hero--dark-bg) .dv2-hero-strip:hover {
  background: rgba(0, 0, 0, 0.1);
  border-color: rgba(0, 0, 0, 0.2);
}
.dv2-hero--photo-bg:not(.dv2-hero--dark-bg) .dv2-hero-strip-caption {
  color: inherit;
}
.dv2-hero--photo-bg:not(.dv2-hero--dark-bg) .dv2-hero-strip-seg {
  background: rgba(0, 0, 0, 0.16);
}
.dv2-hero-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255, 255, 255, 0.14);
  font-size: 11px;
  font-weight: 700;
  padding: 5px 12px;
  border-radius: 20px;
  margin-bottom: 14px;
  letter-spacing: 0.04em;
}
.dv2-hero-tag-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #4ade80;
}
.dv2-hero-greeting {
  font-size: 26px;
  font-weight: 700;
  margin: 0;
}
.dv2-hero-sub {
  font-size: 14px;
  opacity: 0.78;
  margin: 6px 0 0;
}

/* ===== "Getting started" progress strip embedded in the hero =====
   Compact 3-segment bar with a right-aligned caption ("0/3 · Finish
   setup ›"). The whole thing is one button so the teacher can click
   anywhere on it to open the popover with the 3 steps.
   Sits in the same slot as .dv2-hero-sub, so the hero doesn't jump
   in height when the checklist finishes / gets dismissed. */
.dv2-hero-strip-wrap {
  position: relative;
  margin-top: 12px;
}
.dv2-hero-strip {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  /* The right padding RESERVES THE BANNER PAINTBRUSH'S CORNER.
     BannerCustomizer renders a 28x28 trigger absolutely positioned at
     bottom: 8px / right: 10px of the hero. This strip is `width: 100%`, so
     without a reserved lane its last child — the "0/3 · Finish setup ›"
     label — runs straight underneath that button. The owner photographed
     exactly that on her iPhone: "some buttons are one on another".

     Written as calc() ON PURPOSE, for two reasons. It names the three
     numbers it is made of (button + its inset + a breathing gap) instead of
     hiding them in a magic 46. And scripts/gen-mobile-compact.mjs refuses to
     scale any value containing calc() — which matters here, because the
     phone compaction would otherwise shrink this lane to 26px, narrower than
     the button it exists to clear, bringing the overlap back on the exact
     screen size where it was reported. */
  padding: 10px calc(28px + 10px + 8px) 10px 14px;
  color: #fff;
  cursor: pointer;
  text-align: left;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.dv2-hero-strip:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.22);
}
.dv2-hero-strip:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.6);
  outline-offset: 2px;
}
/* The 3-segment bar itself — flexes to fill the strip. */
.dv2-hero-strip-bar {
  flex: 1;
  display: flex;
  gap: 6px;
}
.dv2-hero-strip-seg {
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.20);
  transition: background 0.2s ease;
}
.dv2-hero-strip-seg--done {
  background: #4ade80; /* same green as the "MY CLASSES" tag dot */
}
.dv2-hero-strip-caption {
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  color: #fff;
}

/* ===== The popover that opens under the strip when clicked ===== */
.dv2-hero-popover-backdrop {
  /* Full-screen transparent catcher so any outside click closes the
     popover. Kept transparent so the rest of the dashboard stays
     visible — the popover itself is what gets the visual weight. */
  position: fixed;
  inset: 0;
  z-index: 40;
  background: transparent;
}
.dv2-hero-popover {
  position: absolute;
  z-index: 41;
  top: calc(100% + 10px);
  left: 0;
  right: 0;
  max-width: 640px;
  background: #ffffff;
  color: var(--color-text, #1a1a1a);
  border-radius: 16px;
  box-shadow: 0 20px 50px rgba(15, 0, 60, 0.28);
  overflow: hidden;
  animation: dv2-popover-in 0.14s ease-out;
}
@keyframes dv2-popover-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.dv2-hero-popover-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 18px 10px;
}
.dv2-hero-popover-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--color-brand-indigo-500, #5c3fe0);
  text-transform: uppercase;
}
.dv2-hero-popover-title {
  font-size: 16px;
  font-weight: 700;
  margin-top: 2px;
  color: #1a1a1a;
}
.dv2-hero-popover-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.dv2-hero-popover-chip {
  font-size: 12px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--color-brand-indigo-50, #EEEAFB);
  color: var(--color-brand-indigo-500, #5c3fe0);
  white-space: nowrap;
}
.dv2-hero-popover-close {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: none;
  background: transparent;
  color: var(--color-text-light, #666);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease;
}
.dv2-hero-popover-close:hover {
  background: var(--color-neutral-100, #f2f2f5);
}
.dv2-hero-popover-list {
  list-style: none;
  margin: 0;
  padding: 6px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.dv2-hero-popover-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--color-neutral-50, #fafafa);
  border: 1px solid var(--color-neutral-100, #eee);
}
.dv2-hero-popover-item--done {
  background: rgba(16, 185, 129, 0.06);
  border-color: rgba(16, 185, 129, 0.18);
}
.dv2-hero-popover-bullet {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 2px solid var(--color-neutral-300, #cbd0d8);
  color: var(--color-text-muted, #888);
  font-size: 13px;
  font-weight: 700;
}
.dv2-hero-popover-bullet--done {
  background: var(--color-brand-green-500, #10B981);
  border: none;
  color: #fff;
}
.dv2-hero-popover-item-body {
  flex: 1;
  min-width: 0;
}
.dv2-hero-popover-item-title {
  font-size: 13px;
  font-weight: 600;
  color: #1a1a1a;
}
.dv2-hero-popover-item--done .dv2-hero-popover-item-title {
  color: var(--color-text-muted, #888);
  text-decoration: line-through;
}
.dv2-hero-popover-item-hint {
  font-size: 12px;
  color: var(--color-text-light, #666);
  margin-top: 2px;
}
.dv2-hero-popover-cta {
  flex-shrink: 0;
  white-space: nowrap;
  background: var(--color-brand-indigo-500, #5c3fe0);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  padding: 8px 12px;
  border-radius: 8px;
  text-decoration: none;
  transition: filter 0.15s ease;
}
.dv2-hero-popover-cta:hover {
  filter: brightness(1.08);
}
.dv2-hero-popover-foot {
  padding: 8px 18px 14px;
  display: flex;
  justify-content: flex-end;
}
.dv2-hero-popover-dismiss {
  background: none;
  border: none;
  color: var(--color-text-muted, #888);
  font-size: 12px;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;
}
.dv2-hero-popover-dismiss:hover {
  color: var(--color-text, #1a1a1a);
  text-decoration: underline;
}

/* ===== Stats strip ===== */
.dv2-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}
@media (max-width: 760px) {
  .dv2-stats { grid-template-columns: 1fr 1fr; }
}
.dv2-stat {
  position: relative;
  border-radius: 18px;
  padding: 20px;
  overflow: hidden;
  text-decoration: none;
  transition: transform 0.15s ease;
}
.dv2-stat:hover { transform: translateY(-2px); }
/* Each card's text colour goes through a variable so the banner theme
   can swap in a contrast-checked value. Without the theme the fallback
   is the original hard-coded colour, so the default look is identical. */
.dv2-stat.green { background: var(--dv2-green); color: var(--dv2-stat-green-text, #fff); }
.dv2-stat.yellow { background: var(--dv2-yellow); color: var(--dv2-stat-yellow-text, #3a2e00); }
.dv2-stat.navy { background: var(--dv2-navy); color: var(--dv2-stat-navy-text, #fff); }
.dv2-stat.light {
  background: var(--dv2-t-light, #fff);
  border: 1.5px solid var(--dv2-line);
  color: var(--dv2-stat-light-text, #14171a);
}
/* The emoji always sits inside a white disc, never loose on the colour.
   Dropped straight onto a dark tile a 34px glyph read as a sticker that
   had been slapped on the card: the size fought the number underneath,
   and every emoji's own colours clashed with whatever shade the banner
   palette had picked for that card. The disc gives each icon the same
   footprint and the same background whatever colour the card turns, so
   the four cards finally look like a set.

   Sizes are deliberately identical to `.ppv3-stat-icon-box`
   (payments-page-v3.css) — the money page already showed its emoji this
   way, and a teacher moving between the two screens should not see the
   same idea drawn at two different sizes. */
.dv2-stat-sticker {
  position: absolute;
  top: 14px;
  right: 16px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Half the old 34px, and deliberately smaller than the disc — the
     emoji needs white margin around it or the disc reads as a crop
     rather than a badge. */
  font-size: 20px;
  line-height: 1;
  box-shadow: 0 1px 3px rgba(15, 0, 105, 0.14);
}
/* The "outstanding" card is itself white, so the disc would vanish.
   A hairline keeps the same shape visible without adding a shadow that
   would look heavy on a pale surface. */
.dv2-stat.light .dv2-stat-sticker {
  border: 1px solid var(--dv2-line, #e2e4e0);
  box-shadow: none;
}
.dv2-stat-num {
  font-weight: 700;
  /* Clears the disc (14px top + 40px tall) less the card's own 20px
     padding, so a long amount can never slide under the icon. */
  margin-top: 34px;
  font-size: 26px;
}
.dv2-stat-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  opacity: 0.9;
  margin-top: 2px;
  text-transform: uppercase;
}
/* The caption ships in two lengths (see `StatLabel` in
   MyClassesDashboardV2.tsx). Today only the long one is ever painted:
   the phone strip that needed the short form was reversed by the owner
   on 2026-09-24 (section 1a of mobile-fixes-v2.css), so a card reads the
   same on a phone as on a computer. The short strings stay as the ready
   fallback for any future narrow layout. */
.dv2-stat-label-short { display: none; }
.dv2-stat-sub {
  font-size: 11px;
  margin-top: 6px;
  font-weight: 600;
  color: var(--dv2-red);
}
.dv2-stat.green .dv2-stat-sub,
.dv2-stat.navy .dv2-stat-sub {
  color: #ffd0c8;
}

/* ===== Main grid: today's list + week calendar ===== */
.dv2-main {
  display: grid;
  grid-template-columns: 1fr 1.35fr;
  gap: 16px;
  align-items: start;
}
/* Grid items default to `min-width: auto`, which lets a wide child push
   its column past the container — on a phone the lesson card grew to
   458px inside a 358px page and its right-hand buttons were cut off.
   Pinning it to 0 makes the columns obey the page, and the children
   shrink/ellipsis instead. */
.dv2-main > * {
  min-width: 0;
}
@media (max-width: 980px) {
  .dv2-main { grid-template-columns: 1fr; }
}
.dv2-section-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--dv2-navy);
  margin: 0 0 10px;
  display: flex;
  align-items: center;
  gap: 7px;
}
.dv2-card {
  background: #fff;
  border: 1px solid var(--dv2-line);
  border-radius: 16px;
  padding: 8px;
}

/* ===== Today's lesson rows ===== */
.dv2-lesson-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 10px;
  border-radius: 12px;
}
.dv2-lesson-row:hover { background: var(--dv2-gray-bg); }

/* ---- Row states: confirmed vs "still needs confirming" ----
   When a teacher has several lessons in a day, the list must answer
   "what have I still got to do?" at a glance. Two tinted states do
   that; everything else stays neutral so the tint means something.

   The colour bar is an INSET box-shadow rather than a border-left so
   the text never shifts by 3px between states. */
.dv2-lesson-row--confirmed,
.dv2-lesson-row--needs-confirm {
  box-shadow: inset 3px 0 0 currentColor;
}
/* Done. Calm green, slightly faded — it is history now. */
.dv2-lesson-row--confirmed {
  color: var(--dv2-green);
  background: var(--dv2-green-bg);
}
.dv2-lesson-row--confirmed:hover { background: var(--dv2-green-bg); }
.dv2-lesson-row--confirmed .dv2-lesson-name,
.dv2-lesson-row--confirmed .dv2-lesson-time { opacity: 0.65; }

/* Finished, but nobody has said whether it happened. This is the one
   thing on the list that still wants a click, so it gets the amber. */
.dv2-lesson-row--needs-confirm {
  color: #c98a00;
  background: #fff8e6;
}
.dv2-lesson-row--needs-confirm:hover { background: #fdf1d3; }
.dv2-lesson-row--needs-confirm .dv2-lesson-status { color: #96690b; font-weight: 600; }

/* ===== Inline reschedule picker (opens under its lesson row) ===== */
.dv2-reschedule {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin: 0 10px 10px;
  padding: 12px;
  border-radius: 12px;
  background: var(--dv2-gray-bg, #f8fafc);
  border: 1px solid var(--dv2-border, #e2e8f0);
}
.dv2-reschedule-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--dv2-muted, #64748b);
}
.dv2-reschedule-input {
  font: inherit;
  font-weight: 500;
  color: inherit;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--dv2-border, #cbd5e1);
  background: #fff;
}
.dv2-reschedule-actions { display: flex; gap: 8px; }
.dv2-reschedule-save,
.dv2-reschedule-cancel {
  font: inherit;
  font-weight: 600;
  font-size: 13px;
  padding: 8px 14px;
  border-radius: 8px;
  cursor: pointer;
  border: 1px solid transparent;
}
.dv2-reschedule-save {
  background: var(--dv2-accent, #2563eb);
  color: #fff;
}
.dv2-reschedule-cancel {
  background: #fff;
  color: var(--dv2-muted, #475569);
  border-color: var(--dv2-border, #cbd5e1);
}
.dv2-reschedule-save:disabled,
.dv2-reschedule-cancel:disabled { opacity: 0.6; cursor: not-allowed; }
.dv2-reschedule-error {
  flex-basis: 100%;
  margin: 0;
  font-size: 12px;
  color: #b91c1c;
}
.dv2-lesson-time {
  font-weight: 700;
  font-size: 13px;
  color: var(--dv2-navy);
  width: 50px;
  flex-shrink: 0;
}
.dv2-lesson-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--dv2-gray-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 12px;
  color: var(--dv2-navy);
  flex-shrink: 0;
  text-decoration: none;
}
.dv2-lesson-info {
  flex: 1;
  min-width: 0;
}
/* "Maria Fernanda Gonzalez · B2 · trial" on ONE line, and not bold.
   The owner's words: "why are there 2 lines of text in today's
   schedule… put it all in one and not bold."

   Two lines were not a styling accident — the name, the level and the
   trial tag simply did not fit the column, so the browser wrapped them,
   and the row grew taller than the one below it. A list whose rows are
   different heights reads as clutter before a single word of it is
   read.

   Truncating a long name with an ellipsis is the trade, and it is the
   right way round here: the full text stays available on hover (the
   `title` attribute in TodaySchedule.tsx), the avatar beside it already
   carries the initials, and the level is the part that gets cut last
   because it sits at the end.

   `min-width: 0` on the parent is what makes the ellipsis possible at
   all — a flex child refuses to shrink below its content width without
   it, and the text would push the row sideways instead. */
.dv2-lesson-name {
  font-size: 13px;
  font-weight: 500;
  color: #14171a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dv2-lesson-status {
  font-size: 11px;
  color: var(--dv2-gray);
  margin-top: 1px;
  /* Block, not flex, so the dot sits on the text baseline-ish and the
     line can wrap naturally when the column is narrow. We deliberately
     do NOT force one line here: truncating "Finished — please confirm"
     to "Finishe…" tells a teacher nothing, and the compact Confirm
     button already leaves enough room on a normal screen. */
  display: block;
}
.dv2-status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  display: inline-block;
  margin-right: 5px;
  vertical-align: middle;
}
.dv2-status-dot.ok { background: var(--dv2-green); }
.dv2-status-dot.warn { background: var(--dv2-red); }
.dv2-status-dot.pending { background: #c98a00; }
.dv2-status-dot.attn { background: #c98a00; }
.dv2-status-dot.done { background: var(--dv2-green); }
.dv2-row-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}
.dv2-act-btn {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  cursor: pointer;
  font-weight: 800;
  text-decoration: none;
}
.dv2-act-btn.confirm { background: var(--dv2-green-bg); color: var(--dv2-green); }
/* Locked until the lesson has started. `not-allowed` + the tooltip
   explain the lock; opacity alone reads like a bug. */
.dv2-act-btn.confirm:disabled { opacity: 0.35; cursor: not-allowed; }
/* Promoted to a labelled button once the class is over and the
   confirmation is genuinely the next thing to do. */
.dv2-act-btn.confirm--cta {
  width: auto;
  /* Deliberately tight: this button sits next to two icon buttons in a
     narrow column, and every extra pixel here pushes the student's name
     onto a second line. */
  padding: 0 9px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.2px;
  white-space: nowrap;
  background: var(--dv2-green);
  color: #fff;
}

/* Replaces the three buttons once the lesson is confirmed.
   Solid green with a lift, not an outline: a confirmed class is the good
   outcome and teachers scan this list for it, so it should read at a
   glance rather than fade into the row. The "Confirm" button above still
   reads as the action because it says "Confirm" and sits among two other
   buttons; this one is a rounded pill with a tick and no hover state. */
.dv2-confirmed-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 28px;
  padding: 0 11px;
  border-radius: 999px;
  background: var(--dv2-green);
  border: 1px solid var(--dv2-green);
  color: #fff;
  /* Derived from the green rather than hard-coded, because the banner
     palette re-themes `--dv2-green` at runtime (see the `dv2-themed`
     block at the top of this file) and a fixed rgba shadow would drift
     to a different hue than the pill sitting on top of it. */
  box-shadow: 0 2px 6px -1px color-mix(in srgb, var(--dv2-green) 45%, transparent);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.2px;
  white-space: nowrap;
}
.dv2-act-btn.reschedule { background: var(--dv2-blue-bg); color: var(--dv2-blue); }
.dv2-act-btn.cancel { background: var(--dv2-red-bg); color: var(--dv2-red); }
.dv2-act-btn.generate {
  background: var(--dv2-navy);
  color: #fff;
  width: auto;
  padding: 0 10px;
  font-size: 11px;
  font-weight: 700;
}
.dv2-act-btn:hover:not(:disabled) { filter: brightness(0.94); }

.dv2-empty {
  padding: 32px 12px;
  text-align: center;
  color: var(--dv2-gray);
  font-size: 13px;
}

/* ===== Mini week calendar ===== */
.dv2-cal-card { padding: 14px; }
.dv2-cal-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 2px 4px 12px;
}
.dv2-cal-nav-label {
  font-weight: 700;
  font-size: 13px;
  color: var(--dv2-navy);
}
.dv2-cal-nav-arrows { display: flex; gap: 6px; }
/* The arrows are <a> elements. They need flex-centering so the
   chevron glyph sits in the visual middle of the fixed-size box —
   otherwise it inherits the anchor's inline text-baseline layout
   and drifts to the top-left corner. `text-decoration: none` and
   `line-height: 1` complete the button-like look. */
.dv2-cal-arrow {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  border: 1px solid var(--dv2-line);
  background: #fff;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  color: var(--dv2-gray);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
.dv2-cal-arrow:hover { background: var(--dv2-gray-bg); color: var(--dv2-navy); }
.dv2-cal-scroll {
  max-height: 320px;
  overflow-y: auto;
  border-radius: 10px;
}
.dv2-cal-scroll::-webkit-scrollbar { width: 6px; }
.dv2-cal-scroll::-webkit-scrollbar-thumb {
  background: var(--dv2-line);
  border-radius: 10px;
}
.dv2-cal-grid {
  display: grid;
  /* 40px, not 32px: the hour gutter has to fit its widest label. "10:00"
     through "12:00" measure ~33px at the 12px this column is set in, and the
     label carries 5px of right padding, so 32px clipped every two-digit hour
     against the first day column — visible in the owner's phone screenshot as
     "10:00" running into the grid line. This is not fallout from the phone
     compaction (that pass emits nothing for .dv2-cal-grid or .dv2-cal-time);
     it was clipping at every width, desktop included. */
  grid-template-columns: 40px repeat(7, 1fr);
}
.dv2-cal-head {
  position: sticky;
  top: 0;
  z-index: 5;
  background: #fff;
  font-size: 10px;
  font-weight: 700;
  color: var(--dv2-gray);
  text-align: center;
  padding: 4px 0 6px;
  text-transform: uppercase;
}
.dv2-cal-head.today { color: var(--dv2-navy); }
.dv2-cal-head-num {
  font-size: 12px;
  color: #14171a;
  margin-top: 2px;
}
.dv2-cal-head.today .dv2-cal-head-num {
  background: var(--dv2-navy);
  color: #fff;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 2px auto 0;
  font-size: 11px;
}
.dv2-cal-time {
  font-size: 9px;
  color: var(--dv2-gray);
  text-align: right;
  padding-right: 5px;
  transform: translateY(-5px);
}
.dv2-cal-cell {
  border-top: 1px solid var(--dv2-gray-bg);
  border-left: 1px solid var(--dv2-gray-bg);
  min-height: 32px;
  position: relative;
}
.dv2-cal-cell.today-col { background: var(--dv2-today-bg, #f7f6ff); }
.dv2-cal-block {
  position: absolute;
  left: 1.5px;
  right: 1.5px;
  border-radius: 6px;
  padding: 2px 5px;
  font-size: 9px;
  font-weight: 700;
  line-height: 1.2;
  overflow: hidden;
  cursor: pointer;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-decoration: none;
}
.dv2-cal-block.ok {
  background: var(--dv2-green-bg);
  color: var(--dv2-green);
  border-left: 3px solid var(--dv2-green);
}
.dv2-cal-block.warn {
  background: var(--dv2-red-bg);
  color: var(--dv2-red);
  border-left: 3px solid var(--dv2-red);
}
.dv2-cal-block.pending {
  background: var(--dv2-amber-bg);
  color: var(--dv2-amber);
  border-left: 3px solid #c98a00;
}
.dv2-cal-block-time {
  opacity: 0.7;
  font-weight: 600;
  font-size: 8px;
}
.dv2-cal-hint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  font-size: 11px;
  color: var(--dv2-gray);
  padding-top: 8px;
}
