/* ============================================================================
   Sync AI — Design System
   ----------------------------------------------------------------------------
   A single global layer that gives every page the same surfaces, forms, tables,
   buttons and spacing.  Loaded AFTER material-dashboard.css and custom.css, so
   the rules here intentionally win.

   Contents
     1.  Tokens
     2.  Base / typography
     3.  Layout shell + collapsible sidebar
     4.  Page header
     5.  Cards / panels
     6.  Forms (incl. crispy + select2)
     7.  Tables
     8.  Buttons
     9.  Badges + status pills
     10. Dropdowns, modals, alerts
     11. Utilities
     12. Chat surface
     13. Two-factor authentication
     14. Responsive (mobile + tablet)

   Breakpoints used throughout §14 — they match Bootstrap's so the utility
   classes already in the templates keep lining up with our own rules:
     ≤ 1199.98px  tablet and below — the sidebar becomes an overlay drawer
     ≤  991.98px  the top navbar collapses its search
     ≤  767.98px  phone — tables stack into cards, grids drop to one column
     ≤  575.98px  small phone — tighter padding
   ========================================================================== */

/* ── 1. Tokens ───────────────────────────────────────────────────────────── */
/* Palette is taken from the sign-in page (pages/sign-in-2fa.html) so the app
   and the login screen read as one product:
     #530e81  brand purple      — the sign-in button
     #59198e → #2e1679          — the panel gradient
     #7333f4  violet            — the "forgot password" link
     #d5abf1  light violet      — the particle accent
     #f7f5f5  warm off-white    — the sign-in page background            */
:root {
  /* App canvas. The sign-in page's #f7f5f5 is a warm grey that read as flat
     next to white cards; this carries a light violet tint from the brand so
     surfaces separate from the background. */
  --ds-bg: #f3f1f8;
  --ds-surface: #ffffff;
  --ds-surface-muted: #faf8fc;
  --ds-border: #e8e3ee;
  --ds-border-strong: #d3cadd;

  --ds-text: #1a1024;
  --ds-text-muted: #6b6377;
  --ds-text-subtle: #9b93a6;

  --ds-accent: #530e81;
  --ds-accent-hover: #420a68;
  --ds-accent-soft: #f4ecfb;
  --ds-accent-border: rgba(83, 14, 129, 0.18);
  --ds-accent-tint: #d5abf1;
  --ds-link: #7333f4;
  --ds-link-hover: #530e81;

  --ds-gradient: linear-gradient(180deg, #59198e 0%, #2e1679 100%);
  --ds-gradient-soft: linear-gradient(135deg, #f4ecfb 0%, #ede3f8 100%);

  --ds-success: #0a7d55;
  --ds-success-soft: #e9faf3;
  --ds-danger: #b42318;
  --ds-danger-soft: #fef3f2;
  --ds-warning: #b54708;
  --ds-warning-soft: #fffaeb;
  --ds-info: #2e1679;
  --ds-info-soft: #eeeafa;

  --ds-radius-sm: 8px;
  --ds-radius: 10px;
  --ds-radius-lg: 14px;

  --ds-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
  --ds-shadow: 0 1px 3px rgba(16, 24, 40, 0.1), 0 1px 2px rgba(16, 24, 40, 0.06);
  --ds-shadow-lg: 0 12px 16px -4px rgba(16, 24, 40, 0.08), 0 4px 6px -2px rgba(16, 24, 40, 0.03);

  --ds-sidebar-w: 14rem;
  --ds-sidebar-collapsed-w: 4.5rem;

  --ds-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* ── 2. Base / typography ────────────────────────────────────────────────── */
body.g-sidenav-show {
  background-color: var(--ds-bg) !important;
  font-family: var(--ds-font);
  color: var(--ds-text);
  font-size: 0.875rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.ds-scope h1,
.ds-scope h2,
.ds-scope h3,
.ds-scope h4,
.ds-scope h5,
.ds-scope h6 {
  color: var(--ds-text);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.ds-scope a {
  color: var(--ds-link);
  text-decoration: none;
}

.ds-scope a:hover {
  color: var(--ds-link-hover);
  text-decoration: underline;
}

.ds-text-muted {
  color: var(--ds-text-muted) !important;
}

/* ── 3. Layout shell + collapsible sidebar ───────────────────────────────── */
/* .ds-scope sits on <main> and scopes every component rule below, so pages
   inherit the system without each template opting in.
   .ds-page is the padded content wrapper used by redesigned pages. */

/* <main> carries Material's `max-height-vh-100 h-100`, which resolve to
   `max-height: 100vh !important` and `height: 100% !important` while overflow
   stays `visible`. Any page taller than the viewport is therefore CLIPPED with
   nothing to scroll: the document never grows past 100vh, so the window has no
   scrollbar, and <main> has no scrollbar either because it does not scroll.
   Everything below the fold is painted but unreachable by mouse or wheel —
   which is why long forms looked like their lower fields were "disabled" while
   Tab still reached them (focus scrolls an element into view internally,
   bypassing the missing scroll container).

   The fix is to let the shell grow with its content and let the page scroll
   normally. `min-height` keeps short pages full-height so the sidebar and
   background still fill the screen. */
.main-content.ds-scope {
  height: auto !important;
  max-height: none !important;
  min-height: 100vh;
}

.ds-page {
  padding: 1.5rem 1.5rem 3rem;
}

/* Keep the top navbar out of the form restyling — it has its own compact
   search control that should not become a full-width bordered input. */
.ds-scope .navbar-main .form-control,
.ds-scope .navbar-main input[type="text"] {
  border: 0 !important;
  box-shadow: none;
  background: transparent;
  width: auto;
}

/* Sidebar surface — flatter than the stock Material treatment.
   The background is pinned here because Material's mobile toggle adds and
   removes `.bg-white` as the drawer opens and closes; without this the
   sidebar ends up transparent after the first close. */
.sidenav.navbar-vertical {
  background-color: var(--ds-surface) !important;
  border: 1px solid var(--ds-border) !important;
  box-shadow: var(--ds-shadow-sm) !important;
  border-radius: var(--ds-radius-lg) !important;
}

.sidenav .navbar-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  white-space: nowrap;
}

.sidenav .navbar-nav .nav-link {
  border-radius: var(--ds-radius);
  margin: 0.125rem 0.75rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ds-text-muted) !important;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  transition: background-color 0.15s ease, color 0.15s ease;
}

/* Active item carries the sign-in panel gradient. */
.sidenav .navbar-nav .nav-link.active,
.sidenav .navbar-nav .nav-link.active.bg-gradient-dark {
  background: var(--ds-gradient) !important;
  color: #fff !important;
  font-weight: 600;
  box-shadow: 0 2px 6px rgba(46, 22, 121, 0.28);
}

.sidenav .navbar-nav .nav-link.active .material-symbols-rounded {
  opacity: 1 !important;
  color: #fff !important;
}

.sidenav .navbar-nav .nav-link:not(.active):hover {
  background-color: var(--ds-accent-soft);
  color: var(--ds-accent) !important;
}

.sidenav .navbar-nav .nav-link:not(.active):hover .material-symbols-rounded {
  color: var(--ds-accent) !important;
  opacity: 1 !important;
}

.sidenav .navbar-nav .nav-link .material-symbols-rounded {
  font-size: 1.25rem;
  flex-shrink: 0;
}

/* Toggle button that compresses / expands the sidebar. */
.ds-sidebar-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-sm);
  background: var(--ds-surface);
  color: var(--ds-text-muted);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.ds-sidebar-toggle:hover {
  background: var(--ds-surface-muted);
  color: var(--ds-text);
}

.ds-sidebar-toggle .material-symbols-rounded {
  font-size: 1.125rem;
  transition: transform 0.2s ease;
}

.ds-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding-right: 0.75rem;
}

/* The toggle is a desktop affordance; below 1200px the stock burger menu
   already slides the whole sidebar in and out. */
@media (min-width: 1200px) {
  .ds-sidebar-toggle {
    display: inline-flex;
  }

  body.ds-sidebar-collapsed .sidenav.navbar-vertical.navbar-expand-xs {
    max-width: var(--ds-sidebar-collapsed-w) !important;
    width: var(--ds-sidebar-collapsed-w) !important;
    overflow-x: hidden;
  }

  /* Stock Material expands the rail on hover; keep collapsed strictly
     collapsed so the layout never shifts under the pointer. */
  body.ds-sidebar-collapsed .sidenav.navbar-vertical:hover {
    max-width: var(--ds-sidebar-collapsed-w) !important;
  }

  body.ds-sidebar-collapsed .sidenav.fixed-start + .main-content {
    margin-left: calc(var(--ds-sidebar-collapsed-w) + 1rem) !important;
  }

  body.ds-sidebar-collapsed .sidenav .nav-link-text,
  body.ds-sidebar-collapsed .sidenav .navbar-brand span,
  body.ds-sidebar-collapsed .sidenav .navbar-heading,
  body.ds-sidebar-collapsed .sidenav h6 {
    display: none !important;
  }

  body.ds-sidebar-collapsed .sidenav .navbar-brand {
    justify-content: center;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }

  body.ds-sidebar-collapsed .sidenav .navbar-nav .nav-link {
    justify-content: center;
    margin-left: 0.5rem;
    margin-right: 0.5rem;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
  }

  body.ds-sidebar-collapsed .ds-sidebar-header {
    flex-direction: column;
    padding-right: 0;
    gap: 0.25rem;
  }

  body.ds-sidebar-collapsed .ds-sidebar-toggle .material-symbols-rounded {
    transform: rotate(180deg);
  }
}

/* ── 4. Page header ──────────────────────────────────────────────────────── */
.ds-page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.ds-page-title {
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.3;
  margin: 0;
  color: var(--ds-text);
  letter-spacing: -0.02em;
}

.ds-page-subtitle {
  font-size: 0.8125rem;
  color: var(--ds-text-muted);
  margin: 0.25rem 0 0;
}

.ds-page-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* ── 5. Cards / panels ───────────────────────────────────────────────────── */
.ds-card {
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-sm);
}

