/* Dashboard palette (post-login) — light mode keeps a white page/card
   background per design direction, but every accent/text/tint tracks the
   marketing site's palette instead of the old generic blue admin theme.
   Dark mode redefines the same tokens below; the app-header is exempt
   from both (it's hardcoded near-black — see .app-header) since it should
   look the same regardless of mode. */
:root {
  --ink: #141414;
  --ink-soft: #7a6b57;
  --accent: #c85a32;
  --accent-dark: #a5491f;
  --accent-tint: #f7e9e2;
  --bg: #ffffff;
  --card-bg: #ffffff;
  --hover-bg: #f6f1ea;
  --border: #e6ddd0;
  --table-head-bg: #f7f3ec;
  --neutral-tint: #f2ede4;
  --subtle-row-bg: #faf7f2;
  --success-tint: #e3f3e8;
  --danger-tint: #fbe6e2;
  --danger: #c0392b;
  --danger-solid: #c0392b; /* stays constant in both themes — used as a solid button fill with white text, unlike --danger which is tuned for use as text color */
  --success: #1e7e42;
  --origin-response-tint: #e3e9f7;
  --origin-response-ink: #3b5bb0;
  --origin-submission-tint: #e1f2ef;
  --origin-submission-ink: #2f8b7a;
  --font-body: "DM Sans", sans-serif;
  --font-heading: "EB Garamond", serif;
  --sidebar-width: 220px;
  --sidebar-width-collapsed: 64px;
}

[data-theme="dark"] {
  /* Neutral near-black/gray surfaces (matching the marketing site's own
     --near-black, not a warm brown tint) — terracotta stays the one color
     accent against them, same role it plays on the marketing site. */
  --ink: #f4f1ea;
  --ink-soft: #9a978f;
  --accent: #d9713f;
  --accent-dark: #e88a5c;
  --accent-tint: rgba(217, 113, 63, 0.16);
  --bg: #141414;
  --card-bg: #1f1f1f;
  --hover-bg: rgba(255, 255, 255, 0.06);
  --border: #333333;
  --table-head-bg: #262626;
  --neutral-tint: #2a2a2a;
  --subtle-row-bg: #1c1c1c;
  --success-tint: rgba(111, 207, 151, 0.15);
  --danger-tint: rgba(255, 157, 138, 0.15);
  --danger: #ff9d8a;
  --success: #6fcf97;
  --origin-response-tint: rgba(126, 156, 235, 0.18);
  --origin-response-ink: #9db4f0;
  --origin-submission-tint: rgba(93, 199, 178, 0.18);
  --origin-submission-ink: #7fd9c4;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--ink);
  /* A wide table (or a zoomed-in viewport) can push body wider than the
     viewport; without this the whole page gains horizontal scroll and the
     sidebar — a normal document-flow sibling, not fixed — scrolls off
     along with it. Wide content scrolls inside its own .table-scroll
     container instead, so the sidebar always stays put. */
  overflow-x: hidden;
  /* Mobile Safari (iPadOS included) silently boosts body-text font-size on
     narrow-column paragraphs like .panel-note — the CSS here never
     changes, only what the browser renders. This opts out so every size
     declared in this file is actually what's shown. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

h1, h2, h3 { margin: 0 0 8px; font-weight: 700; }
p { margin: 0 0 12px; line-height: 1.5; }

.btn {
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  padding: 12px 24px;
  border-radius: 6px;
  border: none;
  cursor: pointer;
}

.btn:disabled { cursor: not-allowed; opacity: 0.55; }

.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--accent-dark); }

.btn-secondary {
  background: var(--card-bg);
  color: var(--accent);
  border: 1px solid var(--accent);
}

.btn-ghost {
  background: transparent;
  color: var(--ink-soft);
  border: 1px solid var(--border);
}

.refresh-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  font-size: 13px;
}
.refresh-btn svg { flex-shrink: 0; }
.refresh-btn.is-spinning svg { animation: refresh-spin 0.6s linear; }
@media (prefers-reduced-motion: reduce) {
  .refresh-btn.is-spinning svg { animation: none; }
}
@keyframes refresh-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Auth screens — styled to match the marketing site's palette/type (site
   header is always near-black; EB Garamond for headings, DM Sans for
   body), scoped to just these screens so the dashboard keeps its own look. */
.auth-screen {
  --brand-near-black: #141414;
  --brand-peach: #f1dfd0;
  --brand-cream: #f4f1ea;
  --brand-terracotta: #c85a32;
  --brand-tan-dark: #a88351;

  /* 100vh locks to the browser's largest possible viewport and doesn't
     recompute cleanly across a mobile orientation change (URL bar
     show/hide leaves the old, taller value in place after rotating back to
     portrait) — a real gap of unstyled body below the card was reported
     from exactly that sequence. 100dvh tracks the actual visible viewport
     live; the 100vh line above it is a fallback for browsers that don't
     support dvh and simply ignore that rule. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background:
    radial-gradient(circle at 50% 0%, rgba(200, 90, 50, 0.22), transparent 55%),
    radial-gradient(circle at 85% 100%, rgba(168, 131, 81, 0.18), transparent 50%),
    linear-gradient(160deg, #1c1c1c 0%, var(--brand-near-black) 45%, #0a0a0a 100%);
}

.auth-logo { height: 96px; width: auto; margin-bottom: 32px; }
/* Text wordmark placeholder in place of a real uploaded logo — see Phase 4's
   planned per-tenant branding for a real image-upload path. */
.auth-wordmark { display: flex; align-items: center; justify-content: center; font-size: 28px; font-weight: 700; letter-spacing: 0.02em; }

.auth-card {
  background: var(--brand-cream);
  border: none;
  border-radius: 4px;
  padding: 44px 40px;
  width: 100%;
  max-width: 380px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
}

.auth-card h1 {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 26px;
  color: var(--brand-near-black);
  margin: 0;
}

.auth-subtitle { color: var(--brand-tan-dark); font-size: 14px; margin-top: -8px; }

.auth-card label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--brand-near-black);
}

