/* =========================================================================
   WatchCrew skinning layer
   -------------------------------------------------------------------------
   Bootstrap 5.3.3 is the component substrate. This file holds the project's
   design-token set + the thin layer of components Bootstrap doesn't
   express natively. Roughly grouped:
     1. :root design tokens   (colors, spacing, radii, typography, chart
                                palette, --sidebar-width, etc.)
     2. Dark theme overrides  (twin --color-* values, gated on
                                <html data-bs-theme="dark">)
     3. Shell + nav skin      (sidebar, main pane, profile widget)
     4. Users-page components (role pill, tinted status badge with dot)
     5. .btn-primary override (per-component --bs-btn-* vars)
     6. Typography utilities  (.text-xs / .text-sm / .text-md, .tracking-wide)
     7. Status-dot + chart-legend-swatch utilities
     8. Shift status badge    (.status-badge.{slug} for ShiftStatus enum)
     9. User-cell components  (.user-avatar / .user-name / .user-email)

   No LEGACY block left — the prior bottom-of-file divider was emptied
   when the last template ports landed (shift_create, shift_detail,
   settings). New components: pick the right group above and add a
   comment block.

   Page-scoped stylesheets live alongside this file:
     * static/css/login.css     — loaded only by templates/account/login.html
     * static/css/calendar.css  — loaded only by templates/shifts/schedule.html
   Both consume tokens declared here; load order is pinned by the link-tag
   positions in their host templates' {% block extra_head %}.

   Loaded AFTER Bootstrap's CSS in base.html so declarations here win
   specificity on ties (e.g. `.sidebar` vs BS utility classes).
   ========================================================================= */