.ds-card + .ds-card {
  margin-top: 1.25rem;
}

.ds-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--ds-border);
  background: var(--ds-gradient-soft);
  border-radius: var(--ds-radius-lg) var(--ds-radius-lg) 0 0;
}

.ds-card-title {
  font-size: 0.9375rem;
  font-weight: 600;
  margin: 0;
  color: var(--ds-text);
}

.ds-card-subtitle {
  font-size: 0.8125rem;
  color: var(--ds-text-muted);
  margin: 0.125rem 0 0;
}

.ds-card-body {
  padding: 1.25rem;
}

.ds-card-body--flush {
  padding: 0;
}

.ds-card-footer {
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--ds-border);
  background: var(--ds-surface-muted);
  border-radius: 0 0 var(--ds-radius-lg) var(--ds-radius-lg);
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
}

/* Stat tiles */
.ds-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.ds-stat {
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-left: 3px solid var(--ds-accent);
  border-radius: var(--ds-radius-lg);
  padding: 1rem 1.25rem;
  box-shadow: var(--ds-shadow-sm);
}

.ds-stat-label {
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ds-text-subtle);
  margin: 0 0 0.375rem;
}

.ds-stat-value {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--ds-text);
  line-height: 1.2;
  margin: 0;
}

/* ── 6. Forms ────────────────────────────────────────────────────────────── */
/* Stepper — a multi-step form where every step is a panel of ONE form and only
   the last panel submits. Marks progress; it is not navigation. */
.ds-stepper {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  flex-wrap: wrap;
}

.ds-step {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--ds-text-subtle);
  padding: 0.375rem 0.75rem 0.375rem 0.375rem;
  border-radius: 999px;
  border: 1px solid transparent;
}

/* A completed step can be reopened, so it gets a pointer; the current and
   future ones are not clickable and must not pretend otherwise. */
.ds-step.is-done {
  color: var(--ds-text-muted);
  cursor: pointer;
}

.ds-step.is-active {
  color: var(--ds-accent);
  background: var(--ds-accent-soft);
  border-color: var(--ds-accent-border);
}

.ds-step-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ds-surface-muted);
  border: 1px solid var(--ds-border-strong);
  font-size: 0.75rem;
  font-weight: 600;
  flex-shrink: 0;
}

.ds-step.is-active .ds-step-marker {
  background: var(--ds-gradient);
  border-color: transparent;
  color: #fff;
}

.ds-step.is-done .ds-step-marker {
  background: var(--ds-success-soft);
  border-color: rgba(10, 125, 85, 0.25);
  color: var(--ds-success);
}

/* Connector between steps. Decorative, so it is hidden from assistive tech by
   being a pseudo-element rather than markup. */
.ds-step + .ds-step::before {
  content: "";
  width: 1.5rem;
  height: 1px;
  background: var(--ds-border-strong);
  margin-right: 0.25rem;
}

/* Panels are toggled by the page's own script; only one is ever shown. */
.ds-step-panel {
  display: none;
}

.ds-step-panel.is-active {
  display: block;
}

.ds-stepper-errors {
  margin-bottom: 1rem;
}

@media (max-width: 575.98px) {
  /* The labels are what wrap first and they are the least load-bearing part —
     the numbered markers still show position. */
  .ds-step-label {
    display: none;
  }

  .ds-step + .ds-step::before {
    width: 0.75rem;
  }
}

.ds-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1rem 1.25rem;
  align-items: start;
}

.ds-form-grid--single {
  grid-template-columns: minmax(0, 1fr);
}

/* Multi-line inputs get a row of their own — a textarea squeezed into one
   column beside a short text field reads as a mistake. Also applies to
   anything explicitly marked full width. */