.auth-card input {
  font-family: var(--font-body);
  font-size: 15px;
  padding: 10px 12px;
  border: 1px solid var(--brand-tan-dark);
  border-radius: 4px;
  background: #fff;
}

.auth-card input:focus {
  outline: none;
  border-color: var(--brand-terracotta);
  box-shadow: 0 0 0 3px rgba(200, 90, 50, 0.18);
}

.auth-card .btn-primary {
  background: var(--brand-terracotta);
  font-family: var(--font-body);
}
.auth-card .btn-primary:hover:not(:disabled) { background: #a5491f; }

.auth-card .link-btn { color: var(--brand-tan-dark); }
.auth-card .link-btn:hover { color: var(--brand-terracotta); }

/* Fixed colors, not the theme-variable --danger/--success — the auth
   screens always have a light cream card regardless of dashboard
   light/dark mode, and a stray data-theme="dark" left over from a prior
   dashboard session shouldn't wash these out. */
.auth-error { color: #c0392b; font-size: 13px; margin: 0; }
.auth-success { color: #1e7e42; font-size: 13px; margin: 0; }

.link-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  align-self: flex-start;
}

.link-btn:hover { text-decoration: underline; }

.password-requirements {
  list-style: none;
  margin: -8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Fixed colors, same reasoning as .auth-error above — this only ever
   appears inside the auth screens' fixed light cream card. */
.password-requirements li {
  font-size: 13px;
  color: #a88351;
  padding-left: 22px;
  position: relative;
}

.password-requirements li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1.5px solid #a88351;
  background: #fff;
}

.password-requirements li.met {
  color: #1e7e42;
}

.password-requirements li.met::before {
  border-color: #1e7e42;
  background: #1e7e42;
}

.password-requirements li.met::after {
  content: "";
  position: absolute;
  left: 4.5px;
  top: 5px;
  width: 5px;
  height: 8px;
  border: solid #fff;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}

/* App shell */
.app-shell { min-height: 100vh; }

/* Baseline for every form control in the dashboard — without this they
   fall back to the browser's default white input background, which broke
   badly in dark mode (light --ink text on a default-white box). More
   specific rules below still layer on their own padding/border/font. */
.app-shell input,
.app-shell select,
.app-shell textarea {
  background: var(--card-bg);
  color: var(--ink);
}

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 32px;
  /* Fixed regardless of theme — matches the marketing site's near-black
     header, with the same terracotta/tan-dark glow used on the login
     screen (.auth-screen) so the two feel like one system. */
  background:
    radial-gradient(circle at 15% 0%, rgba(200, 90, 50, 0.28), transparent 55%),
    radial-gradient(circle at 90% 100%, rgba(168, 131, 81, 0.2), transparent 60%),
    #141414;
  color: #fff;
}

.theme-toggle svg { display: block; }
.theme-toggle .icon-moon { display: none; }
[data-theme="dark"] .theme-toggle .icon-sun { display: none; }
[data-theme="dark"] .theme-toggle .icon-moon { display: block; }

.app-title { font-weight: 700; font-size: 16px; }

.app-header-right { display: flex; align-items: center; gap: 16px; }
.session-email { font-size: 13px; color: #c7cee0; }

.app-header .btn-ghost {
  color: #e4e8f5;
  border-color: rgba(255, 255, 255, 0.3);
}

.app-header .btn-ghost:hover { background: rgba(255, 255, 255, 0.08); }

.app-body {
  display: flex;
  align-items: flex-start;
  min-height: calc(100vh - 61px);
}

.sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px;
  background: var(--card-bg);
  border-right: 1px solid var(--border);
  min-height: calc(100vh - 61px);
  max-height: calc(100vh - 61px);
  overflow-y: auto;
  position: sticky;
  top: 61px;
  align-self: flex-start;
  transition: width 0.18s ease;
}

/* Without this, expanding the "Recipients" group pushes total content past
   the sidebar's max-height above, and flex's default shrink behavior
   compresses every nav item to fit instead of letting the sidebar scroll
   (its own overflow-y: auto) the way it's supposed to. */
.sidebar > * { flex-shrink: 0; }

.sidebar.collapsed { width: var(--sidebar-width-collapsed); }

.sidebar-toggle {
  align-self: flex-end;
  background: none;
  border: none;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 8px;
  margin-bottom: 8px;
  border-radius: 6px;
  transition: transform 0.18s ease;
}

.sidebar-toggle:hover { background: var(--hover-bg); }
.sidebar-toggle.rotated { transform: rotate(180deg); }
.sidebar.collapsed .sidebar-toggle { align-self: center; }

.tab-btn {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  padding: 12px 14px;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
}

.tab-btn .nav-icon { flex-shrink: 0; }

/* Positions .tab-badge-dot relative to the icon itself rather than the
   button — see the comment on setTabBadge in notifications.js for why. */
.nav-icon-slot { position: relative; display: inline-flex; flex-shrink: 0; }

.tab-badge-dot {
  position: absolute;
  top: -3px;
  right: -3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  /* --terracotta was never defined anywhere in this file (only --accent
     is) — an undefined custom property has no fallback here, so this was
     rendering as a fully transparent background, not just low-contrast. */
  background: var(--accent);
  border: 1.5px solid var(--card-bg);
}

.tab-btn:hover { background: var(--hover-bg); }
.tab-btn.active { color: var(--accent); background: var(--accent-tint); }

.sidebar.collapsed .tab-btn { justify-content: center; padding: 12px; }
.sidebar.collapsed .nav-label { display: none; }

/* Expandable "Recipients" nav group */
.nav-group-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  padding: 12px 14px;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
}
.nav-group-toggle:hover { background: var(--hover-bg); }
.nav-group-toggle .nav-label { flex: 1; text-align: left; }
.nav-group-chevron { flex-shrink: 0; transition: transform 0.15s ease; }
.nav-group-toggle[aria-expanded="true"] .nav-group-chevron { transform: rotate(180deg); }

.nav-group-children {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 0;
  overflow: hidden;
  padding-left: 14px;
}
.nav-group-children.expanded { max-height: 220px; padding-top: 4px; padding-bottom: 4px; }
.nav-group-children .tab-btn { font-size: 13px; }

.sidebar.collapsed .nav-group-toggle { justify-content: center; padding: 12px; }
.sidebar.collapsed .nav-group-children { padding-left: 0; }

/* min-width: 0 overrides the flex-item default of min-width: auto, which
   otherwise refuses to shrink below a wide descendant's natural width (the
   Dashboard table) — without it, that table forces the whole page to
   overflow horizontally instead of scrolling inside its own container. */
.tab-panels { flex: 1; min-width: 0; padding: 32px; max-width: 1600px; margin: 0 auto; width: 100%; }

.tab-panel { display: none; }
.tab-panel.active { display: block; }

.panel-subtitle { color: var(--ink-soft); }
.panel-note { color: var(--ink-soft); font-size: 13px; }

/* Upload */
.dropzone {
  border: 2px dashed var(--border);
  border-radius: 10px;
  padding: 40px;
  text-align: center;
  background: var(--card-bg);
}

.dropzone.dragover { border-color: var(--accent); background: var(--accent-tint); }

.file-btn { display: inline-block; margin-top: 8px; }

.selected-file { margin-top: 12px; font-size: 13px; color: var(--ink-soft); }

/* Parsing a near-1000-row CSV runs synchronously on the main thread (see
   rowsFromCsv in upload.js) and can take several seconds, during which
   nothing else on the page can update — except a transform-only CSS
   animation, which most browsers keep compositing on their own thread even
   while JS is busy. That's what keeps this spinner visibly spinning
   through the parse instead of freezing, so the app doesn't look hung. */
.upload-parsing-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
  font-size: 13px;
  color: var(--ink-soft);
}
.upload-parsing-indicator svg { animation: refresh-spin 0.9s linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .upload-parsing-indicator svg { animation: none; }
}