:root {
  --color-bg: #F5F6FA;
  --color-surface: #ffffff;
  --color-text: #0F172A;
  --color-muted: #64748B;
  --color-border: #E2E8F0;
  --color-primary: #3b82f6;
  --color-primary-dark: #2563eb;
  --color-primary-tint: #eff6ff;
  --color-surface-hover: #f8fafc;
  --color-accent: #10b981;
  --color-error: #ef4444;
  --color-success: #16a34a;
  --color-text-disabled: #CBD5E1;

  /* User status families (active / invited / expired / deactivated) — used
     by the Users page's status badge. Kept distinct from the shift
     --status-* tokens below because they render differently (tinted
     background + leading dot, not solid-color pill) and would have been
     ambiguous under one name.

     `expired` deliberately reuses the deactivated palette (slate / lapsed-
     inert) rather than a yellow-amber alarm tone: an expired invite is a
     stale-but-recoverable state whose remedy is "resend invite", not an
     error the operator must act on urgently. Pairing the two greys lets
     the eye group "no longer live" rows together while leaving `invited`
     (amber) as the only attention-drawing pre-acceptance pill. */
  --user-active-bg: #dcfce7;
  --user-active-text: #166534;
  --user-active-dot: #22c55e;
  --user-invited-bg: #fef3c7;
  --user-invited-text: #92400e;
  --user-invited-dot: #f59e0b;
  --user-expired-bg: #f1f5f9;
  --user-expired-text: #94a3b8;
  --user-expired-dot: #cbd5e1;
  --user-deactivated-bg: #f1f5f9;
  --user-deactivated-text: #94a3b8;
  --user-deactivated-dot: #cbd5e1;

  --font-sans: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* Shift status pill — tinted-pill (bg + text pair) per the shift-detail
     redesign cycle. Prior single-color seeds (`--status-<slug>`) rendered
     a solid-fill pill with white text and were retired; nothing in the
     codebase still references them, so no aliases kept. The slug suffix
     matches `ShiftStatus.<NAME>.value|slugify` and is consumed by the
     `.status-badge.<slug>` rule further down.
     Color seeds (yellow needs / blue confirmed / amber in-progress /
     neutral-indigo completed / red expired / neutral grey cancelled) are
     drawn from `watchcrew-app.html`'s design-system reference; entries
     not seeded by the prototype (in-progress, expired, cancelled) sit on
     the same tint family the prototype uses for adjacent states. */
  --status-needs-people-bg: #FEF9C3;
  --status-needs-people-text: #854D0E;
  --status-confirmed-bg: #DBEAFE;
  --status-confirmed-text: #1E40AF;
  --status-in-progress-bg: #FEF3C7;
  --status-in-progress-text: #92400E;
  --status-completed-bg: #E0E7FF;
  --status-completed-text: #3730A3;
  --status-expired-bg: #FEE2E2;
  --status-expired-text: #991B1B;
  --status-cancelled-bg: #F1F5F9;
  --status-cancelled-text: #64748B;

  /* "This month" tile (volunteer dashboard) — indigo tint distinct from
     the six ShiftStatus enum entries above. Not part of the shift-status
     state machine; lives in the same `--status-<slug>-{bg,text}` namespace
     because the dashboard's `.card-icon-tile.month` consumes it through
     the same modifier convention as `.needs` / `.ongoing` / `.proposal`,
     and a parallel namespace would have meant duplicating the tile's
     base/modifier wiring. Seed values from `watchcrew-app.html`'s
     volunteer "This month" card. */
  --status-month-bg: #EDE9FE;
  --status-month-text: #6366F1;

  /* Tinted-blue name badge — used by manager-dashboard summary cards
     to render compact "👤 Alex R." pills inside a shift card. Distinct
     from `.user-role-pill` (shipped role display, blue tint) and from
     `.user-status-badge` (active/invited/expired/deactivated state with leading
     dot) so a future restyle of either doesn't drag the dashboard along.
     Seed values (#EFF6FF / #1D4ED8) from the prototype's manager Next/
     Current shift cards. */
  --badge-name-bg: #EFF6FF;
  --badge-name-text: #1D4ED8;

  /* Participant-status badge — tinted pill rendered next to a participant
     row on the shift-detail page. Distinct from `--status-*` (shift
     status enum) because participants have their own state machine
     (invited / confirmed / canceled). Confirmed seed comes straight from
     the prototype (#D1FAE5 / #065F46); invited reuses the amber tint;
     canceled uses the same neutral grey as `--status-cancelled-*` so
     the visual language is consistent. */
  --participant-confirmed-bg: #D1FAE5;
  --participant-confirmed-text: #065F46;
  --participant-invited-bg: #FEF3C7;
  --participant-invited-text: #92400E;
  --participant-canceled-bg: #F1F5F9;
  --participant-canceled-text: #64748B;

  /* Avatar size variants. Default `.user-avatar` is 32px (declared in the
     rule itself, not a token, because nothing else in the project reads
     it). The shift-detail "Created by" sidebar row needs a smaller 22px
     avatar; tokenized so future smaller-avatar callsites land on the same
     value rather than re-seeding inline `style="width:22px"`. */
  --avatar-size-sm: 22px;

  /* Calendar shift color groups — pastel bg + 3px colored left-border +
     unified slate text. Source of truth: the cycle's "Design System" page
     in `watchcrew-app.html` (Calendar event statuses table). The previous
     saturated-solid + white-text treatment was retired with the cycle's
     visual refresh; the left border is now the primary status signal so
     the event body reads as quiet content rather than a colored chip.

     Text color is the same `#1E293B` (slate-800) across every status
     *except* `done`, which uses muted `#475569` (slate-600) so
     completed shifts read as recessed/historical. The unified-text
     rule is a soft default for the active/terminal palette; the
     `done` carveout is deliberate — when the only signal a completed
     shift carries is "this already happened," muted text reinforces
     that more than any border can. `#1E293B` is otherwise distinct
     from `--color-text` (slate-900, `#0F172A`) so the calendar's
     pastel-fill cards don't read as heavy as the page's headline copy. The pastel bgs render fine against the
     dark surface in dark mode (events stay locally light against the
     dark gridlines), so no dark-mode twins — matches the dark-mode
     block's "calendar tokens stay as-is" stance. */
  /* Calendar event color families. Six distinct status hues:
       needs-people  → warm amber   (action required, recruiting)
       confirmed     → pale green   (minimum people joined, ready to run)
       ongoing       → mint green   (IN_PROGRESS, "happening right now")
       done          → muted slate  (COMPLETED, terminal-success / recessed)
       expired       → gray         (passed without filling, alerting)
       cancel        → soft red     (CANCELLED, terminal-fail)
     CONFIRMED and ONGOING share the green hue family but at different
     saturations so a manager can scan the calendar and see at a glance
     which shifts are *staffed-and-waiting* (pale green) vs *running-now*
     (mint, with a deeper border). Done and expired both live in the
     slate family but the signal axis differs: done uses a deeper bg
     (`#E2E8F0` slate-200) with muted text (`#475569`), reading as a
     filled-in historical block; expired uses a lighter bg (`#F1F5F9`
     slate-100) with full-strength text (`#1E293B`), reading as a
     still-bright card that demands attention. Both share the same
     border (`#64748B`) — bg + text carry the distinction. Cancelled
     is red (was grey-brown) so terminal-fail reads at a glance. */
  --cal-needs-bg: #FFF3CD;
  --cal-needs-border: #CA8A04;
  --cal-needs-text: #1E293B;
  --cal-confirmed-bg: #D1FAE5;
  --cal-confirmed-border: #059669;
  --cal-confirmed-text: #1E293B;
  --cal-ongoing-bg: #DCFCE7;
  --cal-ongoing-border: #16A34A;
  --cal-ongoing-text: #1E293B;
  --cal-done-bg: #E0F2FE;
  --cal-done-border: #38BDF8;
  --cal-done-text: #1E293B;
  --cal-expired-bg: #ebe5f3;
  --cal-expired-border: #64748B;
  --cal-expired-text: #1E293B;
  --cal-cancel-bg: #FFE4E4;
  --cal-cancel-border: #DC2626;
  --cal-cancel-text: #1E293B;

  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  --space-2xl: 2.5rem;

  --radius-sm: 4px;
  --radius-md: 8px;
  --sidebar-width: 220px;

  /* Bootstrap variable overrides (theming layer).
     :root-scoped overrides are how we re-skin Bootstrap 5.3 components
     to match the project's visual identity (deeper primary blue, cooler
     slate borders, 8px radii, 800-weight headings) without forking the
     vendor CSS. NOTE: --bs-border-color-translucent — not
     --bs-border-color — is the variable BS actually consumes for
     .card / .dropdown-menu / .modal-content borders; setting only
     --bs-border-color does not flip those. Both are set for parity.
     For .btn-primary specifically, see the per-component override
     block lower in the file — :root-level --bs-primary alone does
     NOT reach button color (BS 5.3 SASS-builds derivative variables
     inside the .btn-primary rule).
     DO NOT override --bs-body-bg here. BS sets every panel component's
     surface (--bs-card-bg, --bs-modal-bg, --bs-dropdown-bg,
     --bs-popover-bg, --bs-list-group-bg, --bs-accordion-bg,
     --bs-pagination-bg) to var(--bs-body-bg) by default, so any
     :root-level --bs-body-bg tint cascades into all of those and
     turns "white" cards/modals/dropdowns gray. The page background
     is already painted by `body { background: var(--color-bg); }`
     below, which uses our --color-bg token and theme-flips correctly. */
  --bs-body-color: #0F172A;
  --bs-secondary-color: #64748B;
  --bs-border-color: #E2E8F0;
  --bs-border-color-translucent: #E2E8F0;
  --bs-primary: #2563EB;
  --bs-primary-rgb: 37, 99, 235;
  --bs-headings-font-weight: 800;
  --bs-border-radius: 0.5rem;

  /* Typography micro-scale.
     Bootstrap's `.small` (~14px) is the smallest off-the-shelf utility;
     captions, badge meta, chart legend text, and pagination info live
     below that. Three tokens cover the project's footprint — change here
     and every callsite tracks. Custom utility names (.text-xs / .text-sm /
     .text-md, defined further down) are deliberately distinct from
     Bootstrap's `.fs-N` (relative scale) and `.small` (single stop). */
  --font-size-xs: 11px;
  --font-size-sm: 12px;
  --font-size-md: 14px;

  /* Dashboard bar-chart palette. The Chart.js config in
     templates/shifts/dashboard.html still hardcodes these hex values
     (Chart.js can't read CSS variables). Aliasing them here keeps the
     legend swatches and the chart bars in lockstep when the palette
     ever shifts. */
  --chart-shifts: #3b82f6;
  --chart-hours: #34d399;
}