.ds-form-grid > .ds-form-full,
.ds-form-grid > .form-group:has(textarea),
.ds-form-grid > .mb-3:has(textarea),
.ds-form-grid > div:has(> textarea),
.ds-form-grid > .form-group:has(.select2),
.ds-form-grid > .form-group:has(select[multiple]) {
  grid-column: 1 / -1;
}

.ds-form-section + .ds-form-section {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--ds-border);
}

.ds-form-section-title {
  font-size: 0.8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ds-text-subtle);
  margin: 0 0 1rem;
}

/* Material's stock inputs are underline-only and float their labels.  Reset
   them to bordered boxes with static labels — far easier to keep uniform, and
   it makes crispy's plain output look intentional. */
.ds-scope .form-group,
.ds-scope .mb-3 {
  margin-bottom: 1rem;
}

.ds-scope label,
.ds-scope .form-label,
.ds-scope .col-form-label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--ds-text);
  margin-bottom: 0.375rem;
  position: static;
  opacity: 1;
  pointer-events: auto;
}

/* Required marker. crispy emits `<span class="asteriskField">*</span>` inside
   the label and ships no colour for it, so it inherited the near-black label
   colour and read as punctuation rather than as "this field is required".
   `.text-danger` covers the hand-written labels that mark the asterisk
   themselves, so both sources land on the same red. */
.ds-scope .asteriskField,
.ds-scope label .text-danger {
  /* Material sets `.text-danger` with !important, so matching it takes one
     too — otherwise a form carrying both kinds of label (company user create)
     shows two different reds side by side. */
  color: var(--ds-danger) !important;
  font-weight: 600;
  margin-left: 0.125rem;
}

/* crispy renders the asterisk as the label's last child with a leading space;
   without this the marker can wrap onto a line of its own. */
.ds-scope label .asteriskField {
  white-space: nowrap;
}

.ds-scope .form-control,
.ds-scope .form-select,
.ds-scope textarea.form-control,
.ds-scope input[type="text"],
.ds-scope input[type="email"],
.ds-scope input[type="password"],
.ds-scope input[type="number"],
.ds-scope input[type="tel"],
.ds-scope input[type="url"],
.ds-scope input[type="date"],
.ds-scope input[type="datetime-local"],
.ds-scope select,
.ds-scope textarea {
  display: block;
  width: 100%;
  padding: 0.5rem 0.75rem !important;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--ds-text);
  background-color: var(--ds-surface);
  background-image: none;
  border: 1px solid var(--ds-border-strong) !important;
  border-radius: var(--ds-radius) !important;
  box-shadow: var(--ds-shadow-sm);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  height: auto;
}

.ds-scope select,
.ds-scope .form-select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23667085' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m4 6 4 4 4-4'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 0.625rem center;
  background-size: 16px 16px;
  padding-right: 2rem !important;
}

.ds-scope .form-control:focus,
.ds-scope .form-select:focus,
.ds-scope select:focus,
.ds-scope textarea:focus,
.ds-scope input:focus {
  outline: 0;
  border-color: var(--ds-accent) !important;
  box-shadow: 0 0 0 3px rgba(83, 14, 129, 0.14);
}

.ds-scope .form-control::placeholder,
.ds-scope textarea::placeholder {
  color: var(--ds-text-subtle);
  opacity: 1;
}

.ds-scope textarea,
.ds-scope textarea.form-control {
  min-height: 96px;
  resize: vertical;
}

/* Bootstrap's .bg-light sets background with !important, so a stray utility
   class on a widget would otherwise repaint the field grey. Inputs are white;
   only readonly/disabled fields are tinted (rule below). */
.ds-scope .form-control.bg-light,
.ds-scope .form-select.bg-light,
.ds-scope input.bg-light,
.ds-scope textarea.bg-light,
.ds-scope select.bg-light {
  background-color: var(--ds-surface) !important;
}

.ds-scope .form-control[readonly],
.ds-scope .form-control:disabled {
  background-color: var(--ds-surface-muted);
  color: var(--ds-text-muted);
  cursor: not-allowed;
}

/* Help text + validation */
.ds-scope .form-text,
.ds-scope .helptext,
.ds-scope small.text-muted {
  display: block;
  font-size: 0.75rem;
  color: var(--ds-text-muted);
  margin-top: 0.3125rem;
}

.ds-scope .invalid-feedback,
.ds-scope .errorlist,
.ds-scope .is-invalid ~ .invalid-feedback {
  display: block;
  font-size: 0.75rem;
  color: var(--ds-danger);
  margin-top: 0.3125rem;
  list-style: none;
  padding: 0;
}

.ds-scope .form-control.is-invalid,
.ds-scope .is-invalid .form-control {
  border-color: var(--ds-danger) !important;
}

/* Checkboxes / radios */
.ds-scope .form-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-left: 0;
  margin-bottom: 0.5rem;
  min-height: auto;
}

.ds-scope .form-check-input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  flex-shrink: 0;
  border: 1px solid var(--ds-border-strong);
  border-radius: 4px;
  appearance: none;
  background: var(--ds-surface);
  cursor: pointer;
  box-shadow: none;
}

.ds-scope .form-check-input[type="radio"] {
  border-radius: 50%;
}

.ds-scope .form-check-input:checked {
  background-color: var(--ds-accent);
  border-color: var(--ds-accent);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m3 8 3.5 3.5L13 5'/%3e%3c/svg%3e");
  background-size: 12px;
  background-position: center;
  background-repeat: no-repeat;
}

.ds-scope .form-check-label {
  margin: 0;
  font-weight: 400;
  font-size: 0.875rem;
  cursor: pointer;
}

/* File inputs */
.ds-scope input[type="file"] {
  padding: 0.4375rem 0.75rem !important;
  font-size: 0.8125rem;
}

/* Select2 — match the native control so mixed forms stay uniform. */
.ds-scope .select2-container .select2-selection--single,
.ds-scope .select2-container .select2-selection--multiple {
  min-height: 38px;
  border: 1px solid var(--ds-border-strong) !important;
  border-radius: var(--ds-radius) !important;
  background: var(--ds-surface);
  box-shadow: var(--ds-shadow-sm);
  padding: 0.1875rem 0.375rem;
}

.ds-scope .select2-container--default .select2-selection--single .select2-selection__rendered {
  line-height: 30px;
  color: var(--ds-text);
  padding-left: 0.375rem;
}

.ds-scope .select2-container--default .select2-selection--single .select2-selection__arrow {
  height: 36px;
}

.ds-scope .select2-container--default.select2-container--focus .select2-selection--multiple,
.ds-scope .select2-container--default.select2-container--open .select2-selection--single {
  border-color: var(--ds-accent) !important;
  box-shadow: 0 0 0 3px rgba(83, 14, 129, 0.14);
}