.preview-wrap {
  margin-top: 24px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 24px;
}

.preview-summary { font-size: 13px; color: var(--ink-soft); }

.table-scroll { overflow-x: auto; margin-bottom: 16px; }

table {
  border-collapse: collapse;
  width: 100%;
  font-size: 13px;
}

th, td {
  border: 1px solid var(--border);
  padding: 8px 10px;
  text-align: left;
  white-space: nowrap;
}

th { background: var(--table-head-bg); font-weight: 700; }

.upload-actions { margin-top: 16px; display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.upload-send-emails-row { display: flex; flex-direction: row; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.upload-send-emails-row:has(input:disabled) { opacity: 0.55; cursor: not-allowed; }
.upload-report { margin-top: 24px; }

.upload-errors-only {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
}

.upload-pagination { display: flex; align-items: center; gap: 12px; margin-top: 12px; }

.empty-state {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 48px;
  text-align: center;
  color: var(--ink-soft);
}

/* Quizzes + Quiz Results tabs */
.panel-header-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
}

.panel-header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.status-counts { display: flex; flex-wrap: wrap; gap: 6px; }

.followup-flag {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 4px;
  vertical-align: middle;
}
.followup-flag.followup-green { background: var(--success); }
.followup-flag.followup-yellow { background: #d4a72c; }
.followup-flag.followup-red { background: var(--danger); }

.status-badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 100px;
  font-size: 12px;
  font-weight: 600;
  text-transform: capitalize;
}
.status-badge.status-draft { background: var(--neutral-tint); color: var(--ink-soft); }
.status-badge.status-published { background: var(--success-tint); color: var(--success); }
.status-badge.status-error { background: var(--danger-tint); color: var(--danger); }

/* Leads table — status-count badges double as filter buttons */
.status-badge-btn {
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
}
.status-badge-btn:hover { filter: brightness(0.95); }
.status-badge-btn.is-active { border-color: currentColor; }

.table-actions { white-space: nowrap; }
.table-actions-row { display: flex; gap: 8px; }
.table-actions-row + .table-actions-row { margin-top: 4px; }

.link-btn-danger { color: var(--danger); }

.field-hint { font-weight: 400; color: var(--ink-soft); font-size: 12px; }
.inbox-message-attachment { display: flex; align-items: center; gap: 5px; margin: 4px 0; }
.inbox-attachment-icon { flex-shrink: 0; color: var(--ink-soft); }

/* Leads table — Origin badges (recipient/response/submission rows unified
   from three prior separate tables) */
.origin-badge { display: inline-block; padding: 2px 10px; border-radius: 100px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.origin-badge.origin-recipient { background: var(--accent-tint); color: var(--accent-dark); }
.origin-badge.origin-response { background: var(--origin-response-tint); color: var(--origin-response-ink); }
.origin-badge.origin-submission { background: var(--origin-submission-tint); color: var(--origin-submission-ink); }
.origin-badge.origin-unknown { background: var(--neutral-tint); color: var(--ink-soft); }

/* Cross-origin match indicator — looks like a greyed/disabled checkbox but
   is a real <button> so it receives clicks (native disabled checkboxes
   don't dispatch click events). Expands cross-origin-detail-row below it. */
.cross-origin-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--neutral-tint);
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 12px;
  line-height: 1;
  padding: 0;
}
/* Unreviewed uses the same amber as followup-yellow — "needs attention",
   distinct from confirmed (green) and dismissed (neutral, reviewed). */