/* =========================================================================
   Dark theme — twin overrides for the foundational color tokens.
   -------------------------------------------------------------------------
   Triggered by <html data-bs-theme="dark"> set by the inline theme bootstrap
   in base.html / base_auth.html (which reads prefers-color-scheme).

   Scope is intentionally narrow: only the eight foundational --color-*
   tokens flip. Status (--status-*), user (--user-*), calendar (--cal-*),
   spacing, and radius tokens stay as-is — they're either status-coded
   semantics that must read the same in both themes or scalar values that
   don't depend on light/dark.

   Bootstrap 5.3's own component variables (--bs-body-bg, --bs-body-color,
   button hover states, modal backdrop, form-control bg, etc.) are picked
   up automatically once data-bs-theme="dark" is on <html>. We don't
   override those — Bootstrap already ships a tuned dark palette.
   ========================================================================= */
[data-bs-theme="dark"] {
  --color-bg: #0b0f14;
  --color-surface: #11161c;
  --color-text: #e5e7eb;
  --color-muted: #9ca3af;
  --color-border: #1f2937;
  --color-primary-tint: #1e293b;
  --color-surface-hover: #161b22;
  --color-text-disabled: #4b5563;

  /* Twin for the one BS override whose light value would otherwise bleed
     through to dark mode. --bs-border-color-translucent restores BS's
     own dark translucent border so cards/dropdowns don't render a slate
     border on a near-black surface. Other :root BS overrides (radius,
     font weight, primary blue, --bs-body-color, --bs-secondary-color)
     are theme-independent or harmless across light/dark — no twins
     needed unless smoke reveals contrast issues. --bs-body-bg is
     deliberately NOT overridden in :root (see note there); BS's own
     dark theme sets it to #212529 automatically. */
  --bs-border-color-translucent: rgba(255, 255, 255, 0.15);
}