.ds-scope .select2-container--default .select2-selection--multiple .select2-selection__choice {
  background-color: var(--ds-accent-soft);
  border: 1px solid var(--ds-accent-border);
  color: var(--ds-accent);
  border-radius: var(--ds-radius-sm);
  padding: 0.125rem 0.5rem;
  font-size: 0.8125rem;
}

/* ── 7. Tables ───────────────────────────────────────────────────────────── */
.ds-table-wrap {
  width: 100%;
  overflow-x: auto;
}

.ds-table {
  width: 100%;
  margin: 0;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 0.875rem;
  color: var(--ds-text);
}

.ds-table thead th {
  background: var(--ds-gradient-soft);
  border-bottom: 1px solid var(--ds-border);
  padding: 0.625rem 1rem;
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ds-text-subtle);
  text-align: left;
  white-space: nowrap;
  vertical-align: middle;
}

.ds-table tbody td {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--ds-border);
  vertical-align: middle;
  color: var(--ds-text);
}

.ds-table tbody tr:last-child td {
  border-bottom: 0;
}

.ds-table tbody tr:hover {
  background-color: var(--ds-surface-muted);
}

.ds-table .ds-table-primary {
  font-weight: 500;
  color: var(--ds-text);
}

.ds-table a.ds-table-primary {
  color: var(--ds-accent);
}

.ds-table a.ds-table-primary:hover {
  color: var(--ds-link);
}

.ds-table .ds-table-secondary {
  color: var(--ds-text-muted);
  font-size: 0.8125rem;
}

.ds-table td.ds-actions,
.ds-table th.ds-actions {
  text-align: right;
  width: 1%;
  white-space: nowrap;
}

.ds-table-empty {
  padding: 3rem 1.25rem;
  text-align: center;
  color: var(--ds-text-muted);
}

.ds-table-empty .material-symbols-rounded {
  font-size: 2.5rem;
  color: var(--ds-accent-tint);
  display: block;
  margin-bottom: 0.5rem;
}

/* ── 8. Buttons ──────────────────────────────────────────────────────────── */
.ds-scope .btn,
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  font-family: var(--ds-font);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.5;
  padding: 0.5rem 0.875rem;
  border-radius: var(--ds-radius);
  border: 1px solid transparent;
  box-shadow: var(--ds-shadow-sm);
  text-transform: none;
  letter-spacing: normal;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.ds-scope .btn:hover,
.ds-btn:hover {
  transform: none;
  box-shadow: var(--ds-shadow-sm);
  text-decoration: none;
}

.ds-scope .btn-primary,
.ds-scope .btn-info,
.ds-scope .bg-gradient-dark.btn,
.ds-btn-primary {
  background-color: var(--ds-accent) !important;
  background-image: none !important;
  border-color: var(--ds-accent) !important;
  color: #fff !important;
  box-shadow: 0 0.2px 0 0 rgba(0, 0, 0, 0.5) inset, var(--ds-shadow-sm);
}

.ds-scope .btn-primary:hover,
.ds-scope .btn-info:hover,
.ds-btn-primary:hover {
  background-color: var(--ds-accent-hover) !important;
  border-color: var(--ds-accent-hover) !important;
  color: #fff !important;
}

.ds-scope .btn-secondary,
.ds-scope .btn-light,
.ds-btn-secondary {
  background-color: var(--ds-surface) !important;
  background-image: none !important;
  border-color: var(--ds-border-strong) !important;
  color: var(--ds-text) !important;
}

.ds-scope .btn-secondary:hover,
.ds-scope .btn-light:hover,
.ds-btn-secondary:hover {
  background-color: var(--ds-surface-muted) !important;
  color: var(--ds-text) !important;
}

.ds-scope .btn-danger,
.ds-btn-danger {
  background-color: var(--ds-danger) !important;
  background-image: none !important;
  border-color: var(--ds-danger) !important;
  color: #fff !important;
}

.ds-scope .btn-success {
  background-color: var(--ds-success) !important;
  background-image: none !important;
  border-color: var(--ds-success) !important;
  color: #fff !important;
}

.ds-scope .btn-sm,
.ds-btn-sm {
  padding: 0.3125rem 0.625rem;
  font-size: 0.75rem;
}

.ds-scope .btn .material-symbols-rounded,
.ds-btn .material-symbols-rounded {
  font-size: 1.125rem;
}

/* Icon-only action button (row menus) */
.ds-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-sm);
  background: var(--ds-surface);
  color: var(--ds-text-muted);
  cursor: pointer;
  box-shadow: none;
}

.ds-icon-btn:hover {
  background: var(--ds-surface-muted);
  color: var(--ds-text);
}

.ds-icon-btn::after {
  display: none !important;
}

/* ── 9. Badges + status pills ────────────────────────────────────────────── */
.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.5;
  border: 1px solid transparent;
  white-space: nowrap;
}

.ds-badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.ds-badge--neutral {
  background: var(--ds-surface-muted);
  color: var(--ds-text-muted);
  border-color: var(--ds-border);
}

.ds-badge--success {
  background: var(--ds-success-soft);
  color: var(--ds-success);
  border-color: rgba(10, 125, 85, 0.18);
}

.ds-badge--danger {
  background: var(--ds-danger-soft);
  color: var(--ds-danger);
  border-color: rgba(180, 35, 24, 0.16);
}

.ds-badge--warning {
  background: var(--ds-warning-soft);
  color: var(--ds-warning);
  border-color: rgba(181, 71, 8, 0.16);
}

.ds-badge--accent {
  background: var(--ds-accent-soft);
  color: var(--ds-accent);
  border-color: var(--ds-accent-border);
}

.ds-badge--plain::before {
  display: none;
}

/* Monospace code chip (client ids, keys) */
.ds-code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 0.75rem;
  background: var(--ds-surface-muted);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-sm);
  padding: 0.125rem 0.375rem;
  color: var(--ds-text);
}

/* ── 10. Dropdowns, modals, alerts ───────────────────────────────────────── */
/* custom.css force-shows every .dropdown-menu; restore Bootstrap's contract so
   menus appear only when toggled, while keeping them above table overflow. */
.ds-scope .dropdown-menu,
.sidenav .dropdown-menu,
.navbar .dropdown-menu {
  display: none;
  visibility: visible;
  /* No transform/position override here — Popper owns both.  Row menus are
     opened with the fixed strategy (see design-system.js) so they escape the
     table's scroll container instead of being trapped inside it. */
  z-index: 1056;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  box-shadow: var(--ds-shadow-lg);
  padding: 0.25rem;
  min-width: 11rem;
  background: var(--ds-surface);
}

.ds-scope .dropdown-menu.show,
.sidenav .dropdown-menu.show,
.navbar .dropdown-menu.show {
  display: block;
}

/* Row menus are moved to <body> while open (see design-system.js) so no
   scrolling ancestor can clip them. Outside .ds-scope they need the same
   styling, and a z-index above the sidebar (9999) and modals. */