.cross-origin-indicator.is-unreviewed { background: #fbeecb; border-color: #d4a72c; color: #8a6d1a; font-weight: 700; }
.cross-origin-indicator.is-confirmed { background: var(--success-tint); border-color: var(--success); color: var(--success); }
.cross-origin-indicator.is-dismissed { background: var(--neutral-tint); border-color: var(--border); color: var(--ink-soft); }
.cross-origin-indicator:hover { border-color: var(--accent); }

.cross-origin-detail-row td { background: var(--subtle-row-bg); white-space: normal; padding: 16px; }
.cross-origin-detail-card { display: flex; flex-direction: column; gap: 10px; max-width: 620px; }
.cross-origin-detail-actions { display: flex; gap: 10px; }

.unconfirmed-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 100px;
  border: 1px solid var(--danger);
  background: var(--danger-tint);
  color: var(--danger);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.unconfirmed-badge:hover { filter: brightness(0.96); }

/* Synced top+bottom horizontal scrollbar, auto-attached to every
   .table-scroll table (see js/table-scroll.js). Desktop-only — mobile
   relies on native touch scrolling inside .table-scroll itself. */
.table-scroll-top { overflow-x: auto; margin-bottom: 6px; }
.table-scroll-top > div { height: 1px; }
@media not all and (hover: hover) and (pointer: fine) {
  .table-scroll-top { display: none !important; }
}

.pagination-row { display: flex; align-items: center; gap: 12px; margin: 10px 0; }

/* Merge tokens (composer + template editor preview) — the substituted
   VALUE is styled, not the raw {{token}} syntax, so it reads as "this part
   is dynamic" without cluttering the rest of the message. */
.merge-token-value { color: var(--accent); font-style: italic; }
.merge-token-note { margin-top: 8px; }
.merge-token-note code { background: var(--neutral-tint); border-radius: 4px; padding: 1px 5px; font-size: 11px; }

/* Template editor — Save + the auto-send checkbox live at the top of the
   page, left-justified, instead of buried below the form fields. */
.template-editor-top-bar { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; justify-content: flex-start; margin-bottom: 4px; }

/* Inbox tab — one card per contact, expanding to one row per thread
   (one thread = one origin-interaction, see js/inbox.js) */
.inbox-list { display: flex; flex-direction: column; gap: 10px; }

.inbox-contact-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-bg);
  overflow: hidden;
}

.inbox-contact-header {
  display: grid;
  grid-template-columns: 1.4fr 1.1fr 0.9fr;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  text-align: left;
  font-family: inherit;
  color: var(--ink);
}

.inbox-contact-secondary { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; }

.inbox-contact-toggle {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: var(--ink);
  min-width: 0;
}
.inbox-contact-toggle:hover .inbox-contact-primary { text-decoration: underline; }

.inbox-chevron { flex-shrink: 0; margin-top: 2px; color: var(--ink-soft); transition: transform 0.15s ease; }
.inbox-contact-toggle.is-open .inbox-chevron { transform: rotate(180deg); }

.inbox-contact-primary-group { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; width: 100%; }
/* flex-wrap + overflow-wrap: the email is often the widest thing in its
   grid column, and this column got narrower once the thread-count/date
   line moved under it (see .inbox-contact-header) — without these, a long
   email plus the "Needs reply" badge overflow the column's box and
   visually stack on top of column 2 instead of dropping to a second line. */
.inbox-contact-primary { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-weight: 700; min-width: 0; overflow-wrap: anywhere; }