* { box-sizing: border-box; }

/* Apply Nunito globally. Bootstrap's reboot already resets margins, so we
   only need to swap the font-family + base background. */
body {
  font-family: var(--font-sans);
  background: var(--color-bg);
  color: var(--color-text);
}

/* =========================================================================
   Shell skin: sidebar + main pane
   -------------------------------------------------------------------------
   At lg+ (≥992px) the sidebar is fixed-position (pulled out of flow) so
   the main pane's scroll doesn't affect it; `.main { margin-left: ... }`
   leaves space for it. Below lg, Bootstrap's `offcanvas-lg` component owns
   the presentation: the same DOM element becomes a slide-in drawer, and
   the fixed-position / margin-left rules MUST NOT apply (they would
   double-up with Bootstrap's own offcanvas transform and clip the
   hamburger top bar). The 992px literal repeats in three @media blocks
   below; that's deliberate — three copies isn't worth a token.
   ========================================================================= */

@media (min-width: 992px) {
  /* !important is load-bearing here. At lg+ Bootstrap's `.offcanvas-lg`
     forces `background-color: transparent !important` and `position: static`
     to make the offcanvas render inline as page chrome (not as the slide-in
     drawer it is below lg). Our sidebar IS the project's static lg+ chrome,
     so we have to win that cascade — there's no specificity-only path,
     since Bootstrap uses !important. The `var(--color-surface)` background
     was masked when `--bs-body-bg` was Bootstrap's default white; once we
     set `--bs-body-bg: #F5F6FA` the transparency became visible. */
  .sidebar {
    width: var(--sidebar-width) !important;
    min-height: 100vh;
    position: fixed !important;
    top: 0;
    left: 0;
    bottom: 0;
    background: var(--color-surface) !important;
    border-right: 1px solid var(--color-border) !important;
    padding: 1.5rem 1rem;
    z-index: 100;
  }
}

.sidebar-brand {
  display: block;
  font-size: 16px;
  font-weight: 700;
  color: #0F172A;
  margin-bottom: 1.5rem;
}

.brand-logo {
  display: block;
  height: 48px;
  width: auto;
}

.brand-logo--sm {
  height: 24px;
}