.dropdown-menu[data-ds-portal-id] {
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  box-shadow: var(--ds-shadow-lg);
  padding: 0.25rem;
  min-width: 11rem;
  background: var(--ds-surface);
  z-index: 10000;
}

.dropdown-menu[data-ds-portal-id] .dropdown-item {
  border-radius: var(--ds-radius-sm);
  padding: 0.4375rem 0.625rem;
  font-size: 0.8125rem;
  color: var(--ds-text);
  display: block;
  width: 100%;
  clear: both;
  font-weight: 400;
  text-align: inherit;
  white-space: nowrap;
  background-color: transparent;
  border: 0;
  text-decoration: none;
}

.dropdown-menu[data-ds-portal-id] .dropdown-item:hover,
.dropdown-menu[data-ds-portal-id] .dropdown-item:focus {
  background-color: var(--ds-surface-muted);
  color: var(--ds-text);
  text-decoration: none;
}

.dropdown-menu[data-ds-portal-id] .dropdown-item--danger,
.dropdown-menu[data-ds-portal-id] .dropdown-item.text-danger {
  color: var(--ds-danger) !important;
}

.dropdown-menu[data-ds-portal-id] .dropdown-item--danger:hover {
  background-color: var(--ds-danger-soft);
}

.dropdown-menu[data-ds-portal-id] .dropdown-divider {
  margin: 0.25rem 0;
  border-top: 1px solid var(--ds-border);
}

.ds-scope .dropdown-item {
  border-radius: var(--ds-radius-sm);
  padding: 0.4375rem 0.625rem;
  font-size: 0.8125rem;
  color: var(--ds-text);
}

.ds-scope .dropdown-item:hover,
.ds-scope .dropdown-item:focus {
  background-color: var(--ds-surface-muted);
  color: var(--ds-text);
  text-decoration: none;
}

.ds-scope .dropdown-item--danger,
.ds-scope .dropdown-item.text-danger {
  color: var(--ds-danger) !important;
}

.ds-scope .dropdown-item--danger:hover {
  background-color: var(--ds-danger-soft);
}

.ds-scope .dropdown-divider {
  margin: 0.25rem 0;
  border-color: var(--ds-border);
}

/* Modals */
.modal-content {
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-lg);
}

.modal-header {
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--ds-border);
  align-items: center;
}

.modal-title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ds-text);
}

.modal-body {
  padding: 1.25rem;
  font-size: 0.875rem;
}

.modal-footer {
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--ds-border);
  background: var(--ds-surface-muted);
  border-radius: 0 0 var(--ds-radius-lg) var(--ds-radius-lg);
}

/* Alerts / flash messages */
.flashmessage-container .alert {
  border-radius: var(--ds-radius);
  border: 1px solid var(--ds-border);
  box-shadow: var(--ds-shadow-lg);
  font-size: 0.8125rem;
  padding: 0.75rem 1rem;
  background-image: none;
}

.flashmessage-container .alert-success {
  background-color: var(--ds-success-soft);
  color: var(--ds-success);
  border-color: rgba(10, 125, 85, 0.2);
}

.flashmessage-container .alert-error,
.flashmessage-container .alert-danger {
  background-color: var(--ds-danger-soft);
  color: var(--ds-danger);
  border-color: rgba(180, 35, 24, 0.2);
}

.flashmessage-container .alert-warning {
  background-color: var(--ds-warning-soft);
  color: var(--ds-warning);
  border-color: rgba(181, 71, 8, 0.2);
}

.flashmessage-container .alert-info {
  background-color: var(--ds-accent-soft);
  color: var(--ds-accent);
  border-color: var(--ds-accent-border);
}

/* Breadcrumb bar */
.navbar-main .breadcrumb-item,
.navbar-main .breadcrumb-item a {
  font-size: 0.8125rem;
  color: var(--ds-text-muted) !important;
}

.navbar-main .breadcrumb-item.active {
  color: var(--ds-accent) !important;
  font-weight: 600;
}