/* Always in the DOM (see renderInbox in inbox.js) so a toggle click can
   animate max-height in place rather than swapping content in and out —
   the JS measures real content height (scrollHeight) and sets it inline,
   which overrides is-open's own max-height:none below (used only as the
   resting value for a fresh, non-animated render — filter change,
   pagination, reload). border-top only applies once open — a 0-height box
   still paints its own border, so a plain border-top would leave a stray
   line under a collapsed header. */
.inbox-thread-list {
  max-height: 0;
  overflow: hidden;
  background: var(--subtle-row-bg);
  transition: max-height 0.25s ease;
}
.inbox-thread-list.is-open { max-height: none; border-top: 1px solid var(--border); }
@media (prefers-reduced-motion: reduce) {
  .inbox-thread-list { transition: none; }
}

.inbox-thread-section { padding: 14px 16px; border-top: 1px solid var(--border); }
.inbox-thread-section:first-child { border-top: none; }

.inbox-thread-section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.inbox-message-list { max-height: 360px; }

@media (max-width: 720px) {
  .inbox-contact-header { gap: 8px; }
}

/* Tighter gap on touchscreens — keyed on "any touchscreen" rather than
   viewport width since a landscape phone is often wider than 720px (so the
   rule above never applies) but still cramped. The thread-count/date line
   now lives under the email (see .inbox-contact-primary-group) rather than
   its own grid column, so 3 columns fit without the crowding that used to
   force a full single-column collapse here. */
@media (hover: none) and (pointer: coarse) {
  .inbox-contact-header { gap: 8px; }
}

/* Form Submissions tab */
.contact-message-cell { white-space: normal; min-width: 340px; max-width: 560px; }

.contact-reply-row td { background: var(--subtle-row-bg); white-space: normal; }

.contact-reply-panel { padding: 16px 4px; max-width: 760px; }

.contact-name-gate {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}
.contact-name-gate label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  color: var(--ink-soft);
}
.contact-name-gate input {
  font-family: var(--font-body);
  font-size: 14px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
}

.contact-reply-textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 14px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  resize: vertical;
  margin-bottom: 10px;
}

/* Rich-text field: a small toolbar (bold/italic/underline/link) over a
   contenteditable div — used for both the composer's message and a
   template's body, wherever formatting/links matter in what gets sent.
   font-weight:400 here is not just cosmetic: the template editor used to
   nest this inside a <label> (font-weight:600 there, for the caption), and
   while CSS inheritance alone is easy to override on a child, Chrome's
   execCommand/queryCommandState('bold') turned out to key off the nearest
   *ancestor's* weight for a fresh, empty contenteditable region regardless
   of the editor's own computed style — so a plain override here didn't
   stop it from silently bolding whatever was typed. The real fix was
   removing the <label> nesting itself (see index.html's #tf-body-container
   — now preceded by a plain .form-caption span, not wrapped by a label);
   this reset stays as a straightforward belt-and-suspenders default. */
.rich-text-field { margin-bottom: 10px; font-weight: 400; }
.rich-text-toolbar {
  display: flex;
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 6px 6px 0 0;
  background: var(--table-head-bg);
}
.rich-text-btn {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-size: 13px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--ink);
  cursor: pointer;
}
.rich-text-btn:hover { background: var(--hover-bg); }
.rich-text-btn.active { background: var(--accent-tint); border-color: var(--accent); color: var(--accent-dark); }
.rich-text-editor {
  min-height: 110px;
  max-height: 320px;
  overflow-y: auto;
  /* Matches the actual email body's own font (see EMAIL_BODY_FONT_HELVETICA
     in quiz-api/src/handler.js and buildReplyEmailPreviewHtml/api.js) —
     var(--font-body) is this app's own UI font (DM Sans), a different
     typeface/size/line-height from what a recipient actually receives.
     What's typed here should look like what comes out, not like the rest
     of the admin app's chrome. */
  font-family: Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.6;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 0 0 6px 6px;
  color: var(--ink);
  background: var(--card-bg);
}
.rich-text-editor:empty::before { content: attr(data-placeholder); color: var(--ink-soft); }
.rich-text-editor a { color: var(--accent); }
/* padding-left on the <ul> is the whole list's indent from the paragraph
   text around it (~5 regular spaces) — same value used server-side in
   styleListsForEmail/styleListsForEmailPreview so the editor matches what
   actually sends. */
.rich-text-editor ul { margin: 0 0 4px; padding-left: 20px; }
.rich-text-editor ul li { margin: 0 0 8px; }
/* The arrow glyph is real text at the start of the <li> (see applyArrowList
   in api.js), not a list-style marker — list-style:none removes the native
   bullet, and this smaller text-indent/padding-left pair (the width of
   "➤ " itself) hangs a wrapped second line under the item's own text
   rather than back under the arrow, without touching the outer indent set
   on ul.arrow-list above. */
.rich-text-editor ul.arrow-list li { list-style: none; padding-left: 14px; text-indent: -14px; }

.contact-reply-actions { display: flex; gap: 10px; }