.nav-item-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 12px;
  border: none;
  background: none;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  color: #64748B;
  text-align: left;
  text-decoration: none;
  transition: background .15s, color .15s;
}
.nav-item-btn:hover { background: #F5F6FA; color: #0F172A; }
.nav-item-btn.active { background: var(--color-primary); color: #fff; }
.nav-item-btn.active:hover { background: var(--color-primary); color: #fff; }

.nav-item-icon { font-size: 15px; line-height: 1; }

/* Main pane base styles apply at every breakpoint. Two things scale with
   the breakpoint:
     1. Padding: a tight 1rem rim on mobile so the content card runs nearly
        edge-to-edge (~40px of viewport on a 360px phone is otherwise lost
        to chrome). Widens to 2.5rem at lg+ where the desktop sidebar sets
        a different visual rhythm.
     2. Left offset: only at lg+, to clear the static sidebar. Below lg the
        sidebar lives in an offcanvas drawer toggled by the top-bar
        hamburger, so no margin is needed. */
.main {
  padding: var(--space-md);
  min-height: 100vh;
  min-width: 0;
}
@media (min-width: 992px) {
  .main {
    padding: var(--space-2xl);
    margin-left: var(--sidebar-width);
  }
}

/* Profile widget: dropup trigger pinned to sidebar bottom via Bootstrap's
   `mt-auto` on the enclosing .dropup wrapper. The <button> looks like a
   nav row but behaves as a Bootstrap dropdown toggle. */
.profile-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: none;
  background: none;
  border-radius: 10px;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  border-top: 1px solid var(--color-border);
  padding-top: 14px;
  margin-top: 4px;
  transition: background .15s;
}
.profile-trigger:hover { background: #F5F6FA; }
.profile-trigger::after { display: none; }   /* kill Bootstrap's default caret */

.profile-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
}

.profile-info { flex: 1; min-width: 0; }
.profile-name {
  font-size: 13px;
  font-weight: 700;
  color: #0F172A;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-role {
  font-size: 11px;
  color: #64748B;
}

/* =========================================================================
   Users page skin (reused by WU5's Bootstrap port)
   -------------------------------------------------------------------------
   These two rules survive the port because Bootstrap doesn't express the
   designs natively: the role pill uses a blue tint distinct from any BS
   badge variant, and the user status badge has a leading dot + rounded pill
   shape that would require multiple utility classes per instance.
   ========================================================================= */

/* Role pill — blue tint, not status-coloured */
.user-role-pill {
  font-size: 0.6875rem;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 20px;
  background: var(--color-primary-tint);
  color: var(--color-primary-dark);
  white-space: nowrap;
  display: inline-block;
}

/* User status badge (distinct from shift .status-badge — tinted bg + dot) */
.user-status-badge {
  font-size: 0.6875rem;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 20px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}
.user-status-badge::before {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
}
.user-status-badge.active { background: var(--user-active-bg); color: var(--user-active-text); }
.user-status-badge.active::before { background: var(--user-active-dot); }
.user-status-badge.invited { background: var(--user-invited-bg); color: var(--user-invited-text); }
.user-status-badge.invited::before { background: var(--user-invited-dot); }
.user-status-badge.expired { background: var(--user-expired-bg); color: var(--user-expired-text); }
.user-status-badge.expired::before { background: var(--user-expired-dot); }
.user-status-badge.deactivated { background: var(--user-deactivated-bg); color: var(--user-deactivated-text); }
.user-status-badge.deactivated::before { background: var(--user-deactivated-dot); }

/* =========================================================================
   .btn-primary — per-component variable override (load-bearing, not legacy)
   -------------------------------------------------------------------------
   Bootstrap 5.3 sets --bs-btn-bg / --bs-btn-hover-bg / --bs-btn-active-bg
   (and matching border colors) INSIDE the .btn-primary rule itself,
   compiled from SASS at vendor build time with hardcoded hex values. A
   :root-level --bs-primary override does NOT flow into button color
   because the button rule's own derivative variables shadow it. To
   actually flip primary-button color, we must override those derivative
   vars on .btn-primary itself. Same pattern applies if we ever need to
   re-skin .btn-secondary, .btn-danger, etc. Do NOT delete this rule as
   redundant with the :root --bs-primary override — they target different
   layers of the cascade.
   ========================================================================= */
.btn-primary {
  --bs-btn-bg: #2563EB;
  --bs-btn-hover-bg: #1D4ED8;
  --bs-btn-active-bg: #1E40AF;
  --bs-btn-disabled-bg: #2563EB;
  --bs-btn-border-color: #2563EB;
  --bs-btn-hover-border-color: #1D4ED8;
  --bs-btn-active-border-color: #1E40AF;
  --bs-btn-disabled-border-color: #2563EB;
}

/* =========================================================================
   Typography utilities (micro-scale)
   -------------------------------------------------------------------------
   Bootstrap ships `.small` (~14px relative) and `.fs-1` … `.fs-6`
   (relative scale, smallest is 1rem). Below 1rem we have nothing native,
   so the project leaked `style="font-size:11px"` etc. across templates.
   Three custom utilities replace those — see the `--font-size-*` tokens
   in :root. The `.tracking-wide` helper covers the one repeated
   letter-spacing the dashboard's status labels use.
   ========================================================================= */
.text-xs { font-size: var(--font-size-xs); }
.text-sm { font-size: var(--font-size-sm); }
.text-md { font-size: var(--font-size-md); }
.tracking-wide { letter-spacing: .5px; }

/* =========================================================================
   Status-indicator dot
   -------------------------------------------------------------------------
   8px circle for label rows on the dashboard summary cards. Three
   variants — colors come from the calendar event tokens (--cal-*-border)
   so the dashboard cards and the calendar speak the same status visual
   language. Distinct from `.status-badge` (further down) which is a
   solid-fill pill carrying the shift-status enum.
   ========================================================================= */
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.status-dot.needs    { background: var(--cal-needs-border); }
.status-dot.ongoing  { background: var(--cal-ongoing-border); }
.status-dot.proposal { background: var(--color-border); }

/* =========================================================================
   Chart legend swatch
   -------------------------------------------------------------------------
   10×10 square with a 2px radius — used by the dashboard bar chart's
   inline HTML legend (Chart.js's own legend is disabled there). Two
   variants reading the chart palette tokens; if the palette shifts,
   the swatches and the bars track because the JS reads --chart-* via
   getComputedStyle is NOT possible in Chart.js, so the JS still uses
   hex literals — but the tokens make the relationship explicit.
   ========================================================================= */
.chart-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}
.chart-legend-swatch.shifts { background: var(--chart-shifts); }
.chart-legend-swatch.hours  { background: var(--chart-hours); }
/* Indigo variant for the volunteer dashboard's personal bar chart. The
   manager bar's "Shifts" series stays blue (`--chart-shifts`); the
   volunteer surface deliberately diverges to indigo (#6366F1, matching
   the THIS MONTH card's `--status-month-text`) so the two surfaces
   read as distinct. The hex literal is intentional — Chart.js can't
   resolve CSS custom properties, so the legend swatch matches the bar
   color the JS init ships. */
.chart-legend-swatch.shifts-personal { background: #6366F1; }

/* =========================================================================
   Shift status badge — tinted pill carrying the ShiftStatus enum
   -------------------------------------------------------------------------
   Used by templates/shifts/shift_card.html (slugified status) and
   templates/shifts/reports.html (per-row status column), plus the
   shift-detail header. Distinct from `.status-dot` (above) which is a
   label-row indicator using the calendar border palette, and from
   `.user-status-badge` (skin layer) which is the tinted-pill-with-dot
   shape on the Users page.

   Pre-cycle this rule rendered a solid-fill pill with white text seeded
   by single-color `--status-*` tokens. The shift-detail redesign moved
   the visual language to a tinted pill (soft bg + dark same-hue text)
   to match the prototype's pill treatment and to read quieter against
   white card surfaces. Each state now consumes a `--status-<slug>-bg`
   / `--status-<slug>-text` pair declared in :root.

   No leading dot here (see `.user-status-badge` for that shape). The
   shift-status pill is tinted enough that the dot would be visual
   redundancy at the dashboard scale.

   Class slug matches `ShiftStatus.<NAME>.value|slugify`
   (e.g. "needs_people" → "needs-people").
   ========================================================================= */
.status-badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 20px;
  font-size: 0.6875rem;
  font-weight: 600;
  white-space: nowrap;
}
.status-badge.needs-people { background: var(--status-needs-people-bg); color: var(--status-needs-people-text); }
.status-badge.confirmed    { background: var(--status-confirmed-bg);    color: var(--status-confirmed-text); }
.status-badge.in-progress  { background: var(--status-in-progress-bg);  color: var(--status-in-progress-text); }
.status-badge.completed    { background: var(--status-completed-bg);    color: var(--status-completed-text); }
.status-badge.expired      { background: var(--status-expired-bg);      color: var(--status-expired-text); }
.status-badge.cancelled    { background: var(--status-cancelled-bg);    color: var(--status-cancelled-text); }

/* =========================================================================
   Participant badge — tinted pill for shift-detail's per-participant row
   -------------------------------------------------------------------------
   Three modifiers map to the participant state machine: `.invited` (the
   row exists because a manager invited them; they haven't accepted yet),
   `.confirmed` (active member of the shift), and `.canceled` (was on
   the shift, then dropped). Same shape as `.status-badge` (rounded
   pill, soft bg + dark text) — kept as a sibling rule rather than a
   shared base because the modifier vocabulary and the token namespace
   are independent (`--participant-*-{bg,text}`), and conflating them
   would couple two unrelated state machines.
   ========================================================================= */
.participant-badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 20px;
  font-size: 0.6875rem;
  font-weight: 600;
  white-space: nowrap;
}
.participant-badge.invited   { background: var(--participant-invited-bg);   color: var(--participant-invited-text); }
.participant-badge.confirmed { background: var(--participant-confirmed-bg); color: var(--participant-confirmed-text); }
.participant-badge.canceled  { background: var(--participant-canceled-bg);  color: var(--participant-canceled-text); }