/* ── 11. Utilities ───────────────────────────────────────────────────────── */
.ds-stack {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.ds-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ds-row--between {
  justify-content: space-between;
}

.ds-truncate {
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ds-wrap {
  white-space: normal;
  word-break: break-word;
  max-width: 420px;
}

.ds-empty {
  padding: 3rem 1.25rem;
  text-align: center;
  color: var(--ds-text-muted);
}

.ds-empty-title {
  font-weight: 600;
  color: var(--ds-text);
  margin: 0 0 0.25rem;
}

.ds-divider {
  height: 1px;
  background: var(--ds-border);
  border: 0;
  margin: 1.25rem 0;
}

/* ── 12. Chat surface ────────────────────────────────────────────────────── */
/* Shared by the Campaign Bot page and the thread conversation view so both
   read as the same product. Outbound/assistant messages sit right in brand
   purple; inbound/contact messages sit left on a white card. */
.ds-chat {
  display: flex;
  flex-direction: column;
  height: 65vh;
  min-height: 420px;
}

.ds-chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 1.5rem;
  background: var(--ds-bg);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.ds-chat-message {
  display: flex;
  max-width: 75%;
}

.ds-chat-message--out {
  align-self: flex-end;
  justify-content: flex-end;
}

.ds-chat-message--in {
  align-self: flex-start;
}

.ds-chat-bubble {
  padding: 0.75rem 1rem;
  border-radius: var(--ds-radius-lg);
  font-size: 0.875rem;
  line-height: 1.5;
  box-shadow: var(--ds-shadow-sm);
  white-space: pre-wrap;
  word-break: break-word;
}

.ds-chat-message--out .ds-chat-bubble {
  background: var(--ds-accent);
  color: #fff;
  border-bottom-right-radius: 4px;
}

.ds-chat-message--in .ds-chat-bubble {
  background: var(--ds-surface);
  color: var(--ds-text);
  border: 1px solid var(--ds-border);
  border-bottom-left-radius: 4px;
}

.ds-chat-bubble img {
  max-width: 220px;
  border-radius: var(--ds-radius);
  margin-top: 0.5rem;
  display: block;
}

.ds-chat-meta {
  font-size: 0.6875rem;
  color: var(--ds-text-subtle);
  margin-top: 0.25rem;
}

.ds-chat-message--out .ds-chat-meta {
  text-align: right;
}

.ds-chat-empty {
  margin: auto;
  text-align: center;
  color: var(--ds-text-muted);
}

.ds-chat-empty .material-symbols-rounded {
  font-size: 2.5rem;
  color: var(--ds-accent-tint);
  display: block;
  margin-bottom: 0.75rem;
}

.ds-chat-input {
  display: flex;
  gap: 0.75rem;
  align-items: flex-end;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--ds-border);
  background: var(--ds-surface);
}

.ds-chat-input textarea {
  flex: 1;
  resize: none;
  border: 1px solid var(--ds-border-strong) !important;
  border-radius: var(--ds-radius) !important;
  padding: 0.65rem 0.9rem !important;
  font-size: 0.875rem;
  outline: none;
  min-height: 42px;
  max-height: 160px;
}

.ds-chat-input textarea:focus {
  border-color: var(--ds-accent) !important;
  box-shadow: 0 0 0 3px rgba(83, 14, 129, 0.14);
}

.ds-chat-status {
  font-size: 0.8rem;
  color: var(--ds-text-muted);
  padding: 0 1.5rem 0.5rem;
  display: none;
}

.ds-chat-note {
  font-size: 0.75rem;
  color: var(--ds-text-muted);
  padding: 0 1.5rem 1rem;
}

/* Microphone button — pulses while recording so the state is unmistakable. */
.ds-mic-btn.is-recording {
  background-color: var(--ds-danger) !important;
  border-color: var(--ds-danger) !important;
  color: #fff !important;
  animation: ds-mic-pulse 1.4s ease-in-out infinite;
}

@keyframes ds-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(180, 35, 24, 0.5); }
  50% { box-shadow: 0 0 0 7px rgba(180, 35, 24, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .ds-mic-btn.is-recording {
    animation: none;
  }
}

/* ---------------------------------------------------------------------------
   13. TWO-FACTOR AUTHENTICATION
   The 2FA wizard steps come from django-two-factor-auth, so their markup is
   not ours to change; these rules make the generated fields sit correctly in
   the design system.
   ------------------------------------------------------------------------ */

/* Method picker ("token generator" / "text message" / "email"): the library
   emits a plain radio list, which we present as selectable rows. */
.ds-wizard-choice > div,
.ds-wizard-choice li {
  list-style: none;
  margin: 0 0 8px;
}

.ds-wizard-choice label {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  margin: 0;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  background: var(--ds-surface);
  font-weight: 500;
  color: var(--ds-text);
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.ds-wizard-choice > label {
  /* The field's own <label> is a heading, not an option. */
  display: block;
  padding: 0;
  margin-bottom: 8px;
  border: 0;
  background: none;
  font-size: 0.85rem;
  color: var(--ds-text);
  cursor: default;
}

.ds-wizard-choice label:has(input:checked) {
  border-color: var(--ds-accent);
  background: var(--ds-accent-soft);
  color: var(--ds-accent);
}

.ds-wizard-choice input[type="radio"] {
  accent-color: var(--ds-accent);
  width: 16px;
  height: 16px;
  margin: 0;
  flex: 0 0 auto;
}

/* Token entry: a short, centred, monospaced field reads as a code box. */
.ds-gate-body input[name$="otp_token"],
.ds-gate-body input[name$="token"] {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.15rem;
  letter-spacing: 0.35em;
  text-align: center;
}

/* ---------------------------------------------------------------------------
   14. RESPONSIVE
   Everything below narrows the shell for touch screens. The rules live here
   rather than in each template so a page only has to use the design system's
   classes to be usable on a phone.
   ------------------------------------------------------------------------ */

/* ── 14.1 Shell guards ───────────────────────────────────────────────────── */
/* A single wide element (a long client id, an unwrapped table) must not be
   able to push the whole document sideways. `clip` rather than `hidden` so
   sticky/fixed descendants keep working. */
.main-content {
  max-width: 100%;
  overflow-x: clip;
}

.ds-scope img,
.ds-scope video,
.ds-scope canvas,
.ds-scope iframe {
  max-width: 100%;
}

.ds-scope pre,
.ds-scope code,
.ds-code {
  overflow-wrap: anywhere;
}

/* ── 14.2 Top navbar ─────────────────────────────────────────────────────── */
.navbar-main .container-fluid {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 0.5rem;
}

.ds-nav-left {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.ds-nav-actions {
  display: flex;
  align-items: center;
  gap: 0.125rem;
  margin-left: auto;
  flex-shrink: 0;
}

/* Drawer trigger. Hidden on desktop, where the sidebar is always docked. */
.ds-nav-burger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  background: var(--ds-surface);
  color: var(--ds-text);
  cursor: pointer;
}

.ds-nav-burger:hover {
  background: var(--ds-surface-muted);
}

.ds-nav-burger .material-symbols-rounded {
  font-size: 1.375rem;
}

@media (max-width: 767.98px) {
  .navbar-main {
    margin-left: 0.75rem !important;
    margin-right: 0.75rem !important;
  }

  .navbar-main .container-fluid {
    padding-left: 0 !important;
    padding-right: 0 !important;
  }

  /* Four icons plus a burger plus a breadcrumb do not fit at 390px, so the
     breadcrumb gives up its width first. */
  .navbar-main .breadcrumb {
    flex-wrap: nowrap;
    min-width: 0;
    overflow: hidden;
  }

  .navbar-main .breadcrumb-item.active {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .ds-nav-actions .nav-item {
    padding-left: 0.4375rem !important;
    padding-right: 0.4375rem !important;
  }

  .ds-nav-actions .nav-link .material-symbols-rounded {
    font-size: 1.375rem;
  }
}

@media (max-width: 419.98px) {
  /* Only the current page matters once space runs out — and with the "Home"
     crumb gone, its separator would be left dangling. */
  .navbar-main .breadcrumb-item:not(.active) {
    display: none;
  }

  .navbar-main .breadcrumb-item.active::before {
    display: none;
  }
}

/* ── 14.3 Sidebar drawer (tablet and below) ──────────────────────────────── */
/* Material already slides the sidebar off-canvas under 1200px and back in on
   `g-sidenav-pinned`; what it never had was a backdrop, an escape route, or a
   scroll lock — so the drawer felt broken on a phone. */
/* Stacking. Docked on desktop the sidebar has to stay under modals (1055) and
   the portalled row menus (10000); as a drawer it has to rise above its own
   backdrop. Material ships z-index 9999, and the template used to pin it to 1
   inline — which left the backdrop painting over the open drawer and
   swallowing every tap on it. Both cases are handled here instead. */
.sidenav.navbar-vertical {
  z-index: 1;
}

@media (max-width: 1199.98px) {
  .sidenav.navbar-vertical {
    z-index: 9999;
  }
}

.ds-sidebar-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(26, 16, 36, 0.45);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s ease;
  z-index: 9998;
  border: 0;
  padding: 0;
}

body.g-sidenav-pinned .ds-sidebar-backdrop {
  opacity: 1;
  visibility: visible;
}

.ds-sidebar-close {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  flex-shrink: 0;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-sm);
  background: var(--ds-surface);
  color: var(--ds-text-muted);
  cursor: pointer;
}

.ds-sidebar-close:hover {
  background: var(--ds-surface-muted);
  color: var(--ds-text);
}

@media (min-width: 1200px) {
  .ds-sidebar-backdrop {
    display: none;
  }
}

@media (max-width: 1199.98px) {
  .ds-nav-burger {
    display: inline-flex;
  }

  .ds-sidebar-close {
    display: inline-flex;
  }

  /* A short screen must still be able to reach the last nav item. */
  .sidenav.navbar-vertical {
    max-height: calc(100dvh - 1rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* No background scrolling behind the open drawer. */
  body.g-sidenav-pinned {
    overflow: hidden;
  }

  /* Larger hit areas — these are being tapped, not clicked. */
  .sidenav .navbar-nav .nav-link {
    padding: 0.6875rem 0.75rem;
  }
}

/* ── 14.4 Page shell ─────────────────────────────────────────────────────── */
@media (max-width: 991.98px) {
  /* The navbar search is a desktop convenience; on a phone it competes with
     the breadcrumb for the little width there is, and Material stacks it into
     a second row that pushes the account icons off the edge.
     The selector deliberately mirrors material-dashboard.css's own
     `.g-sidenav-show .navbar:not(.sidenav).navbar-main .navbar-collapse`,
     which sets `display: flex !important` — matching its specificity is what
     lets this rule win on source order. */
  .g-sidenav-show .navbar:not(.sidenav).navbar-main .navbar-collapse {
    display: none !important;
  }

  .ds-page {
    padding: 1.25rem 1.25rem 2.5rem;
  }

  /* Legacy pages that never moved onto .ds-page still need side padding. */
  .main-content > .container,
  .main-content > .container-fluid,
  .ds-scope .container,
  .ds-scope .container-fluid {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  /* custom.css forces `overflow: visible` on .table-responsive so row menus
     can escape it. On a narrow screen that turns a wide table into a page
     that scrolls sideways, so scrolling is restored here — design-system.js
     lifts those menus out to <body>, which keeps them visible either way. */
  .table-responsive {
    overflow-x: auto !important;
    -webkit-overflow-scrolling: touch;
  }
}

@media (max-width: 767.98px) {
  .ds-page {
    padding: 1rem 1rem 2rem;
  }

  .ds-page-header {
    flex-direction: column;
    align-items: stretch;
    gap: 0.875rem;
    margin-bottom: 1rem;
  }

  .ds-page-title {
    font-size: 1.125rem;
  }

  /* Action rows go two-up; a lone trailing button takes the full width. */
  .ds-page-actions {
    width: 100%;
    gap: 0.5rem;
  }

  .ds-page-actions > .btn,
  .ds-page-actions > .ds-btn,
  .ds-page-actions > form {
    flex: 1 1 calc(50% - 0.25rem);
    min-width: calc(50% - 0.25rem);
  }

  .ds-card {
    border-radius: var(--ds-radius);
  }

  .ds-card-header,
  .ds-card-footer {
    padding: 0.875rem 1rem;
    border-radius: 0;
  }

  .ds-card-header {
    border-radius: var(--ds-radius) var(--ds-radius) 0 0;
  }

  .ds-card-footer {
    border-radius: 0 0 var(--ds-radius) var(--ds-radius);
  }

  .ds-card-footer .btn {
    flex: 1 1 auto;
  }

  .ds-card-body {
    padding: 1rem;
  }

  .ds-stat-grid {
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 0.75rem;
  }

  .ds-stat {
    padding: 0.875rem 1rem;
  }

  .ds-stat-value {
    font-size: 1.25rem;
  }

  .flashmessage-container {
    left: 0.75rem;
    right: 0.75rem;
    top: 4.5rem;
  }
}

@media (max-width: 575.98px) {
  .ds-page {
    padding: 0.75rem 0.75rem 2rem;
  }

  .ds-card-body {
    padding: 0.875rem;
  }
}

/* ── 14.5 Forms ──────────────────────────────────────────────────────────── */
/* iOS zooms the viewport when a focused field is under 16px and never zooms
   back out. It does this on iPad as well as iPhone, so the floor covers every
   touch-width layout — not just the phone breakpoint below. */
@media (max-width: 991.98px) {
  .ds-scope .form-control,
  .ds-scope .form-select,
  .ds-scope textarea.form-control,
  .ds-scope input[type="text"],
  .ds-scope input[type="email"],
  .ds-scope input[type="password"],
  .ds-scope input[type="number"],
  .ds-scope input[type="tel"],
  .ds-scope input[type="url"],
  .ds-scope input[type="date"],
  .ds-scope input[type="datetime-local"],
  .ds-scope input[type="search"],
  .ds-scope select,
  .ds-scope textarea,
  .ds-chat-input textarea,
  .ds-gate-body input,
  .ds-gate-body select,
  .ds-gate-body textarea {
    font-size: 16px;
  }

  .ds-scope .form-control,
  .ds-scope .form-select,
  .ds-scope select,
  .ds-scope input,
  .ds-scope textarea {
    padding: 0.625rem 0.75rem !important;
  }

  /* The password-reset / sign-up pages run on Material's stock auth layout,
     which has no .ds-scope wrapper — they need the same 16px floor or iOS
     zooms into the form and never zooms back out.  Keyed off the *absence*
     of the app shell's body class so the rule cannot reach into the dashboard
     navbar's compact search field. */
  body:not(.g-sidenav-show) input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]),
  body:not(.g-sidenav-show) select,
  body:not(.g-sidenav-show) textarea {
    font-size: 16px;
  }

  /* Comfortable touch targets — 40px is the practical floor. */
  .ds-scope .btn,
  .ds-btn {
    padding: 0.625rem 1rem;
    font-size: 0.875rem;
    min-height: 40px;
  }

  .ds-scope .btn-sm,
  .ds-btn-sm {
    padding: 0.4375rem 0.75rem;
    min-height: 34px;
  }

  .ds-icon-btn {
    width: 38px;
    height: 38px;
  }

  .ds-scope .form-check-input {
    width: 1.125rem;
    height: 1.125rem;
  }

  .ds-scope .select2-container .select2-selection--single,
  .ds-scope .select2-container .select2-selection--multiple {
    min-height: 44px;
  }

  .ds-scope .select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: 36px;
    font-size: 16px;
  }

  .ds-scope .select2-container--default .select2-selection--single .select2-selection__arrow {
    height: 42px;
  }
}

@media (max-width: 767.98px) {
  /* auto-fit already collapses at this width; stating it keeps a nested or
     unusually wide card from squeezing two columns onto a phone. */
  .ds-form-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.875rem;
  }
}