.email-preview-wrap {
  margin: 0 0 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.email-preview-header {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 12px;
  background: var(--subtle-row-bg);
  border-bottom: 1px solid var(--border);
}
.email-preview-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.email-preview-subject {
  font-size: 13px;
  color: var(--ink-soft);
}
.email-preview-subject-text { color: var(--ink); font-weight: 600; }

.contact-reply-subject-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 10px;
}
.contact-reply-subject {
  font-family: var(--font-body);
  font-size: 14px;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.email-preview-frame {
  display: block;
  width: 100%;
  height: 280px;
  border: 0;
  background: #f4f1ea;
}

/* Dashboard tab */
.dashboard-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 20px;
}
.dashboard-filters label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
}
.dashboard-filters input,
.dashboard-filters select {
  font-family: var(--font-body);
  font-size: 14px;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
}

.filter-search-group { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
/* The rest of .dashboard-filters top-aligns (see align-items above) so the
   search field's input lines up with the other fields' inputs even though
   its group is taller (Clear All Filters sits below it) — but the checkbox
   row has no label-text line above it, so it still needs to bottom-align to
   land next to the other fields' inputs rather than up near their labels. */
.dashboard-filter-checkbox { flex-direction: row !important; align-items: center; gap: 8px !important; cursor: pointer; align-self: flex-end; }

.correspondence-thread { display: flex; flex-direction: column; gap: 10px; max-height: 260px; overflow-y: auto; }
.correspondence-message {
  padding: 10px 14px;
  border-radius: 8px;
  background: var(--subtle-row-bg);
  max-width: 80%;
  /* A flex item's default min-width is auto (its content's own minimum
     size), which wins over max-width — a wide child (the bullet/arrow
     table) was pushing this past its 80% cap instead of being constrained
     by it, which is what let the table overflow the card in the first
     place. min-width:0 lets max-width actually apply. */
  min-width: 0;
}
.correspondence-message p { margin: 0; }
.correspondence-message p:first-child { margin-bottom: 4px; }
.correspondence-admin { align-self: flex-end; background: var(--accent-tint); }

.form-error { color: var(--danger); font-size: 13px; margin: 8px 0 0; }
.form-success-text { color: var(--success); font-size: 13px; margin: 8px 0 0; }

.btn-danger { background: var(--danger-solid); color: #fff; }
.btn-danger:hover:not(:disabled) { background: #a5311f; }

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 24, 38, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 24px;
}

.modal-card {
  background: var(--card-bg);
  border-radius: 10px;
  padding: 24px;
  max-width: 400px;
  width: 100%;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}

.modal-card-wide { max-width: 820px; max-height: 85vh; overflow-y: auto; }

/* Dashboard: edit-fields form + Add Recipient modal share this grid */
.dashboard-edit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px 16px;
  margin: 16px 0;
}
.dashboard-edit-grid label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
}
.dashboard-edit-grid input,
.dashboard-edit-grid textarea {
  font-family: var(--font-body);
  font-size: 14px;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--ink);
}
.dashboard-edit-grid textarea { resize: vertical; }

/* Shared reply composer: greeting, message-history, and template controls */
.composer-options {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 14px 0;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--subtle-row-bg);
}
.composer-option-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.composer-checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
}
.attachment-field { display: flex; flex-direction: column; gap: 6px; }
.attachment-label { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.attachment-picker { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.attachment-current { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.attachment-filename { font-weight: 600; color: var(--ink); }

.composer-template-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
}
.composer-template-row select {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  flex: 1;
  min-width: 160px;
}

.modal-hint { font-size: 13px; color: var(--ink-soft); margin-bottom: 12px; }

.modal-input {
  width: 100%;
  font-family: var(--font-body);
  font-size: 14px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  margin-bottom: 16px;
}

.modal-actions { display: flex; justify-content: flex-end; gap: 10px; }

.quiz-editor-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 640px;
}
/* Template editor only — wider than the shared form width above so its
   email preview has real desktop room, without widening the Quiz editor
   form (same .quiz-editor-form class) that has no preview of its own. */
#template-editor-form { max-width: 800px; }

.profile-section-heading { margin-top: 40px; }
.notif-email-status { margin-bottom: 16px; max-width: 640px; }

.stale-alerts-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; max-width: 480px; }
.stale-alert-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card-bg);
}

.quiz-editor-section {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding-top: 32px;
}

.quiz-editor-section:first-child { padding-top: 0; }

.quiz-editor-section + .quiz-editor-section { border-top: 1px solid var(--border); }

.quiz-editor-form label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
}

/* A field caption that deliberately isn't a <label> wrapping its control —
   used above a contenteditable rich-text field, which a real <label>
   should never wrap: Chrome's execCommand keys bold state off an ancestor's
   font-weight for a fresh contenteditable region even when the region's
   own computed weight is overridden back to normal (see the comment on
   .rich-text-field), so the caption has to sit outside the inheritance
   chain entirely rather than just being overridden back down. */
.form-caption {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
}

.quiz-editor-form input,
.quiz-editor-form select,
.quiz-editor-form textarea {
  font-family: var(--font-body);
  font-size: 15px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--ink);
}

.quiz-editor-form input[readonly] {
  background: var(--subtle-row-bg);
  color: var(--ink-soft);
  cursor: not-allowed;
}

.quiz-link-row { display: flex; gap: 8px; align-items: center; }
.quiz-link-row input { flex: 1; min-width: 0; }
.quiz-link-row .btn { flex-shrink: 0; white-space: nowrap; }