/* =========================================================================
   Card icon tile — corner glyph for dashboard summary cards
   -------------------------------------------------------------------------
   34px rounded square holding a centered SVG icon, used in the top-right
   of each manager / volunteer dashboard summary card (Next shift, Current
   shift, Proposals open, This month). Background is status-tinted; the
   icon's stroke color is set inline on the SVG via `currentColor`, so the
   rule sets `color` to the matching `--status-<slug>-text` token and the
   SVG inherits it.

   Modifiers reuse the existing `--status-<slug>-{bg,text}` token namespace
   from the shift-status pill (`.needs` → needs-people, `.ongoing` →
   confirmed/in-progress green family per the prototype, `.proposal` →
   cancelled neutral family). `.month` consumes the new `--status-month-*`
   pair declared in :root for the volunteer "This month" card's indigo
   tint, which has no shift-status counterpart.
   ========================================================================= */
.card-icon-tile {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.card-icon-tile.needs    { background: var(--status-needs-people-bg); color: var(--status-needs-people-text); }
.card-icon-tile.ongoing  { background: var(--participant-confirmed-bg); color: var(--participant-confirmed-text); }
.card-icon-tile.proposal { background: var(--status-cancelled-bg);    color: var(--status-cancelled-text); }
.card-icon-tile.month    { background: var(--status-month-bg);        color: var(--status-month-text); }

/* =========================================================================
   Stat delta — inline trend indicator (+12% vs last month)
   -------------------------------------------------------------------------
   Tiny semibold colored text rendered under a stat-card value. Two
   modifiers: `.up` (improvement, green) and `.down` (regression, yellow/
   amber).

   IMPORTANT: `.down` is yellow (amber #B45309), NOT red. This is a
   deliberate "caution-not-alarm" choice — coverage drops on a bottom-up
   shift roster are a signal to look, not a failure state. Red is reserved
   for true error / cancellation states (see `--status-expired-*` and the
   `.dropdown-item.text-danger` family). Do NOT "fix" `.down` to red on
   the assumption it was an oversight; the asymmetric green/amber pairing
   is the design.
   ========================================================================= */
.stat-delta {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
}
.stat-delta.up   { color: #059669; }
.stat-delta.down { color: #B45309; }

/* Name badge — tinted-blue compact pill for participant names rendered
   inside manager-dashboard summary cards. Consumes the WU1 `--badge-name-*`
   token pair so a re-tinting only touches :root. Distinct from
   `.user-role-pill` and `.user-status-badge` (see token comment block). */
.name-badge {
  background: var(--badge-name-bg);
  color: var(--badge-name-text);
}

/* =========================================================================
   Activity icon — round 28px tinted circle for the manager dashboard's
   Recent Activity feed. Status-themed via modifier classes; the icon's
   stroke color comes from `currentColor`, so the SVG inherits the
   modifier's `color`.
   ========================================================================= */
.activity-icon {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.activity-icon.joined  { background: var(--participant-confirmed-bg); color: var(--participant-confirmed-text); }
.activity-icon.created { background: var(--badge-name-bg);            color: var(--badge-name-text); }

/* =========================================================================
   Empty-seat icon — placeholder for an unfilled participant slot
   -------------------------------------------------------------------------
   32px circle with a 2px dashed border (project's `--color-border`
   token, so it tracks dark-mode flips) wrapping a centered plus icon.
   Sits in the same row position as `.user-avatar` for filled spots, so
   the participant list reads as a uniform vertical rhythm regardless of
   filled vs empty. SVG glyph is provided by the consuming template
   (the rule owns the chrome only — color of the glyph is set inline
   on the SVG so the icon palette can vary if a future variant needs
   it). Width/height intentionally hardcoded at 32px to match the
   default `.user-avatar` size; if `.user-avatar` ever moves to a
   token, this rule should follow.
   ========================================================================= */
.empty-seat-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px dashed var(--color-border);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* User-cell components (avatar circle + name/email pair).
   Used by templates/accounts/users.html row partial AND
   templates/shifts/reports.html aggregated/detailed tabs. The avatar
   background is set per-row via JS (static/js/avatars.js) from
   `data-seed`-hashed palette slot — the rule below only owns the
   geometry and the static text styling, NOT the per-user color. */
.user-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6875rem;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
  letter-spacing: -0.3px;
}
/* Smaller avatar variant — used in the shift-detail sidebar's
   "Created by" row, where the avatar reads as supplementary metadata
   alongside a name rather than as a primary list-row identity. Modifier
   class so existing JS that hashes `data-seed` still applies; only the
   geometry shrinks. Font drops to 9px to keep the two-letter initials
   legible inside a 22px circle without overflowing. */
.user-avatar.user-avatar-sm {
  width: var(--avatar-size-sm);
  height: var(--avatar-size-sm);
  font-size: 9px;
}
.user-name {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text);
}
.user-email {
  font-size: 0.75rem;
  color: var(--color-muted);
  margin-top: 1px;
}

/* ── Welcome / onboarding modal ──
   The modal is a two-step setup wizard rendered on the dashboard for users
   with `User.onboarding_completed_at IS NULL` and at least one pending
   profile field. The shape matches `templates/accounts/_onboarding_modal.html`:
   step-pill + progress bar + step-1 (avatar preview + name/phone/tshirt) /
   step-2 (notification toggles).

   Below the sm breakpoint the modal converts to a bottom sheet via the
   `.wm-content` overrides — bigger touch targets, anchored to the viewport
   bottom, with a small "drag handle" pseudo-element. Mobile is verified
   only via Chrome devtools — see ui-standards.md §"Known gaps". */
.wm-dialog {
  max-width: 500px;
}
.wm-step-pill {
  font-size: 11px;
  font-weight: 700;
  color: var(--color-primary-dark);
  background: var(--color-primary-tint);
  padding: 3px 10px;
  border-radius: 20px;
  letter-spacing: 0.3px;
}
.wm-progress-track {
  width: 100%;
  height: 3px;
  background: var(--color-border);
  border-radius: 2px;
  margin-bottom: 16px;
}
.wm-progress-fill {
  height: 3px;
  border-radius: 2px;
  background: var(--color-primary-dark);
  transition: width 0.3s ease;
}
.wm-avatar-preview {
  background: var(--color-surface-hover);
  border: 1px solid var(--color-border);
}
.wm-avatar.user-avatar {
  width: 44px;
  height: 44px;
  font-size: 15px;
}
.wm-pref-row {
  border-bottom: 1px solid var(--color-surface-hover);
}
@media (max-width: 575px) {
  .wm-dialog {
    margin: 0 !important;
    max-width: 100% !important;
    min-height: 100%;
    align-items: flex-end !important;
  }
  .wm-content {
    border-radius: 20px 20px 0 0 !important;
    max-height: 92vh;
    overflow-y: auto;
  }
  /* Bigger touch targets on mobile */
  .wm-content .form-control-sm,
  .wm-content .form-select-sm {
    font-size: 16px !important;
    padding: 10px 12px !important;
    height: auto !important;
  }
  .wm-content .form-check-input {
    width: 3em !important;
    height: 1.6em !important;
  }
  .wm-content .btn-sm {
    padding: 10px 16px !important;
    font-size: 14px !important;
  }
  /* Sheet handle indicator */
  .wm-content::before {
    content: '';
    display: block;
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--color-border);
    margin: 12px auto 0;
  }
}

/* ── Change-role modal: role-card selector ── */
.cr-role-card {
  display: block;
  padding: 12px 14px;
  border: 1.5px solid var(--bs-border-color-translucent);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  background: #fff;
}
.cr-role-card:hover {
  border-color: #93c5fd;
  background: #f8fafc;
}
.cr-role-card.active {
  border-color: #2563eb;
  background: #eff6ff;
}
.cr-check {
  opacity: 0;
  transition: opacity 0.15s;
  flex-shrink: 0;
}
.cr-role-card.active .cr-check {
  opacity: 1;
}