/* ── 14.6 Tables become cards ────────────────────────────────────────────── */
/* An eight-column table cannot be read on a phone, and sideways scrolling
   pushes the row menu — the column people actually need — off the right edge.
   Below 992px each row is re-laid out as a card: the first cell is the title,
   the rest become label/value lines, and the row menu pins to the top-right.
   992 rather than 768 because a portrait tablet is no better off scrolling a
   wide table than a phone is. Add .ds-table--no-stack to keep a table
   scrolling horizontally instead (right for dense numeric grids).
   The data-label values are injected from <thead> by design-system.js. */
@media (max-width: 991.98px) {
  /* Truncation caps keep desktop columns tidy; on a stacked card the text
     should simply wrap. */
  .ds-truncate,
  .ds-wrap {
    max-width: none;
    white-space: normal;
    overflow: visible;
    word-break: break-word;
  }

  .ds-table-wrap:not(:has(.ds-table--no-stack)) {
    overflow-x: visible;
  }

  .ds-table:not(.ds-table--no-stack),
  .ds-table:not(.ds-table--no-stack) tbody,
  .ds-table:not(.ds-table--no-stack) tbody tr,
  .ds-table:not(.ds-table--no-stack) tbody td {
    display: block;
    width: 100%;
  }

  .ds-table:not(.ds-table--no-stack) thead {
    display: none;
  }

  .ds-table:not(.ds-table--no-stack) tbody tr {
    position: relative;
    padding: 0.875rem 3.25rem 0.875rem 1rem;
    border-bottom: 1px solid var(--ds-border);
  }

  .ds-table:not(.ds-table--no-stack) tbody tr:last-child {
    border-bottom: 0;
  }

  /* Centred, not baseline-aligned: a cell can hold a QR thumbnail or a badge
     that is taller than the label, and baseline alignment drops the label to
     the bottom of it. */
  .ds-table:not(.ds-table--no-stack) tbody td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.25rem 0;
    border: 0;
    text-align: right;
    min-width: 0;
  }

  .ds-table:not(.ds-table--no-stack) tbody td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto;
    text-align: left;
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ds-text-subtle);
  }

  /* First cell is the card's heading, so it keeps the full width and drops
     its column label. */
  .ds-table:not(.ds-table--no-stack) tbody td:first-child {
    display: block;
    text-align: left;
    font-size: 0.9375rem;
    padding-bottom: 0.375rem;
  }

  .ds-table:not(.ds-table--no-stack) tbody td:first-child::before {
    display: none;
  }

  /* Row menu floats over the card instead of taking a line of its own. */
  .ds-table:not(.ds-table--no-stack) tbody td.ds-actions {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    width: auto;
    padding: 0;
  }

  .ds-table:not(.ds-table--no-stack) tbody td.ds-actions::before {
    display: none;
  }

  /* The empty state spans the table and has no label of its own. */
  .ds-table:not(.ds-table--no-stack) tbody tr:has(> td[colspan]) {
    padding: 0;
  }

  .ds-table:not(.ds-table--no-stack) tbody td[colspan] {
    display: block;
    text-align: center;
    padding: 0;
  }

  .ds-table:not(.ds-table--no-stack) tbody td[colspan]::before {
    display: none;
  }

  .ds-table-empty {
    padding: 2rem 1rem;
  }

  /* Values that are themselves layouts (a QR thumbnail beside a download
     button) should hug the right edge like any other value. */
  .ds-table:not(.ds-table--no-stack) tbody td .ds-row {
    justify-content: flex-end;
  }
}