/* Analytics (Dashboard) tab */
.analytics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: 20px;
}
.chart-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
}
.chart-card h3 { margin: 0 0 12px; font-size: 15px; }
.chart-card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.chart-card-header h3 { margin: 0; }
.chart-canvas { width: 100%; height: 220px; display: block; }

.chart-toggle { display: flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.chart-toggle-btn {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  padding: 5px 10px;
  background: var(--card-bg);
  border: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.chart-toggle-btn + .chart-toggle-btn { border-left: 1px solid var(--border); }
.chart-toggle-btn.active { background: var(--accent-tint); color: var(--accent-dark); }

/* Calendar */
.mini-calendar-widget { position: relative; }
.mini-calendar-icon-btn { padding: 8px; }
.mini-calendar-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 500;
  width: 260px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
  padding: 14px;
}
.mini-calendar-header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.mini-calendar-title { font-weight: 700; font-size: 13px; color: var(--ink); }
.mini-calendar-controls { display: flex; gap: 10px; }
.mini-calendar-controls .link-btn { font-size: 11px; }

/* Side-by-side on desktop, stacked on mobile — flex-wrap alone handles
   both: the grid and the list are each given a minimum width wider than
   half of any narrow/mobile viewport, so they can never fit on one row
   there and always wrap the list below the grid (source order). */
.calendar-layout { display: flex; flex-wrap: wrap; gap: 28px; align-items: flex-start; }
.calendar-full { max-width: 640px; flex: 2 1 420px; }

.calendar-upcoming { flex: 1 1 280px; min-width: 260px; max-width: 360px; }
.calendar-upcoming-title { font-size: 14px; margin: 0 0 10px; color: var(--ink); }
.calendar-upcoming-list { display: flex; flex-direction: column; gap: 6px; }
.calendar-upcoming-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: var(--ink);
}
.calendar-upcoming-item:hover { border-color: var(--accent); background: var(--hover-bg); }
.calendar-upcoming-item .cal-dot { flex-shrink: 0; margin-top: 0; }
.calendar-upcoming-date { flex-shrink: 0; font-size: 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.calendar-upcoming-title-text { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.cal-month-label { font-weight: 700; font-size: 14px; color: var(--ink); }
.cal-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  margin-bottom: 4px;
}
.cal-weekdays span { text-align: center; font-size: 11px; font-weight: 600; color: var(--ink-soft); }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-cell {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--subtle-row-bg);
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  font-family: var(--font-body);
  color: var(--ink);
}
.cal-cell:hover { border-color: var(--accent); }
.cal-cell-empty { background: none; cursor: default; }
.cal-cell-empty:hover { border-color: transparent; }
.cal-cell-today { font-weight: 700; box-shadow: inset 0 0 0 1.5px var(--accent); }
.cal-day-num { font-size: 12px; }
.cal-dot { width: 6px; height: 6px; border-radius: 50%; margin-top: 2px; background: var(--ink-soft); }
.cal-dot.cal-flag-green { background: var(--success); }
.cal-dot.cal-flag-yellow { background: #d4a72c; }
.cal-dot.cal-flag-red { background: var(--danger); }
.cal-dot.cal-flag-note { background: var(--accent); }

.cal-grid-compact .cal-cell { font-size: 10px; }
.cal-grid-compact .cal-day-num { font-size: 10px; }

.cal-event-row { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-bottom: 10px; }
.cal-event-row-header { display: flex; align-items: center; justify-content: space-between; }
.cal-event-reschedule { display: flex; gap: 8px; margin-top: 8px; align-items: center; }
.cal-event-reschedule input { font-family: var(--font-body); font-size: 13px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; }

/* Charts (analytics tab) */
.chart-tooltip {
  position: fixed;
  z-index: 2000;
  pointer-events: none;
  background: var(--near-black, #141414);
  color: #fff;
  font-family: var(--font-body);
  font-size: 12px;
  padding: 6px 10px;
  border-radius: 6px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}

.quiz-editor-form textarea { resize: vertical; }

.quiz-editor-form h3 { margin: 0; }

.quiz-editor-error { color: var(--danger); font-size: 13px; }

.quiz-editor-actions { margin-top: 8px; padding-top: 32px; border-top: 1px solid var(--border); }

.quiz-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.quiz-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.quiz-option-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.quiz-option-row input { flex: 1; }
.quiz-option-row select { flex: 1; }

.quiz-option-letter-badge {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-soft);
}

.results-picker {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 320px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 20px;
}

.results-picker select {
  font-family: var(--font-body);
  font-size: 15px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--ink);
}

@media (max-width: 700px) {
  .app-header { padding: 16px; flex-wrap: wrap; gap: 8px; }
  .tab-panels { padding: 20px; }
  .auth-card { padding: 28px; }
}

/* ==========================================================================
   Mobile: portrait rotate-prompt + landscape compaction.

   Gated on (hover: none) and (pointer: coarse) in addition to size, so a
   narrow-but-tall desktop browser window (e.g. a resized dev tools pane)
   never triggers this — only real touchscreens do. Everything here is pure
   CSS; no JS/orientation listeners needed. */