/* ── 14.7 Modals ─────────────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  .modal-dialog {
    margin: 0.5rem;
    max-width: calc(100% - 1rem);
  }

  /* Keep a tall form inside the viewport and scroll the body, so the footer
     buttons are always reachable. */
  .modal-content {
    max-height: calc(100dvh - 1rem);
  }

  .modal-body {
    overflow-y: auto;
    padding: 1rem;
  }

  .modal-header,
  .modal-footer {
    padding: 0.875rem 1rem;
  }

  .modal-footer {
    gap: 0.5rem;
  }

  .modal-footer > .btn {
    flex: 1 1 auto;
    margin: 0;
  }
}

/* ── 14.8 Chat ───────────────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  /* 420px of fixed minimum pushed the composer below the fold on a phone;
     a viewport-relative height keeps the input visible without scrolling. */
  .ds-chat {
    height: 62dvh;
    min-height: 320px;
  }

  .ds-chat-messages {
    padding: 1rem;
    gap: 0.75rem;
  }

  .ds-chat-message {
    max-width: 88%;
  }

  .ds-chat-input {
    padding: 0.75rem;
    gap: 0.5rem;
  }

  .ds-chat-input .btn {
    padding: 0.625rem 0.75rem;
  }

  .ds-chat-status,
  .ds-chat-note {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
  }

  .ds-chat-bubble img {
    max-width: 100%;
  }
}

/* ── 14.9 Gate pages (2FA) + configurator ────────────────────────────────── */
@media (max-width: 575.98px) {
  body.ds-gate {
    padding: 16px 12px;
    align-items: flex-start;
  }

  .ds-gate-head {
    padding: 18px 20px;
  }

  .ds-gate-body {
    padding: 20px;
  }

  .ds-gate-actions {
    padding: 14px 20px;
    flex-wrap: wrap;
  }

  .ds-gate-actions .btn {
    flex: 1 1 auto;
  }

  .ds-gate-qr img {
    width: 100%;
    max-width: 190px;
    height: auto;
  }
}

/* The Material settings panel is hidden by POSITION alone (`right: -360px`),
   never by `display`, `visibility` or `pointer-events`. Off-screen or not, it
   stays a full-height 360px hit target at z-index 1020, anchored to the
   viewport's right edge — so wherever it overlapped a form it silently ate the
   clicks on the fields underneath, while Tab still reached them because
   keyboard focus does no hit-testing. Any sub-pixel rounding, a horizontal
   scrollbar, or an ancestor creating a containing block is enough to slide it
   back into view. Closed, it takes no pointer input at all; open, it does.

   base.html and chatlay.html no longer render the panel at all, so these rules
   are a safety net: the stock demo pages (pages/rtl.html, virtual-reality.html)
   still include it, and this keeps it from doing the same damage there or
   anywhere it gets reintroduced. */
.fixed-plugin .card {
  pointer-events: none;
}

.fixed-plugin.show .card {
  pointer-events: auto;
}

/* The toggle button is a sibling of the card, not a child, so it keeps its own
   pointer input and the panel can still be opened. */
.fixed-plugin .fixed-plugin-button {
  pointer-events: auto;
}

/* The Material settings panel is a fixed 360px card that hangs off the edge of
   a small phone. Material hides it with `right: -360px` — exactly its own
   width — so narrowing or widening it WITHOUT moving that offset by the same
   amount leaves a strip of the panel parked over the page. Width and offset
   are therefore driven from one custom property. */
@media (max-width: 767.98px) {
  .fixed-plugin .card {
    --ds-configurator-w: calc(100vw - 1.5rem);
    width: var(--ds-configurator-w) !important;
    max-width: var(--ds-configurator-w) !important;
    right: calc(-1 * var(--ds-configurator-w));
  }

  .fixed-plugin.show .card {
    right: 0;
  }
}

/* ── 14.10 Print / reduced motion courtesy ───────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ds-sidebar-backdrop,
  .sidenav,
  .main-content {
    transition: none !important;
  }
}