.rotate-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 5000;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 32px;
  background:
    radial-gradient(circle at 50% 0%, rgba(200, 90, 50, 0.22), transparent 55%),
    radial-gradient(circle at 85% 100%, rgba(168, 131, 81, 0.18), transparent 50%),
    linear-gradient(160deg, #1c1c1c 0%, #141414 45%, #0a0a0a 100%);
  color: #f4f1ea;
}

.rotate-overlay-content { max-width: 320px; }

.rotate-phone-graphic { width: 96px; height: 96px; color: #d9713f; margin-bottom: 20px; }

.rotate-overlay h2 {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 24px;
  margin: 0 0 10px;
  color: #f4f1ea;
}

.rotate-overlay p { color: #c9beae; font-size: 14px; margin: 0; }

.rotate-phone { transform-origin: 60px 60px; animation: rotate-phone-swing 2.4s ease-in-out infinite; }
.rotate-arrow { opacity: 0; animation: rotate-arrow-fade 2.4s ease-in-out infinite; }

@keyframes rotate-phone-swing {
  0%, 15% { transform: rotate(0deg); }
  45%, 70% { transform: rotate(90deg); }
  100% { transform: rotate(90deg); }
}
@keyframes rotate-arrow-fade {
  0%, 10% { opacity: 0; }
  25%, 60% { opacity: 1; }
  75%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .rotate-phone, .rotate-arrow { animation: none; }
  .rotate-phone { transform: rotate(90deg); }
  .rotate-arrow { opacity: 1; }
}

/* Scoped to body.app-active (toggled in showView(), js/app.js) so the
   rotate prompt only ever appears once signed in — login/new-password/
   forgot-password all work fine in portrait, so nothing should block
   reaching them on a phone. */
@media (orientation: portrait) and (max-width: 900px) and (hover: none) and (pointer: coarse) {
  body.app-active .rotate-overlay { display: flex; }
}

/* Landscape phones: short viewport height is the real constraint, not
   width — compact vertical rhythm everywhere so the whole app stays
   reachable without the sidebar's nav list alone exceeding the screen. */
@media (orientation: landscape) and (max-height: 500px) and (hover: none) and (pointer: coarse) {
  .app-header { padding: 8px 14px; }
  .app-title { font-size: 13px; }
  .session-email { display: none; }
  .app-header-right { gap: 8px; }
  .app-header .btn-ghost { padding: 6px 10px; font-size: 12px; }
  .mini-calendar-icon-btn { padding: 6px; }
  .mini-calendar-panel { width: 220px; max-height: calc(100vh - 50px); overflow-y: auto; }

  .app-body { min-height: calc(100vh - 45px); }

  .sidebar {
    width: 150px;
    min-height: 0;
    max-height: calc(100vh - 45px);
    top: 45px;
    padding: 6px;
    gap: 2px;
  }
  .sidebar.collapsed { width: 46px; }
  .tab-btn, .nav-group-toggle { padding: 8px 10px; font-size: 12px; gap: 8px; }
  .nav-group-children.expanded { max-height: 160px; }
  .nav-group-children .tab-btn { font-size: 11px; padding: 6px 10px; }
  .tab-btn .nav-icon, .nav-group-toggle .nav-icon, .nav-group-chevron { width: 14px; height: 14px; }

  .tab-panels { padding: 12px 16px; }
  h2 { font-size: 18px; margin-bottom: 4px; }
  .panel-subtitle { font-size: 12px; margin-bottom: 10px; }

  .dashboard-filters { gap: 8px; margin-bottom: 12px; }
  .dashboard-filters label { font-size: 11px; gap: 3px; }
  .dashboard-filters input, .dashboard-filters select { padding: 6px 8px; font-size: 13px; }
  .panel-header-row { margin-bottom: 12px; }

  table { font-size: 12px; }
  th, td { padding: 5px 7px; }
  .empty-state { padding: 24px; }

  .modal-overlay { padding: 10px; }
  .modal-card { padding: 14px; max-height: calc(100vh - 20px); overflow-y: auto; }
  .modal-card-wide { max-height: calc(100vh - 20px); }
  .dashboard-edit-grid { gap: 10px 12px; margin: 12px 0; }

  .analytics-grid { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
  .chart-card { padding: 10px; }
  .chart-card h3 { font-size: 13px; margin-bottom: 6px; }
  .chart-canvas { height: 130px; }

  .calendar-full { max-width: 100%; }

  .btn { padding: 8px 14px; font-size: 13px; }
  .auth-screen { padding: 12px; min-height: 100vh; min-height: 100dvh; }
  .auth-logo { height: 56px; margin-bottom: 12px; }
  .auth-card { padding: 18px 20px; max-height: calc(100vh - 24px); max-height: calc(100dvh - 24px); overflow-y: auto; }
}

/* Phase 4 dunning banner — shown app-wide (not per-page) whenever the
   tenant's subscription isn't active; a grace-period notice, not a lockout
   (see app.js's checkDunningStatus for the reasoning). */
.dunning-banner {
  background: var(--danger-tint);
  color: var(--danger);
  padding: 10px 20px;
  font-size: 13px;
  font-weight: 600;
  text-align: center;
  border-bottom: 1px solid var(--danger);
}
.dunning-banner a { color: inherit; text-decoration: underline; }
