/*
 * Round 4 fix 6 (14 October 2026). Project-wide: the HTML `hidden`
 * attribute wins over any class rule setting `display`. Every admin
 * script controls visibility of modals, dropdowns, panes and inline
 * errors by toggling `element.hidden = true/false`; without this
 * rule a stray `.scrim { display: flex }` would render a `hidden`
 * element visible (the bug on fix 3 and fix 5 where the compose
 * scrim and the link mini-popup rendered on load). Keep this
 * `!important` so later ad-hoc `display:` rules can't win back.
 */
[hidden] { display: none !important; }

/*
 * DonorFix admin shell.
 *
 * Authoritative reference: donorfix-design.md.
 * Where a screen and the design document disagree, the document wins.
 *
 * Working tool, not a marketing page. Server rendered. Deployed by
 * uploading files. One stylesheet, no build step, no framework, no
 * icon font.
 *
 * Type scale (single source of truth, do not override per-widget):
 *   page title        23px / 500
 *   card heading      16px / 500
 *   body + inputs     15px / 400
 *   emphasis          15px / 500
 *   metric figure     19px / 500
 *   big metric        22px / 500
 *
 * Nothing below 15px anywhere. Not labels, not table headers, not
 * badges, not metadata. If something feels like it needs to be
 * smaller, it needs to be shorter.
 */

/* -------------------- fonts -------------------- */

@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/admin/assets/fonts/figtree-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/admin/assets/fonts/figtree-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/admin/assets/fonts/figtree-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/admin/assets/fonts/figtree-700.woff2') format('woff2');
}

/* -------------------- tokens -------------------- */

:root {
  /* CRM colour restyle — 26 September 2026. The whole admin now
     reads on the Financials palette. Every token below either moved
     onto a Financials colour or was left alone with the reason
     noted. Page background stays the same grey as before, so the
     "cards on a soft grey" shape of the app is unchanged. */

  /* Page background stays grey. Held on its own token so a future
     restyle can point it somewhere else without touching --surface-1
     (which now drives beige surfaces like table header rows). */
  --page-bg:    #f4f5f7;

  /* Surface tokens. --surface-1 now drives BEIGE surfaces (table
     header row, per-page tinted rows, the settings "later" card
     background). The page background above is separate. */
  --surface-1:  #F8F7F3;   /* beige: table headers, tinted rows */
  --surface-2:  #ffffff;   /* white cards, sidebar */
  --tint:       #eef1f5;   /* row hover, filter bar background — left as-is; a warmer hover reads too close to --surface-1 on hover, so tint stays cool for legibility */
  --ink:        #1c2530;
  --muted:      #667080;
  --line:       #D6D2C8;   /* Financials border tone (beige) */
  --line-2:     #E4E1D8;   /* Financials row-divider tone (paler beige) */

  --radius:     12px;      /* card */
  --radius-s:   6px;       /* control */
  --hairline:   0.5px;

  --control-h:  38px;
  --sidebar-w:  178px;
  --sidebar-rail: 48px;
  --topbar-h:   44px;
  --content-max: 1440px;
  --page-pad:   18px;

  /* Primary navy — now the Financials verdict-band navy. Primary
     buttons, links and the active sidebar item all read as the
     same navy as the top band on Financials. */
  --accent:     #1F3A5F;
  --accent-2:   #132640;   /* hover */
  --accent-tint: #DFE5EE;  /* active nav item tint */

  /* Status palettes — all on the Financials tones. */
  --success-bg: #E7F1E9;   --success-ink: #1E6B45;
  --warning-bg: #FCEFD9;   --warning-ink: #7A4108;
  --danger-bg:  #FBE3E1;   --danger-ink:  #8E1F17;
  --info-bg:    #E6ECF4;
  --info-ink:   #1F3A5F;   /* aligned with new accent so info reads as navy on band-body */

  /* Border tones for the four status palettes. Each is a middle
     tone between the palette's background and its ink, sized to
     read at a distance on badges and flashes. Replaces the four
     hardcoded border colours that used to sit on .flash-* and
     .badge-*. */
  --success-border: #B5D8C0;
  --warning-border: #E8CE8F;
  --danger-border:  #E6B4B0;
  --info-border:    #B7C6DC;

  /* Financials verdict-band tokens. Navy #1F3A5F is the same value
     --accent now carries; kept as a separate token so a later
     restyle can drift the band away from the primary button without
     one change chasing the other. */
  --band-navy:    #1F3A5F;
  --band-inner:   #2B4A73;
  --band-eyebrow: #C9D5E6;
  --band-body:    #E6ECF4;
  --band-label:   #D4DEEB;

  /* Financials tick stroke — the pale mint used on the "All clear"
     tick SVG in the verdict band. Held as a token so any future
     shell-level tick can reuse it; the SVG itself is inline on the
     page and picks up the hex directly. */
  --success-tick: #B5E4C5;

  /* Navy-band control borders (26 September 2026). The search box
     and the Open/Close-filters toggle sit on the navy band, so
     their borders need to read as an inset against the navy —
     lighter than the band, darker than white. Two tokens because
     the input's border is a shade darker than the toggle's. */
  --band-input-border:  #46618A;
  --band-toggle-border: #6F87AB;

  --font-body:  Figtree, -apple-system, BlinkMacSystemFont,
                "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono:  ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--page-bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Round 6c design pass (16 October 2026). Rob's rules, in his
   words: "No chip or pill is ever underlined, on hover or
   otherwise, anywhere in the CRM", and (on hover underlines in
   general) "looks very oldschool". Shared rule — any element
   whose class name contains pill / chip / badge, as a link OR a
   button, never underlines, not even on hover. If a NEW chip or
   pill class is added later it inherits this rule for free. The
   dedicated name list keeps the specificity low; the catch-all
   attribute matchers below cover any dashed sub-class (pill-rec,
   chip-x, badge-info, …) without needing to list each one.

   Note: `.tag` is NOT in this rule because other components use
   `.tag` (filter-tag, donor-tag) with intended hover underline
   on the "remove" affordance. If Rob wants those de-underlined
   too, they go in with the design sweep. */
.pill, .chip, .badge,
.df-chip, .df-ec-chip, .df-band-chip,
.df-emails-pill, .df-nav-bell-item,
[class*="pill-"], [class*="chip-"], [class*="badge-"],
a.pill, a.chip, a.badge,
button.pill, button.chip, button.badge {
    text-decoration: none;
}
.pill:hover, .chip:hover, .badge:hover,
.df-chip:hover, .df-ec-chip:hover, .df-band-chip:hover,
.df-emails-pill:hover, .df-nav-bell-item:hover,
[class*="pill-"]:hover, [class*="chip-"]:hover, [class*="badge-"]:hover,
a.pill:hover, a.chip:hover, a.badge:hover,
button.pill:hover, button.chip:hover, button.badge:hover {
    text-decoration: none;
}

/* Round 6c design pass: a row that opens something is itself the
   target. `data-row-href` (shell JS admin.js already handles it)
   or `.row-open` on any element gets a pointer cursor + a hover
   shade, so you can click anywhere. Keyboard still opens via
   Enter / Space on `[data-row-href][tabindex]` (admin.js). */
[data-row-href],
.row-open {
    cursor: pointer;
}
tr[data-row-href]:hover,
.row-open:hover {
    background: var(--tint);
}

/* Round 6c design pass: no small underlined link text inside a
   clickable row. The row's own hover shade and pointer cursor are
   the affordance; the inner <a> stays for no-JS and middle-click
   but doesn't paint an underline when the user is reading. */
.tl-email-clickable a:hover,
.df-emails-row a:hover,
.df-nav-bell-item a:hover,
tr[data-row-href] a:hover,
.dash-attention-row[data-row-href] a:hover,
.dash-attention-row[data-row-href] a {
    text-decoration: none;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* -------------------- app shell -------------------- */

/* Layout: fixed left sidebar, content column fills the rest. */
.app {
  display: flex;
  min-height: 100vh;
  align-items: stretch;
}

.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--surface-2);
  border-right: var(--hairline) solid var(--line);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  z-index: 20;
  transition: width 160ms ease;
  /* Hide the visible scrollbar without disabling the scrolling
     itself. On a short window the sidebar still scrolls via mouse
     wheel, trackpad, touch and keyboard (Tab moves focus through
     every item; ArrowUp / ArrowDown scroll when the sidebar has
     focus). Applies in the full-width sidebar and in the
     peeked-open sidebar on narrow screens - same element, same
     rule. */
  scrollbar-width: none;      /* Firefox */
  -ms-overflow-style: none;   /* Old Edge and IE */
}
.sidebar::-webkit-scrollbar {  /* Chrome, Safari, WebKit */
  width: 0;
  height: 0;
  display: none;
}

/* ============================================================
   Brand mark - shared with donorfix.com. See donorfix-decisions.md
   for the "kept identical" rule and once-per-session play. Two
   variants: .brand-sidebar (26px mark + 16px wordmark) on the
   sidebar header; .brand-narrow (44px mark + 26px wordmark) on the
   login screen. Gap between mark and wordmark is 3px on both -
   tighter than the mockup suggested.
   ============================================================ */

.brand {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  text-decoration: none;
  color: var(--ink);
  white-space: nowrap;
}
.brand:hover { text-decoration: none; }
.brand-name       { font-weight: 500; color: #17365D; }
.brand-name .fix  { color: #169B62; }

/* Stage 20a Round 2a (26 October 2026). The brand bar now holds a
   .logo-slot that scales any uploaded logo to fit; the default is
   the Nonprofio logo served from /admin/assets/img/nonprofio-logo.svg.
   Round 2b opens the client-upload path; the slot size stays. */
.sidebar  .brand .logo-slot { height: 36px; max-width: 150px; display: inline-flex; align-items: center; flex-shrink: 0; }
.sidebar  .brand .logo-slot img { max-height: 100%; max-width: 150px; width: auto; height: auto; display: block; }
.brand-narrow .logo-slot { height: 48px; max-width: 300px; display: inline-flex; align-items: center; flex-shrink: 0; }
.brand-narrow .logo-slot img { max-height: 100%; max-width: 300px; width: auto; height: auto; display: block; }

/* Animated logo mark - CSS copied verbatim from
   donorfix.com/index.html under "animated logo mark". Keep in sync
   with the site; edit both together. */
.logo-mark { flex: none; overflow: visible; }
.logo-mark .gift { transform-box: fill-box; transform-origin: center; }
.logo-mark .bowl { transform-box: fill-box; transform-origin: center top; }
.logo-mark.play .gift { animation: logoDrop 1.15s cubic-bezier(.42,0,.58,1) both; }
.logo-mark.play .bowl { animation: logoSettle 1.15s ease-out both; }
@keyframes logoDrop {
  0%   { transform: translateY(-18px); opacity: 0; }
  15%  { opacity: 1; }
  52%  { transform: translateY(0); }
  66%  { transform: translateY(-3.6px); }
  80%  { transform: translateY(0); }
  90%  { transform: translateY(-1.1px); }
  100% { transform: translateY(0); }
}
@keyframes logoSettle {
  0%,50% { transform: scaleY(1); }
  58%    { transform: scaleY(1.13); }
  72%    { transform: scaleY(.975); }
  86%    { transform: scaleY(1.02); }
  100%   { transform: scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
  .logo-mark.play .gift,
  .logo-mark.play .bowl { animation: none; }
}

.sidebar .brand {
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  padding: 0 14px;
  color: var(--ink);
  border-bottom: var(--hairline) solid var(--line-2);
  white-space: nowrap;
  overflow: hidden;
}

.sidebar-nav {
  padding: 10px 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-s);
  color: var(--ink);
  font-size: 15px;
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-decoration: none;
  min-height: 34px;
}
.sidebar-nav a:hover { background: var(--tint); text-decoration: none; }
.sidebar-nav a.active {
  background: var(--accent-tint);
  color: var(--accent);
  font-weight: 500;
}
.sidebar-nav .sn-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: currentColor;
}
.sidebar-nav .sn-icon svg { width: 100%; height: 100%; display: block; }
.sidebar-nav hr {
  border: none;
  border-top: var(--hairline) solid var(--line-2);
  margin: 8px 4px;
}

/* Fold-out group (Donations, 23 September 2026).
   A native <details> so it toggles without JavaScript and screen
   readers announce expanded / collapsed. The <summary> mirrors the
   size, spacing and hover of a regular .sidebar-nav a exactly; the
   chevron rotates 90 degrees on open. The sub-list indents 14px
   and carries a hairline left border in --line-2, the same token
   as the sidebar's rule. When an item inside the fold-out is
   active it gets .active in the existing selected style; the
   summary itself is never marked active because Donations has no
   page of its own. */
.sidebar-nav details.sidebar-nav-group {
  margin: 0;
  padding: 0;
}
.sidebar-nav details.sidebar-nav-group > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-s);
  color: var(--ink);
  font-size: 15px;
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  min-height: 34px;
  cursor: pointer;
  list-style: none;
}
.sidebar-nav details.sidebar-nav-group > summary::-webkit-details-marker {
  display: none;
}
.sidebar-nav details.sidebar-nav-group > summary:hover {
  background: var(--tint);
}
.sidebar-nav details.sidebar-nav-group > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.sidebar-nav details.sidebar-nav-group > summary .sn-label {
  flex: 1;
  min-width: 0;
}
.sidebar-nav details.sidebar-nav-group > summary .sn-chevron {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  color: var(--muted);
  transition: transform 120ms ease;
}
.sidebar-nav details.sidebar-nav-group[open] > summary .sn-chevron {
  transform: rotate(90deg);
}
.sidebar-nav details.sidebar-nav-group .sidebar-nav-sub {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 2px 0 4px 14px;
  padding-left: 8px;
  border-left: var(--hairline) solid var(--line-2);
}
.sidebar-nav details.sidebar-nav-group .sidebar-nav-sub a {
  /* Sub-items reuse the .sidebar-nav a size and .active style
     defined above; nothing to override here. */
}

.app-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--page-bg);
}

/* Slim top bar in the content column, right aligned. Never holds
   navigation. */
.topbar {
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--page-pad);
  background: var(--surface-2);
  border-bottom: var(--hairline) solid var(--line);
  position: sticky;
  top: 0;
  z-index: 10;
}
.topbar-left { display: flex; align-items: center; gap: 10px; }
.topbar-right {
  display: flex;
  align-items: center;
  gap: 14px;
  color: var(--ink);
  font-size: 15px;
}
.topbar-right .role { color: var(--muted); }
.topbar-right .divider { color: var(--muted); }
.topbar-right a { color: var(--muted); }
.topbar-right a:hover { color: var(--ink); text-decoration: none; }
/* Stage 18 Round 2: the person's name is a link to Your settings.
   All links in the CRM use --accent (donorfix-design.md:216), so
   the name link reads navy to make it clear it opens somewhere. */
.topbar-right a.me { color: var(--accent); font-weight: 500; }
.topbar-right a.me:hover { color: var(--accent-2); text-decoration: none; }

/* Sidebar toggle button (visible only on collapsed layouts). */
.sidebar-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  color: var(--ink);
  cursor: pointer;
}
.sidebar-toggle svg { width: 18px; height: 18px; display: block; }

/* -------------------- page frame -------------------- */

.page {
  padding: var(--page-pad);
  max-width: var(--content-max);
  margin: 0 auto;
  width: 100%;
  /* Round 2b fix 1 (26 October 2026). .app-main is a flex column
     (topbar · main.page · .admin-powered-footer). Letting
     main.page grow pushes the footer to the bottom of the window
     on short pages (Emails › Unread when empty, for example) and
     keeps it at the end of the content on long pages
     (Contacts, Reports). One rule, no per-page shim. */
  flex: 1;
}
body.narrow .page { max-width: 480px; padding-top: 48px; }
.page-body { padding-top: 4px; }

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
}
.page-header .ph-title { flex: 1; min-width: 0; }
.page-header h1 {
  margin: 0 0 4px;
  font-size: 23px;
  font-weight: 500;
  line-height: 1.25;
  color: var(--ink);
}
.page-header .subtitle {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
}
.page-header .ph-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-shrink: 0;
}

/* -------------------- breadcrumb -------------------- */

/* Style A from mockups/mockup-breadcrumbs.html: chevrons between
   steps, each step a link back except the last, the current step
   in a light navy pill, 15px throughout. Sits directly above the
   page title on every non-top-level page. */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 15px;
  flex-wrap: wrap;
  margin-bottom: 10px;
  min-width: 0;
}
.breadcrumb .crumb-step,
.breadcrumb a.crumb-step {
  color: var(--muted);
  text-decoration: none;
  padding: 4px 8px;
  border-radius: 6px;
  max-width: 340px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.breadcrumb a.crumb-step:hover,
.breadcrumb a.crumb-step:focus-visible {
  background: #e6e9ee;
  color: var(--accent);
  outline: none;
}
.breadcrumb a.crumb-step:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.breadcrumb .crumb-here {
  color: var(--accent);
  font-weight: 500;
  background: var(--accent-tint);
  padding: 4px 12px;
  border-radius: 999px;
  max-width: 420px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.breadcrumb .crumb-chev {
  width: 16px;
  height: 16px;
  color: #9aa0aa;
  flex-shrink: 0;
}

/* -------------------- collapsed sidebar (mobile / tablet) -------------------- */

@media (max-width: 1000px) {
  .sidebar {
    width: var(--sidebar-rail);
  }
  .sidebar .brand-name,
  .sidebar-nav .sn-label { display: none; }
  .sidebar .brand { justify-content: center; padding: 0; }
  .sidebar-nav a { justify-content: center; padding: 8px; }
  .sidebar-toggle { display: inline-flex; }

  /* Expanded state overlays content instead of pushing it, so
     content behind stays in place while the sidebar is peeked. */
  body.sidebar-open .sidebar {
    width: var(--sidebar-w);
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    box-shadow: 2px 0 12px rgba(0,0,0,0.08);
  }
  body.sidebar-open .sidebar .brand { justify-content: flex-start; padding: 0 14px; }
  body.sidebar-open .sidebar-nav a { justify-content: flex-start; padding: 8px 10px; }
  body.sidebar-open .sidebar .brand-name,
  body.sidebar-open .sidebar-nav .sn-label { display: inline; }

  /* Fold-out group in the collapsed rail: hide the label and the
     chevron, centre the icon, and drop the sub-nav's indent + left
     border so the sub-icons line up with the rail's icons. On the
     peeked-open sidebar (.sidebar-open) the summary and sub-nav
     return to their full appearance via the two rules below. */
  .sidebar-nav details.sidebar-nav-group > summary {
    justify-content: center;
    padding: 8px;
  }
  .sidebar-nav details.sidebar-nav-group > summary .sn-chevron {
    display: none;
  }
  .sidebar-nav details.sidebar-nav-group .sidebar-nav-sub {
    margin: 2px 0;
    padding-left: 0;
    border-left: 0;
  }
  body.sidebar-open .sidebar-nav details.sidebar-nav-group > summary {
    justify-content: flex-start;
    padding: 8px 10px;
  }
  body.sidebar-open .sidebar-nav details.sidebar-nav-group > summary .sn-chevron {
    display: block;
  }
  body.sidebar-open .sidebar-nav details.sidebar-nav-group .sidebar-nav-sub {
    margin: 2px 0 4px 14px;
    padding-left: 8px;
    border-left: var(--hairline) solid var(--line-2);
  }

  body.sidebar-open .sidebar-overlay {
    position: fixed;
    inset: 0;
    background: rgba(28,37,48,0.35);
    z-index: 15;
  }
}

/* -------------------- panels (cards) -------------------- */

.panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
  margin-bottom: 12px;
}
.panel.flush { padding: 0; overflow: hidden; }
.panel h2 {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  padding-bottom: 10px;
  border-bottom: var(--hairline) solid var(--line-2);
}
.panel .note {
  color: var(--muted);
  font-size: 15px;
  margin: 0 0 10px;
  line-height: 1.55;
}
.section-gap { margin-top: 16px; }

.muted { color: var(--muted); }
.mono {
  font-family: var(--font-mono);
  font-size: 15px;
  background: var(--tint);
  padding: 2px 6px;
  border-radius: 3px;
}

/* -------------------- form groups + fields -------------------- */

.form-group {
  padding-top: 20px;
  margin-top: 20px;
  border-top: var(--hairline) solid var(--line-2);
}
.form-group:first-of-type { border-top: none; padding-top: 0; margin-top: 0; }
.form-group h3 {
  margin: 0 0 14px;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
}

.fields-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 16px;
  align-items: flex-start;
  margin-bottom: 14px;
}
.fields-row:last-child { margin-bottom: 0; }

/* Scoped hide utility. The built-in [hidden] attribute has the same
   specificity as .form-group / .fields-row and loses to them because
   they are declared later in the cascade, so the record-type toggle
   in donors/edit.php could not hide those blocks with `hidden` alone.
   This class carries an explicit !important so any element the toggle
   flips does hide, regardless of what layout rule it also matches. */
.df-hidden { display: none !important; }

/* Make the built-in `hidden` attribute win over the two layout classes
   above. Without this, a `.fields-row` or `.form-group` element with the
   `hidden` attribute still renders as a flex / block, because the class
   rules sit later in the cascade. Scoped to the two classes only, so no
   other element's `hidden` behaviour changes. */
.fields-row[hidden],
.form-group[hidden] { display: none; }

/* Warning banner shown inline next to a form field that carries a
   value the current record_type does not own. Rendered only on load,
   from stray DB data, not from mid-form toggles. */
.field-warning {
  margin: 6px 0 0;
  padding: 8px 10px;
  border-left: 3px solid var(--warning-border);
  background: var(--warning-bg);
  color: var(--warning-ink);
  font-size: 13px;
  border-radius: var(--radius-s);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 0 1 auto;
  min-width: 0;
}
.field > label {
  font-size: 15px;
  color: var(--ink);
  font-weight: 400;
}
.field .hint {
  color: var(--muted);
  font-size: 15px;
  margin: 2px 0 0;
  line-height: 1.5;
}
.field .req { color: var(--danger-ink); margin-left: 3px; }

/* Natural widths per design. Applied as fixed width with max-width:100%
   so a narrow viewport shrinks them gracefully. */
.field.w-postcode { width: 120px; max-width: 100%; }
.field.w-name     { width: 220px; max-width: 100%; }
.field.w-city     { width: 220px; max-width: 100%; }
.field.w-state    { width: 200px; max-width: 100%; }
.field.w-country  { width: 220px; max-width: 100%; }
.field.w-phone    { width: 200px; max-width: 100%; }
.field.w-email    { width: 300px; max-width: 100%; }
.field.w-address  { width: 456px; max-width: 100%; }
.field.w-select   { width: 240px; max-width: 100%; }
.field.grow       { flex: 1 1 220px; }

/* -------------------- form controls (uniform 38px) -------------------- */

.form-control,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="search"],
input[type="tel"],
input[type="url"],
select,
textarea {
  width: 100%;
  min-height: var(--control-h);
  padding: 8px 12px;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  background: var(--surface-2);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  line-height: 1.4;
  transition: border-color 120ms, box-shadow 120ms;
}
textarea {
  min-height: calc(var(--control-h) * 2);
  resize: vertical;
  line-height: 1.55;
}
select {
  appearance: none;
  -webkit-appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(-45deg, transparent 50%, var(--muted) 50%);
  background-position: right 14px center, right 8px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 30px;
}

input:focus, select:focus, textarea:focus, .form-control:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(32,77,138,0.15);
}

::placeholder { color: var(--muted); opacity: 1; }

label.check {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  color: var(--ink);
  min-height: var(--control-h);
  cursor: pointer;
  padding: 0 2px;
}
label.check input[type="checkbox"] {
  width: 18px;
  height: 18px;
  cursor: pointer;
  flex-shrink: 0;
  margin: 0;
}

/* Legacy narrow-page forms (login, MFA, password reset). Keep the
   .stack pattern working; visual output is identical to fields-row. */
form.stack { display: flex; flex-direction: column; gap: 14px; }
form.stack .row { display: flex; flex-direction: column; gap: 6px; }
form.stack label { font-size: 15px; color: var(--ink); }
form.stack .hint { color: var(--muted); font-size: 15px; margin: 2px 0 0; }

.form-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  margin-top: 10px;
}
.form-error { color: var(--danger-ink); font-size: 15px; margin: 4px 0 0; }

/* -------------------- buttons -------------------- */

/* Verb first, sentence case, no punctuation. One primary per screen. */
button.btn, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--control-h);
  padding: 0 16px;
  border: var(--hairline) solid var(--accent);
  background: var(--accent);
  color: #fff;
  border-radius: var(--radius-s);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
}
button.btn:hover, .btn:hover {
  background: var(--accent-2);
  border-color: var(--accent-2);
  color: #fff;
  text-decoration: none;
}
button.btn.secondary, .btn.secondary {
  background: var(--surface-2);
  color: var(--accent);
  border-color: var(--line);
}
button.btn.secondary:hover, .btn.secondary:hover {
  background: var(--tint);
  color: var(--accent);
  border-color: var(--line);
  text-decoration: none;
}
button.btn.danger, .btn.danger {
  background: var(--surface-2);
  color: var(--danger-ink);
  border-color: var(--danger-ink);
}
button.btn.danger:hover, .btn.danger:hover {
  background: var(--danger-bg);
  color: var(--danger-ink);
  border-color: var(--danger-ink);
}
button.btn.small, .btn.small { height: 32px; padding: 0 12px; font-size: 15px; }
button.btn[disabled], .btn[disabled] {
  background: #a9b5c3;
  border-color: #a9b5c3;
  color: #fff;
  cursor: not-allowed;
}
button.btn[disabled]:hover, .btn[disabled]:hover { background: #a9b5c3; color: #fff; }

/* -------------------- flashes -------------------- */

.flashes { margin-bottom: 14px; }
.flash {
  padding: 10px 14px;
  border-radius: var(--radius-s);
  margin-bottom: 8px;
  font-size: 15px;
  border: var(--hairline) solid transparent;
}
.flash-ok    { background: var(--success-bg); color: var(--success-ink); border-color: var(--success-border); }
.flash-warn  { background: var(--warning-bg); color: var(--warning-ink); border-color: var(--warning-border); }
.flash-error { background: var(--danger-bg);  color: var(--danger-ink);  border-color: var(--danger-border); }
.flash-info  { background: var(--info-bg);    color: var(--info-ink);    border-color: var(--info-border); }

/* -------------------- filter bar -------------------- */

form.filters {
  background: var(--tint);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 12px;
}
form.filters .filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 14px;
  align-items: flex-end;
}
form.filters .filter {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 150px;
  flex: 1 1 150px;
}
form.filters .filter label { font-size: 15px; color: var(--muted); }
form.filters .filter input,
form.filters .filter select {
  min-height: var(--control-h);
  height: var(--control-h);
}
form.filters .filter-actions {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  margin-left: auto;
}

/* v0.8 collapsible filters. Search + date range ride the primary
   row. A second .filter-actions-row sits under it: the green
   MORE FILTERS button anchors the left, Apply/Reset anchor the
   right. Putting the toggle on its own line and giving it the
   only green button in the app is deliberate - if this control
   is missed, the hidden filters are missed. Green is used
   nowhere else for a button.

   The button's aria-controls points at #more-filters-body; a
   shell-level handler (AdminLayout footer JS) toggles the body's
   hidden attribute and swaps the label span between "More
   filters" and "Hide filters" on click. When any hidden filter
   is set the server renders the body without `hidden` (so it
   opens on load), the button label as "Hide filters", and a
   "(N)" count span. Reset lands on the query-string-free URL,
   where $moreCount is 0 and the body renders hidden with the
   button reading "More filters". */
form.filters .filter-row-primary { align-items: flex-end; }

form.filters .filter-actions-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  flex-wrap: wrap;
}

/* .more-filters-btn / .more-filters-count / .more-filters-body —
   the v0.8 collapsible filter pattern that Contacts, Gifts and
   Recurring used before the 23 September and 26 September
   restyles moved them onto the topbar-details pattern and then
   onto the navy band. The classes are no longer rendered by any
   PHP file in the admin. The shell footer JS handler in
   AdminLayout.php stays as-is (documented not to change) — it now
   simply binds to zero elements per page. Removed from admin.css
   on 26 September 2026. */

/* v2 list-page filter shape (23 September 2026) - Contacts, Gifts
   and Recurring. The v0.8 "More filters" green-button pattern above
   stays in place for Pledges, batches and audit-log until they move
   onto this shape.

   Structure:
     form.filters
       .filter-topbar         // flex row, flex-wrap: wrap
         input.filter-topbar-search
         details.filter-more
           summary             // Open filters / Close filters, with count badge
         .filter-tags          // one <a class="filter-tag"> per applied filter,
                               // last child is <a class="filter-clear">Clear all</a>
         .filter-panel-body    // sibling of .filter-more; hidden until the
                               // details is open, then flex-basis: 100% wraps
                               // it to its own row full-width below the bar

   The panel-body is a sibling of the details rather than a child of
   it. Nesting the panel inside the details left Recurring's four-
   field panel narrow enough to sit beside the search box (the wrap
   only fired when the panel's intrinsic width overflowed the flex
   row, which held for the eight- and fourteen-field pages but not
   for the four-field one). Making the panel a sibling and forcing
   `flex-basis: 100%` via `.filter-more[open] ~ .filter-panel-body`
   drops it below the bar full width on every page.

   Native <details> so no JavaScript is required. The panel always
   loads closed (server never sets `open`). Everything uses existing
   admin.css tokens; no new colours. */
form.filters .filter-topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
form.filters .filter-topbar-search {
  flex: 0 1 320px;
  min-width: 200px;
  height: var(--control-h);
  padding: 0 10px;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  background: var(--surface-2);
  font: inherit;
  color: var(--ink);
}
form.filters .filter-more {
  /* Container only; every visual detail lives on <summary>. */
}
form.filters .filter-more > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  list-style: none;
  cursor: pointer;
  height: var(--control-h);
  padding: 0 14px;
  border: var(--hairline) solid var(--accent);
  border-radius: var(--radius-s);
  color: var(--accent);
  background: var(--surface-2);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
}
form.filters .filter-more > summary::-webkit-details-marker { display: none; }
form.filters .filter-more > summary::marker { content: ''; }
form.filters .filter-more > summary:hover { background: var(--accent-tint); }
form.filters .filter-more > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
form.filters .filter-more > summary .filter-more-shut { display: none; }
form.filters .filter-more[open] > summary .filter-more-open { display: none; }
form.filters .filter-more[open] > summary .filter-more-shut { display: inline; }
form.filters .filter-more-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--accent);
  color: #ffffff;
  font-size: 15px;
  font-weight: 500;
}
form.filters .filter-tags {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  flex: 1 1 auto;
  min-width: 0;
}
form.filters .filter-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px 3px 12px;
  border-radius: 999px;
  background: var(--accent-tint);
  color: var(--accent);
  font-size: 15px;
  text-decoration: none;
  line-height: 1.5;
}
form.filters .filter-tag:hover { text-decoration: none; }
form.filters .filter-tag .filter-tag-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  font-size: 15px;
  line-height: 1;
}
form.filters .filter-tag:hover .filter-tag-x { background: var(--accent); color: #ffffff; }
form.filters .filter-clear {
  color: var(--muted);
  font-size: 15px;
  margin-left: 6px;
}
form.filters .filter-clear:hover { color: var(--ink); }
/* Panel body lives inside .filter-topbar as a sibling of
   .filter-more and .filter-tags. When the details is closed it is
   hidden; when open, `flex-basis: 100%` forces it onto its own row
   below the top bar so the fields lay out full width regardless of
   how many there are. The earlier structure (panel-body nested
   inside the details) relied on the panel's intrinsic width
   overflowing the flex row to force a wrap - fine on Contacts (8
   filters) and Gifts (14) but not on Recurring (4), where the
   narrow content stayed on the same row as the search box. */
form.filters .filter-panel-body {
  display: none;
}
form.filters .filter-more[open] ~ .filter-panel-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex-basis: 100%;
  width: 100%;
  margin-top: 12px;
  padding-top: 12px;
  border-top: var(--hairline) solid var(--line-2);
}
form.filters .filter-panel-body .filter-actions {
  margin-left: 0;
  align-self: flex-start;
}

/* Screen-reader-only text. Used by the filter tag's × so the
   accessible name reads "Remove Field: value" rather than just "×". */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Listbar carries the top pager on the right, next to Save as
   report. The bottom pager sits on its own line, right-aligned,
   under the table. */
.listbar-actions .pager {
  margin: 0;
}
.listbar-bottom-pager {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}
.listbar-bottom-pager .pager { margin: 0; }
.listbar-bottom-pager:empty { display: none; }

/* -------------------- count strip -------------------- */

.listbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 2px 12px;
  flex-wrap: wrap;
  gap: 10px;
}
.listbar .counts { color: var(--muted); font-size: 15px; }
.listbar .counts strong { color: var(--ink); font-weight: 500; }
.listbar-actions { display: flex; gap: 10px; align-items: center; }

details.save-report { position: relative; }
details.save-report > summary { list-style: none; cursor: pointer; }
details.save-report > summary::-webkit-details-marker { display: none; }
details.save-report > summary::marker { content: ''; }
details.save-report[open] > form {
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: 6px;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  padding: 10px;
  display: flex;
  gap: 8px;
  align-items: center;
  min-width: 300px;
  z-index: 10;
}
details.save-report[open] > form input[type="text"] {
  min-height: 32px;
  padding: 4px 10px;
  flex: 1;
}

/* -------------------- tables -------------------- */

table.data {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface-2);
  border: 0;
  font-size: 15px;
}
table.data th, table.data td {
  padding: 10px 14px;
  border-bottom: var(--hairline) solid var(--line-2);
  vertical-align: top;
  line-height: 1.5;
}
/* Alignment split per row-type. The header alignment must MATCH the
   value alignment beneath it in the same column - a right-aligned
   money value under a left-aligned header sits at the wrong side of
   the column and the two do not read as related.
   Default is left for both; .num flips both to right; .nowrap is
   orthogonal. The .num rules are stated separately for thead and
   tbody so they can never be out-specificity'd by future thead-only
   rules and so the pairing "header + value in same column line up"
   is legible in one glance right here. */
table.data thead th          { text-align: left; }
table.data tbody td          { text-align: left; }
table.data thead th.num,
table.data tbody td.num      { text-align: right; white-space: nowrap; }
table.data thead th.nowrap,
table.data tbody td.nowrap   { white-space: nowrap; }
table.data thead th {
  background: var(--surface-1);
  color: var(--ink);
  font-size: 15px;
  font-weight: 400;
  border-bottom: var(--hairline) solid var(--line);
}
table.data tbody tr:last-child td { border-bottom: none; }
table.data tbody tr:hover { background: var(--tint); }
table.data tbody tr.row-link { cursor: pointer; }
table.data td .secondary { color: var(--muted); font-size: 15px; margin-top: 2px; }

/* -------------------- badges -------------------- */

.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 15px;
  font-weight: 400;
  border: var(--hairline) solid transparent;
  line-height: 1.5;
}
.badge-new       { background: var(--success-bg); color: var(--success-ink); border-color: var(--success-border); }
.badge-lapsed    { background: var(--warning-bg); color: var(--warning-ink); border-color: var(--warning-border); }
.badge-recurring { background: var(--info-bg);    color: var(--info-ink);    border-color: var(--info-border); }
.badge-major     { background: var(--info-bg);    color: var(--info-ink);    border-color: var(--info-border); }
.badge-failed    { background: var(--danger-bg);  color: var(--danger-ink);  border-color: var(--danger-border); }
.badge-muted     { background: var(--tint);       color: var(--muted);       border-color: var(--line-2); }

/* Semantic badge variants — same palettes as the badge families
   above, named for what they mean rather than for the row that
   first used them. Added 26 September 2026 for the Dashboard
   Needs-attention row labels; reusable anywhere a row/section
   label should read as warning, danger or info. */
.badge-warning   { background: var(--warning-bg); color: var(--warning-ink); border-color: var(--warning-border); }
.badge-danger    { background: var(--danger-bg);  color: var(--danger-ink);  border-color: var(--danger-border); }
.badge-info      { background: var(--info-bg);    color: var(--info-ink);    border-color: var(--info-border); }

/* -------------------- table row actions -------------------- */
/* Trailing "actions" column on a list row: keep the inline forms
   and links on one line, sitting flush to the right edge. Buttons
   inside keep their normal 38px height (via .btn); the utility
   here is just the horizontal spacing and alignment. */
.row-actions {
  white-space: nowrap;
  text-align: right;
}
.row-actions .btn,
.row-actions form.js-confirm {
  margin-left: 6px;
}
.row-actions form { display: inline; }

/* -------------------- check grid -------------------- */
/* Wrapping row of checkboxes used by any form that offers a
   multi-select as a set of labels. Two columns on narrow screens,
   three or four on wider. Each label still uses the shared .check
   pattern for the box + label alignment. */
.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 6px 16px;
}

/* -------------------- unsaved-changes bar -------------------- */
/* Fixed to the bottom of the content column when body carries
   data-df-form-dirty. Wired by the shell JS in AdminLayout.php.
   Left offset matches the sidebar's width so the bar aligns with
   the content column, not the sidebar; collapses to full width
   below 1000px where the sidebar itself becomes a rail. */
.form-dirty-bar {
  position: fixed;
  left: var(--sidebar-w);
  right: 0;
  bottom: 0;
  padding: 10px 18px;
  background: var(--warning-bg);
  color: var(--warning-ink);
  border-top: var(--hairline) solid var(--warning-border);
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  z-index: 30;
}
.form-dirty-bar .form-dirty-text {
  font-size: 15px;
  font-weight: 500;
}
body[data-df-form-dirty] .form-dirty-bar { display: flex; }
/* Leave room for the bar so a scrolled-to-the-bottom form isn't
   covered by it. Only applied while the bar is visible. */
body[data-df-form-dirty] main.page { padding-bottom: 68px; }
@media (max-width: 999px) {
  .form-dirty-bar { left: 0; }
}

/* -------------------- campaign setup cards (Stage 14 round 4b) -------------------- */
/* The campaign setup page splits into clearly titled cards so a
   non-technical Executive Director can see what each group controls.
   Each card is a .panel with a title row, a one-line description
   under the title, and the fields underneath. One-off to this page:
   nothing else in the admin needs the "card with a lead sentence"
   shape yet. */
.setup-card { margin-bottom: 14px; }
.setup-card-title {
  margin: 0 0 2px;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
}
.setup-card-desc {
  margin: 0 0 14px;
  font-size: 15px;
  color: var(--muted);
  line-height: 1.5;
}
.setup-card .form-group,
.setup-card .form-group:first-of-type {
  padding-top: 0;
  margin-top: 0;
  border-top: none;
}
/* Calmer help text under each field — smaller and lighter than
   the 15px body hint elsewhere, as agreed in round 4b B2. The
   admin minimum (15px, from donorfix-design.md) still applies to
   labels, inputs and body text; this is a one-sentence sub-hint
   that reads as a whisper under the field. */
.setup-card .field .hint {
  font-size: 14px;
  color: var(--muted);
  line-height: 1.45;
}

/* The image preview is shown in the banner's shape so staff see
   what will be trimmed. 16:5 roughly matches the recommended
   1920x600. */
.setup-image-preview {
  position: relative;
  width: 100%;
  max-width: 480px;
  aspect-ratio: 16 / 5;
  background: var(--surface-1);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  margin: 0 0 10px;
}
.setup-image-preview img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.setup-image-preview.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 14px;
}

/* The section-order list: one row per section, with a Show toggle,
   a placement radio (above / below), Move up / Move down buttons,
   and the section's label. Plain buttons, keyboard-reachable, no
   drag and drop. */
.setup-order-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.setup-order-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  padding: 10px 12px;
  border-bottom: var(--hairline) solid var(--line-2);
}
.setup-order-list li:last-child { border-bottom: none; }
.setup-order-list .order-label {
  min-width: 110px;
  font-weight: 500;
  color: var(--ink);
}
.setup-order-list .order-placement {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  color: var(--muted);
}
.setup-order-list .order-move {
  margin-left: auto;
  display: flex;
  gap: 6px;
}
.setup-order-list .btn.icon {
  width: 32px;
  min-height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.setup-order-list .btn.icon[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Swatch picker for the brand colours. Each swatch is a radio
   button, styled as a round coloured chip with the token label
   underneath. The chosen swatch gets a navy ring. */
.setup-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 14px;
  margin-top: 4px;
}
.setup-swatch {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  font-size: 13px;
  color: var(--muted);
}
.setup-swatch input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.setup-swatch .swatch-chip {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: var(--hairline) solid var(--line);
  box-shadow: inset 0 0 0 2px #fff;
}
.setup-swatch input:checked + .swatch-chip {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.setup-swatch input:focus-visible + .swatch-chip {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Round 8a close rule 4: when a value would clash with another
   setting (selected_fill matching amount_tiles or fund_cards), the
   chip is rendered greyed and the radio is disabled server-side.
   Half opacity + not-allowed cursor keeps the chip visible so the
   operator can see WHY it's blocked (the tooltip explains). */
.setup-swatch.is-disabled {
  opacity: .4;
  cursor: not-allowed;
}
.setup-swatch.is-disabled .swatch-chip,
.setup-swatch.is-disabled .swatch-name {
  pointer-events: none;
}

/* Sticky Save/Cancel bar for the setup page, so both actions are
   reachable while scrolling. Rob, 6 October 2026: "lets fix the two
   save bars" — this is the one bar for the campaign setup page.
   The shell's own form-dirty-bar is hidden on this page (rule
   below); when the form has unsaved changes, the dirty-text span
   on the left of this bar shows instead. Other pages keep the
   shell's bar. */
.setup-sticky-actions {
  position: sticky;
  bottom: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 10px 14px;
  margin-top: 14px;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
}
.setup-sticky-dirty {
  display: none;
  margin-right: auto;
  color: var(--warning-ink);
  font-size: 15px;
  font-weight: 500;
}
body[data-df-form-dirty] .setup-sticky-dirty { display: inline-flex; }

/* Hide the shell's unsaved-changes bar on the campaign setup page.
   Scoped to body.page-campaign-setup (set by campaigns-edit.js), so
   every other page keeps the shell bar exactly as before. The
   padding-bottom the shell adds to make room for its bar is also
   dropped here. */
body.page-campaign-setup .form-dirty-bar { display: none !important; }
body.page-campaign-setup[data-df-form-dirty] main.page { padding-bottom: 0; }

/* Settings › CRM Branding + Settings › Website Branding: each page
   has its own save pattern (CRM Branding is logo-only, no form
   dirty state; Website Branding has its own sticky save bar).
   Hide the shell's #df-form-dirty-bar on both so no two bars ever
   show. Scoped via body classes set by the two page JS files
   (Round 3 Piece 1b, 27 October 2026). The padding-bottom the
   shell adds for its bar is also dropped. */
body.page-settings-crm-branding .form-dirty-bar,
body.page-settings-website-branding .form-dirty-bar { display: none !important; }
body.page-settings-crm-branding[data-df-form-dirty] main.page,
body.page-settings-website-branding[data-df-form-dirty] main.page { padding-bottom: 0; }

/* Campaign Look tab — Theme selector + per-campaign custom picker.
   Round 3 Piece 1b (27 October 2026). The two choice rows read as
   one radio group; the custom tiles below are hidden until the
   custom choice is picked. Styling pulled from the mockup's
   .choice / .custom / .swatches family — namespaced here under
   .dp-theme-* / .dp-custom-* so no admin.css class is reused. */
.dp-theme-choices { display: flex; flex-direction: column; gap: 10px; margin: 4px 0 14px; }
.dp-theme-choice {
    display: flex; gap: 12px; align-items: flex-start;
    border: 1px solid var(--line); border-radius: 10px; padding: 14px;
    cursor: pointer; background: #fff;
}
.dp-theme-choice.on {
    border-color: var(--navy); background: #F5F8FC;
    box-shadow: 0 0 0 1px var(--navy) inset;
}
.dp-theme-choice input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; }
.dp-theme-choice .dp-theme-r {
    width: 18px; height: 18px; border-radius: 50%;
    border: 2px solid #9CA3AF; flex: none; margin-top: 2px;
    transition: border .12s ease;
}
.dp-theme-choice.on .dp-theme-r { border: 5px solid var(--navy); }
.dp-theme-choice .dp-theme-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.dp-theme-choice .t { font-weight: 600; }
.dp-theme-choice .d { color: var(--muted); font-size: 14px; }
.dp-theme-minis { display: flex; gap: 6px; margin-top: 6px; }
.dp-theme-minis i {
    width: 22px; height: 22px; border-radius: 5px;
    border: 1px solid rgba(0,0,0,.1); display: inline-block;
}

.dp-custom-box { display: none; }
.dp-custom-box.show { display: block; }
.dp-custom-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
@media (max-width: 1050px) { .dp-custom-tiles { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px)  { .dp-custom-tiles { grid-template-columns: 1fr; } }
.dp-custom-tile { border: 1px solid var(--line); border-radius: 10px; background: #fff; position: relative; }
.dp-custom-tile.open { outline: 2px solid var(--navy); outline-offset: 2px; }
.dp-custom-tile .dp-custom-fill {
    display: block; width: 100%; height: 54px; padding: 6px;
    border: 0; border-bottom: 1px solid var(--line);
    border-radius: 10px 10px 0 0; cursor: pointer; text-align: right;
}
/* Round 3 Piece 1b: shared picker popup inside each custom tile. */
.dp-custom-tile .pop {
    display: none; position: absolute; top: 60px; left: 8px;
    z-index: 45; width: 268px;
    background: #fff; border: 1px solid var(--line); border-radius: 12px;
    box-shadow: 0 14px 34px rgba(16,24,40,.18); padding: 14px;
}
.dp-custom-tile.open .pop { display: block; }
.dp-custom-tile .sv {
    position: relative; height: 150px; border-radius: 8px;
    cursor: crosshair;
    background: linear-gradient(to top, #000, rgba(0,0,0,0)),
                linear-gradient(to right, #fff, rgba(255,255,255,0));
}
.dp-custom-tile .sv .knob, .dp-custom-tile .hue .knob {
    position: absolute; width: 16px; height: 16px;
    border: 2px solid #fff; border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(0,0,0,.35);
    transform: translate(-50%, -50%); pointer-events: none;
}
.dp-custom-tile .hue {
    position: relative; height: 14px; border-radius: 999px;
    margin: 14px 0 12px; cursor: pointer;
    background: linear-gradient(90deg, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
}
.dp-custom-tile .hue .knob { top: 50%; }
.dp-custom-tile .prow { display: flex; gap: 8px; align-items: center; }
.dp-custom-tile .prow .now {
    width: 34px; height: 34px; border-radius: 8px;
    border: 1px solid rgba(0,0,0,.1); flex: none;
}
.dp-custom-tile .prow input {
    flex: 1; min-width: 0;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 14px; border: 1px solid #D1D5DB; border-radius: 6px;
    padding: 7px 8px; text-transform: uppercase;
}
.dp-custom-tile .prow input.bad { border-color: #C2410C; }
.dp-custom-tile .plab { font-size: 12.5px; color: var(--muted); margin: 12px 0 6px; }
.dp-custom-tile .presets { display: flex; gap: 8px; flex-wrap: wrap; }
.dp-custom-tile .presets button {
    width: 26px; height: 26px; border-radius: 6px;
    border: 1px solid rgba(0,0,0,.12); cursor: pointer; padding: 0;
}
.dp-custom-tile .pfoot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.dp-custom-tile .pfoot .btn { padding: 6px 12px; font-size: 13.5px; }
.dp-custom-tile .dp-custom-fill span {
    display: inline-block; font-size: 12px; line-height: 1.3;
    background: rgba(255,255,255,.94); color: var(--ink);
    border-radius: 6px; padding: 2px 6px;
    border: 1px solid rgba(0,0,0,.06); user-select: none;
}
.dp-custom-meta { padding: 8px 10px; }
.dp-custom-name { font-weight: 600; font-size: 14px; }
.dp-custom-input {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px; width: 100%;
    border: 1px solid #D1D5DB; border-radius: 6px;
    padding: 5px 8px; margin: 6px 0 4px; color: var(--ink);
    text-transform: uppercase; box-sizing: border-box;
}
.dp-custom-input.bad { border-color: #C2410C; }
.dp-custom-use { font-size: 12.5px; color: var(--muted); }
.dp-custom-hint { color: var(--muted); font-size: 13px; margin: 10px 0 0; }
.dp-custom-hint code {
    font-family: ui-monospace, Menlo, Consolas, monospace;
    background: #F3F4F6; padding: 1px 5px; border-radius: 4px;
}

/* -------------------- inline cost table (campaign editor) -------------------- */
/* A data table nested inside a form loses its normal card wrapper.
   Give it a hairline border and matching padding so the section
   still reads as a bounded block. Description input takes the full
   cell width; amount stays narrow and right-aligned. */
.cost-table {
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius-s);
  margin-top: 8px;
}
.cost-table input[type="text"] {
  width: 100%;
  box-sizing: border-box;
}
.cost-table tfoot th {
  background: var(--tint);
  font-weight: 500;
}

/* -------------------- tree indent for hierarchical lists -------------------- */
/* Used by the campaign list (parent -> child) and any future two-
   level list that needs to show membership. Depth is stated on the
   <tr> as tree-depth-<N>; the first name cell picks up a left
   padding proportional to depth. tree-indent is the small "└ " glyph
   the row template can prepend when depth > 0. */
.tree-row td:first-child { padding-left: 12px; }
.tree-row.tree-depth-1 td:first-child { padding-left: 32px; }
.tree-row.tree-depth-2 td:first-child { padding-left: 52px; }
.tree-indent { color: var(--muted); margin-right: 4px; }

table.data.donors .donor-name-cell a { font-weight: 500; }

/* -------------------- record-type icon (donor list) -------------------- */

.rt-line {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 15px;
  color: var(--muted);
  margin-top: 2px;
}
.rt-icon { width: 14px; height: 14px; flex-shrink: 0; color: var(--muted); }
.rt-icon svg { width: 100%; height: 100%; display: block; }

/* -------------------- definition list (contact block) -------------------- */

.dl-tight {
  display: grid;
  grid-template-columns: 78px 1fr;
  gap: 6px 14px;
  font-size: 15px;
  margin: 0;
}
.dl-tight dt { color: var(--muted); font-weight: 400; }
.dl-tight dd { margin: 0; color: var(--ink); word-break: break-word; }
.dl-tight dd.full-row {
  grid-column: 1 / -1;
  padding-top: 8px;
  margin-top: 4px;
  border-top: var(--hairline) dashed var(--line-2);
  display: grid;
  grid-template-columns: 78px 1fr;
  gap: 6px 14px;
}
.dl-tight dd.full-row .lbl { color: var(--muted); }

/* -------------------- donor record blocks -------------------- */

.donor-header .dh-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
}
.donor-header .dh-actions { display: flex; gap: 8px; }

.donor-tags-block { margin-top: 14px; padding-top: 12px; border-top: var(--hairline) solid var(--line-2); }
.donor-tags-existing {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.donor-tags-existing .lbl {
  color: var(--muted);
  font-size: 15px;
  margin-right: 4px;
}
.donor-tags-add {
  margin-top: 10px;
  display: flex;
  gap: 6px;
  align-items: center;
}
.donor-tags-add input[type="text"] {
  min-height: 32px;
  height: 32px;
  padding: 4px 10px;
  font-size: 15px;
  width: 240px;
  max-width: 100%;
}
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--tint);
  border: var(--hairline) solid var(--line);
  border-radius: 999px;
  padding: 2px 4px 2px 12px;
  font-size: 15px;
}
.tag-chip .tag-x {
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--muted);
  padding: 0 6px;
  font-size: 16px;
  line-height: 1;
  height: auto;
  min-height: 0;
}
.tag-chip .tag-x:hover { color: var(--danger-ink); }

form.inline { display: inline-flex; align-items: center; gap: 4px; margin: 0; }

/* -------------------- giving summary strip -------------------- */

.giving-summary {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0;
  padding: 0;
}
.giving-summary .gs-cell {
  padding: 14px 18px;
  border-right: var(--hairline) solid var(--line-2);
}
.giving-summary .gs-cell:last-child { border-right: none; }
.giving-summary .gs-label { color: var(--muted); font-size: 15px; margin-bottom: 4px; }
.giving-summary .gs-value { font-size: 19px; font-weight: 500; }
@media (max-width: 720px) {
  .giving-summary { grid-template-columns: repeat(2, 1fr); }
  .giving-summary .gs-cell { border-right: none; border-bottom: var(--hairline) solid var(--line-2); }
}

/* -------------------- household member list -------------------- */

.household-list { list-style: none; padding: 0; margin: 0; }
.household-list li {
  padding: 8px 0;
  border-bottom: var(--hairline) solid var(--line-2);
  font-size: 15px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.household-list li:last-child { border-bottom: none; }
.pill {
  background: var(--tint);
  color: var(--muted);
  border: var(--hairline) solid var(--line);
  border-radius: 999px;
  padding: 1px 10px;
  font-size: 15px;
}

/* -------------------- tabs -------------------- */

/* Anchor lives on the wrapper: no-JS fallback scrolls to the tab
   strip after a full reload. Server rendered: only the active tab's
   content is in the DOM at any moment. With JS on, view.php fetches
   the next tab's content and swaps it in place; the page does not
   move. */
.tabs-wrapper { scroll-margin-top: 8px; }
nav.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  margin-top: 18px;
  padding: 0 2px;
}
nav.tabs a {
  padding: 12px 22px;
  color: var(--muted);
  border-bottom: 3px solid transparent;
  border-top-left-radius: 6px;
  border-top-right-radius: 6px;
  font-size: 15px;
  font-weight: 400;
  margin-bottom: -1px;
  transition: background 120ms, color 120ms;
}
nav.tabs a:hover {
  color: var(--ink);
  background: var(--tint);
  text-decoration: none;
}
nav.tabs a.active {
  color: var(--accent);
  background: var(--accent-tint);
  border-bottom-color: var(--accent);
  font-weight: 500;
}

/* Container that swaps on tab change. Kept as its own block so the
   fetch-swap in view.php has an unambiguous target. */
#tab-content { min-height: 120px; }
#tab-content.is-loading { opacity: 0.5; transition: opacity 100ms linear 60ms; }

/* -------------------- timeline -------------------- */

/* Alternating shaded rows plus hairlines. Time column fixed at 108px.
   Relative on top in weight 500, absolute below it muted. Detail joins
   pieces with " · " on one line where it fits. */
ol.timeline { list-style: none; padding: 0; margin: 14px 0 0; }
ol.timeline .tl-item {
  display: grid;
  grid-template-columns: 108px 1fr;
  gap: 16px;
  padding: 10px 14px;
  border-bottom: var(--hairline) solid var(--line-2);
}
ol.timeline .tl-item:nth-child(odd) { background: var(--tint); }
ol.timeline .tl-item:last-child { border-bottom: none; }
/* Round 6b-1 fix 6 (15 October 2026): the whole Emails-tab row is a
   click target when it carries data-compose. The subject link inside
   keeps its own styling for mouse-over feedback and no-JS/screen-
   reader users. */
ol.timeline .tl-email-clickable { cursor: pointer; }
ol.timeline .tl-email-clickable:hover { background: var(--tint); }
ol.timeline .tl-email-clickable:focus-visible { outline: 2px solid #4068A6; outline-offset: -2px; }
ol.timeline .tl-when { font-size: 15px; color: var(--muted); line-height: 1.4; }
ol.timeline .tl-rel { display: block; color: var(--ink); font-weight: 500; }
ol.timeline .tl-abs { display: block; font-size: 15px; margin-top: 2px; }
ol.timeline .tl-headline { font-weight: 500; font-size: 15px; line-height: 1.4; }
ol.timeline .tl-detail {
  color: var(--ink);
  font-size: 15px;
  margin-top: 3px;
  line-height: 1.55;
  white-space: pre-wrap;
}
ol.timeline .tl-actor { color: var(--muted); font-size: 15px; margin-top: 3px; }
ol.timeline .tl-payment_failed .tl-headline,
ol.timeline .tl-email_failed .tl-headline { color: var(--danger-ink); }

/* -------------------- notes list -------------------- */

ol.notes-list { list-style: none; padding: 0; margin: 14px 0 0; }
.note-item .note-head {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-bottom: 8px;
  flex-wrap: wrap;
}
.note-item .note-body { white-space: pre-wrap; line-height: 1.55; font-size: 15px; }
.note-item .note-actions { margin-top: 12px; padding-top: 10px; border-top: var(--hairline) solid var(--line-2); }
.note-item .note-actions .link { font-size: 15px; }

/* -------------------- tasks list -------------------- */

ol.tasks-list { list-style: none; padding: 0; margin: 0; }
ol.tasks-list .task-item {
  padding: 14px 0;
  border-bottom: var(--hairline) solid var(--line-2);
}
ol.tasks-list .task-item:last-child { border-bottom: none; }
ol.tasks-list .task-item.task-closed { opacity: 0.7; }
.task-head {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  align-items: flex-start;
}
.task-title { font-weight: 500; font-size: 15px; }
.task-detail { margin-top: 4px; font-size: 15px; white-space: pre-wrap; line-height: 1.5; }
.task-meta { display: flex; gap: 6px; flex-wrap: wrap; flex-shrink: 0; }
.task-closed { margin-top: 8px; font-size: 15px; padding-left: 2px; color: var(--muted); }
details.task-close { margin-top: 10px; }
details.task-close > summary {
  cursor: pointer;
  color: var(--accent);
  font-size: 15px;
  padding: 4px 0;
  user-select: none;
}
details.task-close > summary:hover { text-decoration: underline; }
details.task-close[open] > form {
  margin-top: 10px;
  padding: 12px 14px;
  background: var(--tint);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius-s);
}
fieldset.outcomes {
  border: none;
  padding: 0;
  margin: 0 0 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 20px;
}
label.outcome {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  cursor: pointer;
  color: var(--ink);
}
label.outcome input[type="radio"] { cursor: pointer; }

/* -------------------- household picker -------------------- */

.hh-picker { position: relative; }
.hh-picker .hh-dropdown {
  position: absolute;
  top: calc(var(--control-h) + 4px);
  left: 0;
  right: 0;
  max-width: 100%;
  z-index: 20;
  list-style: none;
  padding: 4px 0;
  margin: 0;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  max-height: 240px;
  overflow-y: auto;
}
.hh-picker .hh-dropdown[hidden] { display: none; }
.hh-picker .hh-dropdown li { padding: 8px 12px; font-size: 15px; cursor: pointer; }
.hh-picker .hh-dropdown li:hover { background: var(--tint); }
.hh-picker .hh-dropdown li.empty { color: var(--muted); cursor: default; }

/* -------------------- audit detail -------------------- */

.audit-detail dl { display: grid; grid-template-columns: 130px 1fr; gap: 10px 14px; margin: 0; }
.audit-detail dt { color: var(--muted); font-weight: 400; }
.audit-detail dd { margin: 0; }
.audit-detail .payload {
  font-family: var(--font-mono);
  font-size: 15px;
  background: var(--tint);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  padding: 12px 14px;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.55;
  margin: 0;
}

/* -------------------- pager -------------------- */

/* Rendered at both ends of a list. Top instance sits above the
   flush-card table; bottom instance sits below it. Vertical rhythm
   comes from margin-top on both, so top pager is spaced from the
   listbar above it and the bottom pager is spaced from the table. */
.pager { display: flex; gap: 10px; align-items: center; margin-top: 16px; margin-bottom: 8px; }
.pager + .panel { margin-top: 8px; }

/* -------------------- modal -------------------- */

/* Shell-level modal primitive. One .modal-backdrop wraps one .modal.
   Every page uses the same skeleton; JS lives in admin_layout_footer
   (see AdminLayout.php). Two roles:
     - shared confirm modal at #df-modal-confirm, driven by dfConfirm()
     - page-specific content modals (e.g. gifts/new.php soft-credit
       picker at #sc-modal) opened via dfModal.open / .close.
   The backdrop uses the [hidden] attribute rather than a class so the
   markup reads "open by removing hidden", matching how the sidebar
   overlay and pager use display flip elsewhere. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 36, 0.55);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 48px 24px 24px;
  z-index: 100;
  overflow-y: auto;
}
.modal-backdrop[hidden] { display: none; }
/* Round 10a fix 2 (20 October 2026). Mockup 14 screen A: the
   Log activity and Add-a-task pop-ups open on top of the email
   pop-up when the user presses Mark as done or Add a task from
   inside it. `.df-cm-scrim` sits at z-index 1000; the shared
   partial lives on the same page, so both pop-ups bump above the
   compose scrim. The scrim behind must not be clickable while
   either is open — `position: fixed; inset: 0;` on `.modal-
   backdrop` already covers everything behind it and intercepts
   clicks, so this is a pure stacking fix. */
#log-activity-modal.modal-backdrop,
#add-task-modal.modal-backdrop { z-index: 1200; }
.modal {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-l);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
}
.modal-header {
  padding: 16px 20px;
  border-bottom: var(--hairline) solid var(--line-2);
}
.modal-header h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 500;
  line-height: 1.3;
}
.modal-body {
  padding: 16px 20px;
  font-size: 15px;
  line-height: 1.5;
}
.modal-body > :first-child { margin-top: 0; }
.modal-body > :last-child  { margin-bottom: 0; }

/* v0.11 wide-modal variant for long-form reading (Dashboard
   guide, and any future help panel). Wider than the standard
   .modal, and its body scrolls on its own when the content
   overflows the viewport so the header and Close button stay
   in view. Below 900px the modal takes the full viewport width
   minus a small margin. */
.modal.modal-wide {
  max-width: 780px;
}
.modal.modal-wide .modal-body {
  max-height: calc(100vh - 220px);
  overflow-y: auto;
  padding: 20px 24px;
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.modal-close-x {
  background: none;
  border: 0;
  cursor: pointer;
  font-size: 28px;
  line-height: 1;
  color: var(--muted);
  padding: 4px 8px;
  border-radius: 4px;
}
.modal-close-x:hover { color: var(--ink); background: rgba(0,0,0,0.04); }

/* Dashboard guide typography. Plain reading widths, section
   spacing, small headings, quote block for the exact wording. */
.guide-body { font-size: 15px; line-height: 1.6; color: var(--ink); }
.guide-body .guide-section + .guide-section {
  margin-top: 22px;
  padding-top: 22px;
  border-top: var(--hairline) solid var(--line-2);
}
.guide-body h3 {
  margin: 0 0 8px 0;
  font-size: 17px;
  font-weight: 500;
}
.guide-body h4 {
  margin: 16px 0 4px 0;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}
.guide-body p { margin: 0 0 10px 0; }
.guide-body ul.guide-list { margin: 4px 0 12px 0; padding-left: 22px; }
.guide-body ul.guide-list li { margin: 4px 0; }
.guide-body em { color: var(--ink); font-style: italic; }
.guide-body strong { font-weight: 500; }
.guide-body .guide-quote {
  margin: 8px 0;
  padding: 10px 14px;
  border-left: 3px solid var(--warning-border);
  background: var(--warning-bg);
  color: var(--warning-ink);
  font-style: italic;
}
.modal-actions {
  padding: 12px 20px;
  border-top: var(--hairline) solid var(--line-2);
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  flex-wrap: wrap;
}

/* -------------------- walkthrough (MFA enrolment) -------------------- */

.walkthrough .lede { font-size: 15px; line-height: 1.55; color: var(--ink); margin: 0 0 20px; }
.walkthrough ol.steps { list-style: none; padding: 0; margin: 0; counter-reset: step; }
.walkthrough ol.steps > li {
  counter-increment: step;
  position: relative;
  padding: 4px 0 24px 48px;
  border-bottom: var(--hairline) solid var(--line-2);
  margin-bottom: 22px;
}
.walkthrough ol.steps > li:last-child { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }
.walkthrough ol.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 2px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-weight: 500;
  font-size: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.walkthrough ol.steps h2 {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 500;
  padding: 0;
  border: none;
}
.walkthrough ol.steps p { margin: 0 0 12px; color: var(--ink); font-size: 15px; line-height: 1.55; }
.walkthrough .app-links { display: flex; gap: 10px; flex-wrap: wrap; margin: 4px 0; }
.qrcode-box {
  display: flex;
  justify-content: center;
  padding: 16px;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  margin: 8px 0 12px;
}
.qrcode-box svg,
.qrcode-box .df-qr { width: 220px; height: 220px; display: block; }
.walkthrough details.fallback { margin-top: 10px; padding: 6px 0 0; }
.walkthrough details.fallback > summary {
  cursor: pointer;
  color: var(--accent);
  font-size: 15px;
  padding: 4px 0;
  user-select: none;
}
.walkthrough details.fallback > summary:hover { text-decoration: underline; }
.walkthrough details.fallback .big-secret { margin-top: 10px; }

/* -------------------- backup codes -------------------- */

.ack-form { margin-top: 20px; padding-top: 18px; border-top: var(--hairline) solid var(--line-2); }
label.ack {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 15px;
  cursor: pointer;
  padding: 6px 0;
  color: var(--ink);
}
label.ack input[type="checkbox"] {
  margin-top: 3px;
  width: 18px;
  height: 18px;
  cursor: pointer;
  flex-shrink: 0;
}

.big-secret {
  font-family: var(--font-mono);
  font-size: 20px;
  letter-spacing: 3px;
  background: var(--tint);
  padding: 14px 16px;
  border-radius: var(--radius-s);
  border: var(--hairline) solid var(--line);
  text-align: center;
  word-break: break-all;
}
.backup-codes {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px 24px;
  font-family: var(--font-mono);
  font-size: 16px;
  background: var(--tint);
  padding: 16px 20px;
  border-radius: var(--radius-s);
  border: var(--hairline) solid var(--line);
  margin: 8px 0 14px;
}

.link { color: var(--accent); }
.link:hover { text-decoration: underline; }

/* -------------------- empty states -------------------- */

/* "An invitation, not an apology." Name the space, one line explaining
   it, and the action. */
.empty {
  padding: 32px 20px;
  text-align: center;
  color: var(--ink);
}
.empty h3 { margin: 0 0 6px; font-size: 16px; font-weight: 500; }
.empty p { margin: 0 0 12px; color: var(--muted); font-size: 15px; }

/* -------------------- data workshop --------------------
 * Screens: /admin/workshop/*.php
 * Spec: donorfix-4b-spec.md, especially "How it should feel" and
 *   section 3. The pair card is one thing on screen at a time, a
 *   powerhouse for a quiet stretch of work.
 * -------------------------------------------------------- */

/* Hub - module cards on /admin/workshop/index.php. Grid so a second
   module (imported-batch view, bulk edit, cleaning history) lands
   without any layout change. */
.workshop-modules {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
  margin-top: 4px;
}
.workshop-module {
  display: block;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  padding: 16px 18px;
  color: var(--ink);
  text-decoration: none;
  transition: border-color 120ms ease, background-color 120ms ease;
}
.workshop-module:hover {
  background: var(--accent-tint);
  border-color: var(--accent);
  text-decoration: none;
}
.workshop-module h3 {
  margin: 0 0 4px;
  font-size: 16px;
  font-weight: 500;
  color: var(--accent);
}
.workshop-module p {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.5;
}

/* The pair card - two donors side by side, differences marked.
   The reason line at the top is DuplicateRepo's own words for why
   these two are here; kept understated so the records themselves
   are what the operator reads. */
.dup-pair { padding: 18px 20px; }
.dup-reason {
  margin: 0 0 14px;
  padding-bottom: 12px;
  color: var(--muted);
  font-size: 15px;
  border-bottom: var(--hairline) solid var(--line-2);
}
.dup-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px 22px;
}
.dup-column {
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius-s);
  padding: 14px 16px;
  background: var(--surface-2);
}
.dup-column h3 {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.dup-column h3 a { color: var(--accent); }
.dup-id {
  color: var(--muted);
  font-size: 15px;
  font-weight: 400;
  font-family: var(--font-mono);
}

/* dt/dd grid: label left, value right, one row per field. Aligning
   both sides on the same field-list means the operator reads across
   left-to-right at each row. Values that differ pick up a subtle
   background tint - not danger red, not warning yellow; a difference
   is information, not an error. */
.dup-fields {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 6px 12px;
  margin: 0;
}
.dup-fields dt {
  color: var(--muted);
  font-size: 15px;
  padding-top: 2px;
}
.dup-fields dd {
  margin: 0;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  min-height: 24px;
}
.dup-fields dd.differs {
  background: var(--warning-bg);
  color: var(--warning-ink);
}

/* Three actions of equal weight - the spec is explicit that dismiss
   must be as easy as merge. Same button height, same padding, arranged
   left-to-right in the order Merge, Not the same person, Skip. The
   Merge is the accent-coloured primary; the other two are secondaries
   because the operator commits nothing destructive by clicking them. */
.dup-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: var(--hairline) solid var(--line-2);
  align-items: center;
}
.dup-action-form { margin: 0; }

/* Button that looks like a link. Used for the "N more pairs are less
   certain" line and every other "reveal / hide" pref control that
   ships as a form-post rather than a link (POST so the CSRF check
   applies uniformly). Matches .link's colour and hover; no border,
   no background, no button chrome. */
.link-btn {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-size: 15px;
  cursor: pointer;
  text-align: left;
}
.link-btn:hover { text-decoration: underline; }

/* A form nested inside a paragraph - used for "Hide them" / "Show
   them again" text that sits next to a note sentence. Inline layout
   so the button reads as part of the same sentence. */
.inline-form {
  display: inline;
  margin: 0;
}

/* Batch-size row - one label, one select. Compact so the queue's
   controls do not compete with the pair for attention. */
.batch-size-form {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
}
.batch-size-form label {
  color: var(--muted);
  font-size: 15px;
}
.batch-size-form select {
  min-height: 32px;
  height: 32px;
  padding: 0 8px;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  background: var(--surface-2);
  font: inherit;
  font-size: 15px;
}

/* Dismissed pairs list - visible on the queue page, not tucked in a
   settings screen. Rendered as a <details> so it stays compact until
   opened; the summary line shows the count in the operator's own
   words. */
.dismissed-summary {
  color: var(--accent);
  cursor: pointer;
  font-size: 15px;
}
.dismissed-summary:hover { text-decoration: underline; }
.dup-dismissed { margin-top: 12px; }
.dup-dismissed td:last-child { width: 1%; white-space: nowrap; }

/* Below 720px, stack the two donor columns vertically. A phone
   screen cannot fit both side by side and still be readable. */
@media (max-width: 720px) {
  .dup-columns { grid-template-columns: 1fr; }
}

/* -------------------- merge screen --------------------
 * Screen: /admin/contacts/merge.php
 * Spec: donorfix-4b-spec.md sections 4-6. The pair reads the same
 * way as the workshop queue's card (differences marked, side by
 * side) but adds the "which one are you keeping" chip and the
 * two-active-recurring block that halts the merge action.
 * -------------------------------------------------------- */

/* Reuse the .dup-* layout for the pair - identical shape, one file's
   worth of CSS. The extra pieces below are only for the keep/going
   chip, the alert block, and the field-pick table. */
.merge-pair { padding: 18px 20px; }
.merge-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px 22px;
}
.merge-column {
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius-s);
  padding: 14px 16px;
  background: var(--surface-2);
  transition: border-color 120ms ease, background-color 120ms ease;
}
.merge-column-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}
.merge-column-head h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.merge-column-head h3 a { color: var(--accent); }
.merge-id {
  color: var(--muted);
  font-size: 15px;
  font-weight: 400;
  font-family: var(--font-mono);
}

/* Chip that names the column's role in the merge - "Keeping this
   record" on the survivor, "Moving into the other" on the loser.
   Positioned next to the record name so the label is right where
   the eye lands. Accent tint for the kept side (this is the
   affirmative choice), muted line for the going side. */
.merge-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 15px;
  font-weight: 500;
  border: var(--hairline) solid var(--line);
  background: var(--surface-2);
  color: var(--muted);
  white-space: nowrap;
}
.merge-chip-keep {
  background: var(--success-bg);
  color: var(--success-ink);
  border-color: var(--success-border);
}
.merge-chip-going {
  background: var(--tint);
  color: var(--muted);
  border-color: var(--line-2);
}

/* Highlighted column when chosen. A 2px accent border rather than
   a filled background so the fields inside stay legible on eyes
   that struggle with mid-tone panels (see donorfix-design.md,
   "Built for older eyes"). The going side is barely dimmed - not
   greyed out; the operator still needs to read it to decide the
   field-copy choices. */
.merge-column-kept {
  border-color: var(--success-ink);
  border-width: 2px;
  padding: 13px 15px;   /* -1px each side to compensate for the +1px border */
}
.merge-column-going {
  background: var(--tint);
}

/* dt/dd grid - same shape as the workshop card's dup-fields. Kept
   as its own class in case the two diverge later. */
.merge-fields {
  display: grid;
  grid-template-columns: 108px 1fr;
  gap: 6px 12px;
  margin: 0;
}
.merge-fields dt {
  color: var(--muted);
  font-size: 15px;
  padding-top: 2px;
}
.merge-fields dd {
  margin: 0;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  min-height: 24px;
}
.merge-fields dd.differs {
  background: var(--warning-bg);
  color: var(--warning-ink);
}

.merge-column-actions {
  margin-top: 14px;
  padding-top: 12px;
  border-top: var(--hairline) solid var(--line-2);
  display: flex;
  justify-content: flex-end;
}

.merge-bottom-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}
.merge-bottom-spacer { flex: 1 1 auto; }

/* "What is about to move" list. Plain <ul>; kept tight because it
   is a data list, not narrative. */
.merge-move-list {
  margin: 8px 0 0;
  padding-left: 20px;
}
.merge-move-list li {
  font-size: 15px;
  color: var(--ink);
  line-height: 1.6;
}

/* Two-active-recurring block. This is the merge screen's most
   important stopping point - the case that could charge a donor
   twice a month unless someone acts. Highlighted in the warning
   palette to catch the eye; the copy inside is where the actual
   plainness lives. */
.merge-recurring-alert {
  background: var(--warning-bg);
  border-color: var(--warning-border);
}
.merge-recurring-alert h2 {
  border-bottom-color: var(--warning-border);
  color: var(--warning-ink);
}
.merge-recurring-rows {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 22px;
  margin: 10px 0 14px;
  padding: 12px;
  background: var(--surface-2);
  border-radius: var(--radius-s);
  border: var(--hairline) solid var(--warning-border);
  font-size: 15px;
  line-height: 1.5;
}
.merge-recurring-row strong { color: var(--ink); }
.merge-recurring-choice {
  border: 0;
  padding: 0;
  margin: 0;
}
.merge-recurring-choice legend {
  padding: 0;
  margin-bottom: 8px;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}
.merge-recurring-choice label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 0;
  font-size: 15px;
  line-height: 1.5;
  cursor: pointer;
}
.merge-recurring-choice label input {
  margin-top: 4px;
  flex-shrink: 0;
}
.merge-notify-line {
  margin-top: 6px;
  padding-left: 28px;
  color: var(--muted);
}

/* Fields-to-take table. One row per differing field where the
   loser actually has something. Column widths tuned so the two
   value cells get the room and the Field label + checkbox trim
   tight. */
.merge-fields-table th,
.merge-fields-table td { vertical-align: top; }
.merge-fields-table td:first-child { color: var(--muted); }
.merge-fields-table th:last-child,
.merge-fields-table td:last-child { width: 1%; white-space: nowrap; }
.merge-fields-table label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

/* Post-merge banner on the survivor's donor record. Not a flash
   (flashes are one-line and disappear on next request); this
   banner stays while ?merged=<id> is in the URL, so the operator
   can leave the page, come back to the survivor's record via
   the browser back button, and still see the Undo affordance
   until they navigate freely away. */
.merge-banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  background: var(--success-bg);
  border-color: var(--success-border);
}
.merge-banner-text {
  flex: 1 1 300px;
  font-size: 15px;
  color: var(--success-ink);
  line-height: 1.55;
}

/* "This record was merged into X" indicator, rendered on the
   loser's own view page when someone arrives at their URL
   directly. Muted rather than alarmed - the state is fine, just
   worth naming so the operator does not think this is the live
   record for the person. */
.merge-into-indicator {
  background: var(--tint);
  color: var(--muted);
  border-color: var(--line-2);
  font-size: 15px;
  line-height: 1.55;
}

@media (max-width: 720px) {
  .merge-columns,
  .merge-recurring-rows { grid-template-columns: 1fr; }
}

/* -------------------- cleaning history --------------------
 * Screen: /admin/workshop/cleaning-history.php
 * Spec: donorfix-4b-spec.md sections 5 and 6. A record of every
 * merge, newest first, with the current state of each on the same
 * row. Same table styling as the rest of the workshop; only the
 * per-cell composition varies.
 * ---------------------------------------------------------- */

.cleaning-history th,
.cleaning-history td {
  vertical-align: top;
  line-height: 1.5;
}
.cleaning-history th:first-child,
.cleaning-history td:first-child { white-space: nowrap; }
.cleaning-history th:last-child,
.cleaning-history td:last-child {
  /* Status cell holds either short text or one small button; either
     way it does not need to grow past what its content requires. */
  width: 1%;
  white-space: normal;
  min-width: 220px;
  max-width: 340px;
}

.ch-date { font-weight: 500; }
.ch-pair { font-size: 15px; line-height: 1.55; }
.ch-pair a { color: var(--accent); }
.ch-pair .mono { font-size: 13px; padding: 1px 4px; }
.ch-arrow {
  color: var(--muted);
  padding: 0 6px;
}
.ch-by { font-size: 15px; margin-top: 2px; }

/* Undone rows are muted so an active row stands out at a glance.
   Locked rows keep normal contrast - the reason text is what a
   reader needs to see, and dimming it would hide the answer. */
.ch-row-undone .ch-pair,
.ch-row-undone .ch-date { color: var(--muted); }
.ch-row-undone .ch-pair a { color: var(--muted); }

/* -------------------- my work --------------------
 * Screen: /admin/my-work.php
 * Spec: donorfix-task-stage-spec.md section 4.
 * A person's tasks grouped by due date, plus a worklists section
 * that lands empty until Stage 4C.
 * -------------------------------------------------- */

.mywork-group { margin-top: 14px; }
.mywork-group:first-of-type { margin-top: 0; }
.mywork-group-heading {
  font-size: 15px;
  font-weight: 500;
  color: var(--muted);
  margin: 0 0 8px;
  padding-bottom: 6px;
  border-bottom: var(--hairline) solid var(--line-2);
}

.mywork-tasks {
  list-style: none;
  margin: 0;
  padding: 0;
}
.mywork-task {
  padding: 10px 0;
  border-bottom: var(--hairline) solid var(--line-2);
}
.mywork-task:last-child { border-bottom: 0; }

.mywork-task-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.mywork-task-title {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.5;
}
.mywork-task-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 15px;
}
.mywork-task-donor {
  color: var(--accent);
  font-size: 15px;
}
.mywork-pill {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  border-radius: 999px;
  border: var(--hairline) solid var(--line);
  background: var(--tint);
  color: var(--muted);
  font-size: 15px;
  white-space: nowrap;
}

.mywork-task-detail {
  color: var(--muted);
  font-size: 15px;
  line-height: 1.55;
  margin-top: 4px;
  white-space: pre-wrap;
}
.mywork-task-actions {
  margin-top: 8px;
}

/* Close-task modal outcome radios. One per line so the labels stay
   readable at 15px on a narrow modal. */
.mywork-outcomes {
  border: 0;
  padding: 0;
  margin: 0;
}
.mywork-outcomes legend {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  padding: 0;
  margin-bottom: 8px;
}
.mywork-outcome {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  font-size: 15px;
  line-height: 1.5;
  cursor: pointer;
}
.mywork-outcome input { flex-shrink: 0; }

/* -------------------- Stage 4C dashboard (spec v0.2) -------------------- *
 * Layout matches mockup-dashboard.html. Nothing below 15px. No
 * colour except the accent (links / filled primary button) and the
 * overdue-date orange on the tasks list.
 *
 * Section gap: 14px between the top strip, Needs attention, and
 * Scheduled tasks. Driven by margin-top here rather than by a shell
 * flex-gap so a page can drop or reorder a section without a shell
 * change. The very first section on the page keeps no extra margin
 * because the shell already sits it below the page title.
 */

.dash-outage {
  background: var(--warning-bg);
  border: var(--hairline) solid var(--warning-border);
  border-radius: var(--radius);
  padding: 12px 17px;
  color: var(--ink);
  font-size: 15px;
  line-height: 1.5;
  margin-bottom: 14px;
}
.dash-outage-title { font-weight: 500; margin-bottom: 4px; }

/* --------------------------------------------------------------
   Shared navy band (.df-band-*)
   --------------------------------------------------------------
   Added 26 September 2026 for the Dashboard restyle. Same visual
   as the Financials verdict band, but general-purpose: one large
   figure on the left (`.df-band-lead`) with up to three darker
   tiles on the right (`.df-band-tile`). Contacts, Gifts, Recurring
   and Pledges will reuse this component.

   Colour tokens: --band-navy, --band-inner, --band-eyebrow,
   --band-body, --band-label. The Financials page's own
   `.fn-verdict` is unchanged and keeps its own class family; this
   is the reusable version.

   Interactive tiles: wrap each `.df-band-lead` / `.df-band-tile`
   in an <a> and the anchor picks up the tile styling (see
   a.df-band-lead / a.df-band-tile below). Non-interactive tiles
   just use the div directly.
   -------------------------------------------------------------- */
.df-band {
  background: var(--band-navy);
  color: #ffffff;
  border-radius: var(--radius);
  padding: 26px 28px;
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) repeat(3, minmax(0, 1fr));
  gap: 14px;
  align-items: stretch;
}
.df-band-lead .df-band-k,
.df-band-tile .df-band-k {
  color: var(--band-eyebrow);
  font-size: 15px;
}
.df-band-lead .df-band-v {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.5px;
  margin: 4px 0 2px;
  line-height: 1.15;
}
.df-band-lead .df-band-d {
  color: var(--band-body);
  font-size: 15px;
}
/* Positive delta reads in the pale-green tick tone. When the
   figure is behind last year, .df-band-d.is-behind uses the
   plain band body colour (same as the .df-band-d default) — see
   the Dashboard's raised-line render for the is-ahead / is-behind
   split. */
.df-band-lead .df-band-d.is-ahead {
  color: var(--success-tick);
}
.df-band-tile {
  background: var(--band-inner);
  border-radius: 10px;
  padding: 16px 18px;
}
.df-band-tile .df-band-v {
  font-size: 22px;
  font-weight: 700;
  margin: 2px 0;
  line-height: 1.25;
}
.df-band-tile .df-band-d {
  color: var(--band-label);
  font-size: 15px;
}

/* Anchor variant — an <a class="df-band-lead"> or <a class="df-band-tile">
   picks up the tile appearance without underlining or colour shift. */
a.df-band-lead,
a.df-band-tile {
  color: inherit;
  text-decoration: none;
  display: block;
}
a.df-band-lead:hover,
a.df-band-tile:hover {
  text-decoration: none;
}
a.df-band-tile:hover {
  filter: brightness(1.06);
}

/* Narrow screens: the large figure sits on top and the three
   tiles stack under it two-wide, then single-column at phone
   width. Rob's rule: "on a narrow screen the band stacks: the
   large figure on top, the three tiles under it". */
@media (max-width: 900px) {
  .df-band {
    grid-template-columns: 1fr 1fr;
  }
  .df-band-lead {
    grid-column: 1 / -1;
  }
}
@media (max-width: 520px) {
  .df-band {
    grid-template-columns: 1fr;
  }
}

/* List-page band variant. Two columns — the figures on the left,
   the search area on the right. The figures are one large count
   plus a "under it" line with any secondary totals. The search
   area holds the search box, the Open filters / Close filters
   toggle button (with an "N applied" count badge), the applied
   filter chips, and the Clear-all link. On a narrow screen the
   two columns stack. Used on Contacts, Gifts, Recurring and
   Pledges list pages from 26 September 2026. */
.df-band-list {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: 24px;
  align-items: center;
}
.df-band-list .df-band-lead .df-band-v {
  font-size: 32px;
}
.df-band-search {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  justify-content: flex-end;
}
.df-band-search .df-band-search-input {
  flex: 1;
  min-width: 220px;
  max-width: 420px;
  padding: 9px 13px;
  border-radius: var(--radius-s);
  border: 1px solid var(--band-input-border);
  background: #ffffff;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  min-height: 38px;
}
.df-band-toggle {
  height: 38px;
  padding: 0 14px;
  background: transparent;
  border: 1px solid var(--band-toggle-border);
  border-radius: var(--radius-s);
  color: #ffffff;
  cursor: pointer;
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.df-band-toggle:hover { background: var(--band-inner); }
.df-band-toggle-count {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: #ffffff;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  margin-left: 6px;
}
.df-band-chips {
  width: 100%;
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  flex-wrap: wrap;
  font-size: 14px;
  color: var(--band-eyebrow);
}
.df-band-chip {
  background: var(--band-inner);
  color: #ffffff;
  padding: 3px 12px;
  border-radius: 999px;
  text-decoration: none;
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.df-band-chip:hover { text-decoration: none; filter: brightness(1.1); color: #ffffff; }
.df-band-chip-x { opacity: 0.8; }
.df-band-clear {
  color: var(--band-eyebrow);
  font-size: 14px;
  text-decoration: underline;
  cursor: pointer;
  align-self: center;
}
.df-band-clear:hover { color: #ffffff; }

@media (max-width: 900px) {
  .df-band-list { grid-template-columns: 1fr; }
  .df-band-search { justify-content: flex-start; }
  .df-band-chips { justify-content: flex-start; }
}

/* Filter panel — the white card that opens under the list band.
   Beige strip at the top ("Filters" + "N in use") and beige foot
   ("Apply" + "Reset"). The panel's field body reuses the existing
   `.filter-panel-body` grid so the field markup on each list page
   doesn't have to change. */
.df-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  margin-top: 14px;
  overflow: hidden;
}
.df-panel[hidden] { display: none; }
.df-panel-strip {
  background: var(--surface-1);
  border-bottom: var(--hairline) solid var(--line);
  padding: 12px 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--muted);
  font-size: 15px;
}
.df-panel-strip b { color: var(--ink); font-weight: 600; }
.df-panel-foot {
  display: flex;
  gap: 10px;
  padding: 14px 24px;
  border-top: var(--hairline) solid var(--line);
  background: var(--surface-1);
}
/* Override the older `form.filters .filter-panel-body { display: none }`
   rule (23 September pattern) whenever the panel body sits inside a
   `.df-panel`. Selector prefixed with `form.filters` so its specificity
   (0,3,1) beats the default hide rule's (0,2,1) — a plain
   `.df-panel .filter-panel-body { display: block }` was outweighed
   and the fields stayed hidden on the live server.
   26 September 2026 fix. */
form.filters .df-panel .filter-panel-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px 24px;
  margin-top: 0;
}

.dash-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}
.dash-attention  { margin-top: 14px; }
.dash-tasks      { margin-top: 14px; }
.dash-health     { margin-top: 14px; }

/* v0.9 recurring payment health status. Same card shell as
   .dash-attention / .dash-tasks so the section reads as a peer,
   not a special surface. v0.9.1: tighter marks, marks and last-
   run summary sit side-by-side on one row rather than stacked.
   Only the labels under each mark break the row visually - the
   date sits directly under a small tick or cross. */
.dash-health {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius);
  overflow: hidden;
}
.dash-health-head {
  padding: 10px 17px;
  border-bottom: var(--hairline) solid var(--line-2);
  font-size: 16px;
  font-weight: 500;
}
.dash-health-body {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 12px 17px;
  flex-wrap: wrap;
}
.dash-health-runs {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  flex-wrap: nowrap;
}
.dash-health-run {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 34px;
  padding: 2px 0;
}
.dash-health-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  margin-bottom: 2px;
}
/* Circle background per state (26 September 2026 restyle). Pale
   green for a passing run, pale beige for no-record / not-due-yet,
   pale red for a missed run. The SVG mark inside each circle keeps
   its own stroke colour, set on the page. */
.dash-health-run.is-pass        .dash-health-mark { background: var(--success-bg); }
.dash-health-run.is-no-record   .dash-health-mark { background: var(--surface-1); }
.dash-health-run.is-not-due-yet .dash-health-mark { background: var(--surface-1); }
.dash-health-run.is-missed      .dash-health-mark { background: var(--danger-bg); }
.dash-health-date  { font-size: 12px; color: var(--muted); line-height: 1.2; }
.dash-health-label {
  font-size: 11px;
  line-height: 1.2;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.dash-health-run.is-pass        .dash-health-label { color: var(--success-ink); }
.dash-health-run.is-missed      .dash-health-label { color: var(--danger-ink); }
.dash-health-run.is-no-record   .dash-health-label { color: #9aa0aa; }
.dash-health-run.is-not-due-yet .dash-health-label { color: #9aa0aa; }
.dash-health-summary { font-size: 14px; }
.dash-card {
  display: block;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius);
  padding: 14px 17px;
  color: var(--ink);
  text-decoration: none;
}
.dash-card:hover { text-decoration: none; }
.dash-card-label { color: var(--muted); font-size: 15px; line-height: 1.4; }
.dash-card-value { font-size: 22px; font-weight: 500; line-height: 1.3; margin-top: 2px; }
.dash-card-sub   { color: var(--muted); font-size: 15px; line-height: 1.4; margin-top: 2px; }
.dash-card-sub.is-ahead { color: var(--success-ink); }

.dash-attention {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius);
  overflow: hidden;
}
.dash-attention-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 10px 17px;
  font-size: 15px;
  border-bottom: var(--hairline) solid var(--line-2);
}
.dash-attention-head-title { font-size: 16px; font-weight: 500; }
.dash-attention-head-summary { font-size: 15px; }
.dash-attention-head-summary strong { font-weight: 500; }
.dash-attention-head-summary .muted { color: var(--muted); }
.dash-attention-empty { padding: 14px 17px; color: var(--muted); }

/* v0.10 Group 3, item 3: duplicate-check warning line at the
   top of Needs attention. Sits inside the card so it reads as
   part of the block, not a separate banner. Amber accent to
   distinguish from the (red) outage banner above the top strip
   but never dominate the section. Button aligns right. */
.dash-attention-dupcheck {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 17px;
  background: var(--warning-bg);
  border-bottom: var(--hairline) solid var(--warning-border);
  font-size: 15px;
  color: var(--warning-ink);
}
.dash-attention-dupcheck-text { flex-grow: 1; min-width: 0; }
.dash-attention-row {
  /* 26 September 2026 restyle — a four-column grid with a
     coloured row label on the left. Was a plain flex row.
     Columns:
       1. label (150px):   coloured pill + area line under it
       2. text  (fluid):   statement + optional why line
       3. owner (260px):   owner name + progress bar OR
                           "Not assigned". Widened from 190 to
                           260 on 25 October 2026 — the progress
                           text "N on the list so far · M done"
                           (nowrap) needs ~240px at 15px font,
                           and 190 let it overflow under the
                           Open list button at every width.
       4. button (110px):  Open list / Assign
     The receipts-to-mail plain row narrows to two columns via
     .dash-attention-row-plain below. */
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr) 260px 110px;
  gap: 16px;
  align-items: center;
  padding: 14px 17px;
  border-bottom: var(--hairline) solid var(--line-2);
  font-size: 15px;
  line-height: 1.5;
}
.dash-attention-row:last-child { border-bottom: 0; }
.dash-attention-label {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.dash-attention-label-area {
  color: var(--muted);
  font-size: 15px;
}
/* The plain row (Receipts to mail) has no owner or label column;
   its sentence stretches into the space. */
.dash-attention-row-plain {
  grid-template-columns: 150px minmax(0, 1fr) 110px;
}
.dash-attention-row-plain .dash-attention-open,
.dash-attention-row-plain .dash-attention-open-plain {
  grid-column: -2 / -1;
}
.dash-attention-text { min-width: 0; }
.dash-attention-text strong { font-weight: 500; }
.dash-attention-note { color: var(--muted); }

@media (max-width: 900px) {
  .dash-attention-row {
    grid-template-columns: 1fr;
    gap: 8px;
  }
  .dash-attention-row-plain {
    grid-template-columns: 1fr;
  }
}

/* "Assigned to <owner>" column. Fixed 190px per mockup so every
   row's button lines up regardless of owner-name length. Muted
   grey when the row is not assigned.
   Assigned rows use `.dash-attention-owner-col` — owner name on
   top and a thin progress bar with "N of M done" underneath. */
.dash-attention-owner,
.dash-attention-owner-col {
  width: 260px;
  flex-shrink: 0;
}
.dash-attention-owner {
  white-space: nowrap;
  color: var(--ink);
}
.dash-attention-owner.is-none { color: var(--muted); }
.dash-attention-owner-name { white-space: nowrap; color: var(--ink); }
.dash-attention-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
}
.dash-attention-bar {
  flex-grow: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--tint);
  overflow: hidden;
}
.dash-attention-bar-fill {
  height: 6px;
  background: var(--success-ink);
}
.dash-attention-progress-text {
  color: var(--muted);
  white-space: nowrap;
  font-size: 15px;
}

/* Buttons: Assign (outline blue) and Open list (filled blue).
   Same 34px height and 110px width so the row lands the same
   whichever button is on it. */
.dash-attention-assign,
.dash-attention-open {
  height: 34px;
  width: 110px;
  flex-shrink: 0;
  border: var(--hairline) solid var(--accent);
  border-radius: var(--radius-s);
  font-size: 15px;
  white-space: nowrap;
  cursor: pointer;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
.dash-attention-assign { background: #ffffff; color: var(--accent); }
.dash-attention-assign:hover { background: var(--accent-tint); }
.dash-attention-open   { background: var(--accent); color: #ffffff; }
.dash-attention-open:hover { background: var(--accent-2); color: #ffffff; text-decoration: none; }

/* v0.4 removes the grey footer strip - the summary now sits on
   the Needs attention header (see .dash-attention-head above).
   The .dash-attention-footer rule is kept in the stylesheet only
   as a comment; there is no more markup that uses it. */

/* attention.php + worklists/view.php reuse this page header. */
.dash-attention-head-page {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
.dash-attention-head-page h2 {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
}

/* Scheduled tasks: full-width card. */
.dash-tasks {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius);
  overflow: hidden;
}
.dash-tasks-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 17px;
  border-bottom: var(--hairline) solid var(--line-2);
}
.dash-tasks-title { font-size: 16px; font-weight: 500; }
.dash-tasks-empty { padding: 14px 17px; color: var(--muted); }
.dash-tasks-group {
  padding: 5px 17px;
  background: var(--surface-1);
  color: var(--muted);
  border-bottom: var(--hairline) solid var(--line-2);
  font-size: 15px;
}
/* Task row: title, donor, date, type, button. Each in its own
   column with a 12px gap so the date never runs into the type. */
.dash-task-row {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 3fr) 72px 88px 84px;
  gap: 12px;
  align-items: center;
  padding: 6px 17px;
  border-bottom: var(--hairline) solid var(--line-2);
  font-size: 15px;
  line-height: 1.4;
}
.dash-task-row:last-child { border-bottom: 0; }
.dash-task-title,
.dash-task-donor {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dash-task-nodonor { color: var(--muted); }
.dash-task-due { white-space: nowrap; color: var(--ink); }
.dash-task-due.is-overdue { color: var(--danger-ink); }
.dash-task-category { color: var(--muted); white-space: nowrap; }
.dash-task-complete {
  height: 28px;
  padding: 0 10px;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  background: #ffffff;
  color: var(--ink);
  font-size: 15px;
  cursor: pointer;
  font-family: inherit;
}
.dash-task-complete:hover { background: var(--tint); }

/* Piece 5: the Complete button is replaced by a Start link that
   goes to the donor's Tasks tab, scrolled to that task. Same
   footprint as the old Complete button so the row grid still
   lines up. */
.dash-task-start {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 28px;
  padding: 0 10px;
  border: var(--hairline) solid var(--accent);
  border-radius: var(--radius-s);
  background: #ffffff;
  color: var(--accent);
  font-size: 15px;
  text-decoration: none;
}
.dash-task-start:hover { background: var(--accent-tint); text-decoration: none; }
.dash-task-start-empty { display: inline-block; }

/* -------- Worklist view page (v0.4, mockup-list-todo/done) -------- */

.wl-title-block { margin-bottom: 4px; }
.wl-title-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}
.wl-title {
  margin: 0;
  font-size: 23px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 10px;
}
.wl-title-right {
  display: flex;
  align-items: center;
  gap: 12px;
}
.wl-description {
  margin-top: 2px;
  color: var(--muted);
}
.muted { color: var(--muted); }

/* Progress card. */
.wl-progress {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius);
  padding: 14px 17px;
  display: flex;
  align-items: center;
  gap: 28px;
  margin-top: 14px;
}
.wl-progress-headline { display: flex; flex-direction: column; }
.wl-progress-figure { font-size: 22px; font-weight: 500; }
.wl-progress-last { font-size: 15px; }
.wl-progress-bar {
  flex-grow: 1;
  height: 10px;
  border-radius: 5px;
  background: var(--tint);
  overflow: hidden;
}
.wl-progress-bar-fill { height: 10px; background: var(--success-ink); }
.wl-progress-counts { display: flex; gap: 22px; }
.wl-progress-count { display: flex; flex-direction: column; }

/* Panel with the two tabs. */
.wl-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius);
  overflow: hidden;
  margin-top: 14px;
}
.wl-tabs-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 17px;
  border-bottom: var(--hairline) solid var(--line-2);
  gap: 12px;
}
.wl-tabs {
  display: flex;
  gap: 4px;
  background: var(--tint);
  border-radius: 8px;
  padding: 3px;
}
.wl-tab {
  height: 32px;
  padding: 0 14px;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}
.wl-tab.is-active {
  background: #ffffff;
  color: var(--ink);
  font-weight: 500;
  border: var(--hairline) solid var(--line-2);
}
.wl-tab:hover { text-decoration: none; }

.wl-tbl {
  display: grid;
  gap: 16px;
  align-items: center;
  padding: 10px 17px;
}
.wl-tbl-head {
  padding: 8px 17px;
  color: var(--muted);
  border-bottom: var(--hairline) solid var(--line-2);
}
.wl-tbl-row {
  border-bottom: var(--hairline) solid var(--line-2);
}
.wl-tbl-row:last-child { border-bottom: 0; }
.wl-tbl-todo    { grid-template-columns: minmax(0, 4fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 4fr); }
.wl-tbl-pledges { grid-template-columns: minmax(0, 4fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 4fr); }
.wl-tbl-done    { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr) minmax(0, 2fr) 90px minmax(0, 3fr) minmax(0, 3fr); }
.wl-tbl-num  { text-align: right; }
.wl-tbl-open-task { color: var(--ink); line-height: 1.35; }
.wl-tbl-open-task .muted { margin-left: 4px; }
.wl-empty { padding: 14px 17px; margin: 0; }

.wl-outcome-chip {
  display: inline-block;
  padding: 1px 10px;
  border-radius: 999px;
  background: var(--tint);
  border: var(--hairline) solid var(--line-2);
  color: var(--ink);
  font-size: 15px;
}
.wl-comment-cell {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* -------- Complete-task modal (mockup-complete-task) -------- */

.modal.complete-modal { width: 480px; max-width: 92vw; }
.complete-modal .modal-header {
  padding-bottom: 6px;
}
.complete-modal-sub {
  color: var(--muted);
  margin-top: 2px;
  font-size: 15px;
}
.complete-modal-dot { color: var(--muted); }
.complete-modal-comment {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 12px;
}
.complete-modal-comment textarea {
  font-family: inherit;
  font-size: 15px;
  padding: 8px 10px;
  border: var(--hairline) solid var(--line);
  border-radius: 6px;
  resize: vertical;
  min-height: 60px;
}
.complete-modal-comment-hint {
  color: var(--muted);
  font-size: 15px;
}

/* Comment printed under a completed task on the donor record. */
.task-comment {
  margin-top: 6px;
  color: var(--muted);
  white-space: pre-wrap;
  line-height: 1.5;
}

/* Muted pill variant for the "Anyone" chip on system tasks. */
.pill.muted { color: var(--muted); }

/* -------- Scheduled tasks page (Piece 5, mockup-scheduled-tasks) -------- *
 * Class prefix `stask-*` so the old task-stage `.mywork-*` rules
 * (unused after the Piece 5 rewrite) do not collide. Layout is one
 * card per section - the open tasks table with group headings
 * inline, then a Recently completed table below.
 */

.stask-title-block { margin-bottom: 4px; }
.stask-title-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
}
.stask-title {
  margin: 0;
  font-size: 23px;
  font-weight: 500;
}

.stask-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius);
  overflow: hidden;
  margin-top: 14px;
}
.stask-panel-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 10px 17px;
  border-bottom: var(--hairline) solid var(--line-2);
}
.stask-panel-title { font-size: 16px; font-weight: 500; }
.stask-empty { padding: 14px 17px; margin: 0; color: var(--muted); }

.stask-tbl {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 3fr) 110px 80px 96px;
  gap: 14px;
  align-items: center;
  padding: 5px 17px;
  font-size: 15px;
  line-height: 1.4;
}
.stask-tbl-head {
  padding: 6px 17px;
  color: var(--muted);
  border-bottom: var(--hairline) solid var(--line-2);
}
.stask-tbl-row {
  border-bottom: var(--hairline) solid var(--line-2);
}
.stask-tbl-row:last-child { border-bottom: 0; }

.stask-group {
  padding: 5px 17px;
  background: var(--surface-1);
  color: var(--muted);
  border-bottom: var(--hairline) solid var(--line-2);
  font-size: 15px;
}

/* First column: task title + optional muted detail on the same
   line, ellipsis at the end. */
.stask-task-title-cell {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stask-task-title { color: var(--ink); }
.stask-task-detail { color: var(--muted); }
.stask-task-donor {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--accent);
}
.stask-task-nodonor { color: var(--muted); }
.stask-task-type { color: var(--muted); white-space: nowrap; }
.stask-task-due  { color: var(--ink); white-space: nowrap; }
.stask-task-due.is-overdue { color: var(--danger-ink); }

.stask-task-start {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  padding: 0 12px;
  border: var(--hairline) solid var(--accent);
  border-radius: var(--radius-s);
  background: #ffffff;
  color: var(--accent);
  font-size: 15px;
  text-decoration: none;
}
.stask-task-start:hover { background: var(--accent-tint); text-decoration: none; }
.stask-task-start-empty { display: inline-block; }

/* Recently completed table: different grid than the open table. */
.stask-tbl-done {
  grid-template-columns: minmax(0, 4fr) minmax(0, 3fr) minmax(0, 2fr) minmax(0, 2fr) 80px minmax(0, 4fr);
  padding: 6px 17px;
}
.stask-tbl-done.stask-tbl-head { padding: 6px 17px; }
.stask-comment-cell {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* -------- v0.5 Piece 6: do-not-contact + log activity -------- */

.dnc-indicator {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  padding: 12px 17px;
  background: var(--warning-bg);
  border: var(--hairline) solid var(--warning-border);
  border-radius: var(--radius);
}
.dnc-indicator-body {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.dnc-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--danger-ink);
  color: #ffffff;
  font-weight: 500;
  font-size: 15px;
}
.dnc-note { color: var(--danger-ink); }

/* Tasks tab: Add a task and Log activity sit side by side. Below
   about 900px they stack. The muted 'Add a task' panel with the
   DNC explanation uses the same shell as the enabled form so the
   surrounding layout does not jump when the flag lands. */
.tasks-panel-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  align-items: start;
}
.tasks-panel-half { min-width: 0; }
.tasks-add-disabled .note { color: var(--muted); }
@media (max-width: 900px) {
  .tasks-panel-row { grid-template-columns: 1fr; }
}

/* Round 6c fix 4 (16 October 2026): Tasks tab head. Two buttons,
   right-aligned, matching mockup 12-contact-tasks-popups.html. */
.tasks-tab-head {
    display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap;
    margin: 0 0 12px;
}
.tasks-tab-dnc { color: #667080; margin: 0; }

/* Complete-task modal: the reschedule due-date slot revealed by
   the shell script. Same shape as the comment slot below it, so
   the reveal does not shove other fields around. */
.complete-modal-reschedule {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 12px;
}
.complete-modal-reschedule[hidden] { display: none; }
.complete-modal-reschedule input {
  font-family: inherit;
  font-size: 15px;
  padding: 8px 10px;
  border: var(--hairline) solid var(--line);
  border-radius: 6px;
}

/* v0.10 Group 3, item 2: companion ticks inside the Complete
   task modal. One tick per open task on the same donor. The
   whole block hides under Task rescheduled - see the shell
   script (AdminLayout footer JS). */
.complete-modal-companions {
  margin-top: 14px;
  padding-top: 12px;
  border-top: var(--hairline) solid var(--line-2);
}
.complete-modal-companions[hidden] { display: none; }
.complete-modal-companions-label {
  color: var(--muted);
  font-size: 14px;
  margin-bottom: 6px;
}
.complete-modal-companion {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  font-size: 15px;
}
.complete-modal-companion input[type="checkbox"] { margin: 0; }

/* v0.10 Group 3, item 1: "Open task" hint beneath a
   needs-attention system task on the donor record. Same content
   as the Open task column on the list page; muted, one line. */
.task-open-hint {
  margin-top: 4px;
  font-size: 14px;
  line-height: 1.4;
}
.task-open-hint span { margin-left: 4px; }

/* -------- v0.6 Piece 7 -------- */

/* Per-tab sort switch on the donor record. Sits above each list
   (Timeline, Gifts, Notes, Tasks). Active side is a plain span in
   ink; inactive is an accent link. */
.tab-sort {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 10px;
  font-size: 15px;
}
.tab-sort .muted { color: var(--muted); }
.tab-sort .sort-active { color: var(--ink); font-weight: 500; }
.tab-sort a { color: var(--accent); }

/* "Logged activity" pill on the Tasks tab and on the Scheduled
   tasks Recently-completed table. Distinct from the neutral pill
   so a past-event log reads as different from a scheduled task
   that got done. */
.pill.pill-logged {
  background: var(--info-bg);
  border-color: var(--info-border);
  color: var(--info-ink);
}

/* -------- v0.7 Group 2: receipts pages -------- */

/* Receipts to mail. Reuses the .wl-tbl grid table pattern from
   the worklist page. Two new column-count variants; every other
   style below is layout for the surrounds (cutoff line, tab-row
   right side, ticked-count bars). */
.wl-tbl-mail-waiting {
  grid-template-columns: 26px minmax(0, 3fr) minmax(0, 3fr) minmax(0, 5fr) 110px 90px 60px;
}
.wl-tbl-mail-mailed {
  grid-template-columns: minmax(0, 3fr) minmax(0, 3fr) 110px 100px minmax(0, 3fr) 60px 80px;
}

/* Cutoff form sits on its own line under the description, on the
   left. The date input is only as wide as it needs to be so the
   line reads compactly next to the Save button. */
.wl-mail-cutoff {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.wl-mail-cutoff label { color: var(--muted); white-space: nowrap; }
.wl-mail-cutoff input[type="date"] { width: auto; min-width: 0; }
.wl-mail-cutoff-static { margin-top: 8px; white-space: nowrap; }

/* Right side of the wl-tabs-head: address controls and the
   "N have no address" note. Nowrap on the note so it reads
   across on one line beside the dropdown; gap keeps proper
   spacing between the note and the label. */
.wl-tabs-head-right {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}
.wl-mail-no-addr-note { white-space: nowrap; }

/* Ticked-row bars above and below the table. Same shape both
   times: N ticked on the left, the two action buttons on the
   right. Top bar has a subtle background; bottom bar sits
   inside the panel with just a top rule. */
.wl-mail-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 10px 17px;
  background: var(--surface-1);
  border-bottom: var(--hairline) solid var(--line-2);
}
.wl-mail-toolbar-bottom {
  background: transparent;
  border-bottom: 0;
  border-top: var(--hairline) solid var(--line-2);
}
.wl-mail-toolbar-actions { display: flex; gap: 10px; align-items: center; }

/* Row-level bits: print link right-aligned, undo cell
   right-aligned, "No address" marker in overdue orange. */
.wl-tbl-mail-waiting .wl-mail-print,
.wl-tbl-mail-mailed  .wl-mail-print { text-align: right; white-space: nowrap; }
.wl-tbl-mail-mailed  .wl-mail-undo  { text-align: right; white-space: nowrap; }
.wl-mail-noaddr { color: var(--danger-ink); }

/* Plain Needs-attention row (Piece 5): no owner column, no
   progress bar - just the sentence and Open list. Same padding
   as the other rows so the card reads consistently. Uses the
   existing .dash-attention-row flex layout; nothing here changes
   the four assigned/not-assigned rows. */
.dash-attention-row-plain .dash-attention-text { flex-grow: 1; }

/* Small DNC badge on donor-list rows. Same wording as the record
   header badge, at 15px per the design rule "nothing below
   15px". Slightly reduced padding + line-height so it sits
   inline in the name cell without pushing the row height. */
.dnc-badge-sm {
  display: inline-block;
  padding: 0 10px;
  margin-left: 8px;
  border-radius: 999px;
  background: var(--danger-ink);
  color: #ffffff;
  font-size: 15px;
  font-weight: 500;
  line-height: 22px;
  vertical-align: middle;
}

/* Contact-role badges.

   Four roles - Prospect, Donor, Volunteer, Member - each with a
   tinted pill that reads at a glance. Uses only the four tint
   pairs (bg / ink) Rob named; no icons here yet because the
   Tabler icon font is not loaded in the admin and Rob's spec
   forbids loading a web font or reaching a third-party host.
   When Rob decides on an icon source, the .role-badge-icon slot
   inside each pill is where they slot in.

   Two placements:
   - Contact record (donors/view.php): JS-injected as inline
     children of .ph-title h1 with the .role-badge-inline modifier.
     Sit on the name's baseline, 14px, no additional row height.
   - Contact list (donors/index.php): rendered server-side inline
     in the name cell with the .role-badge-sm modifier, 12px, next
     to the DNC pill.

   Order in the DOM matches Rob's spec: Prospect, Donor,
   Volunteer, Member. */
.role-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  vertical-align: middle;
}
.role-badge-sm {
  margin-left: 8px;
}
/* Inline badges appended into the H1 by donors/view.php. Sit on
   the name's baseline (baseline is the default for inline spans;
   the alignment override on the parent H1 does not apply to the
   badge's own text baseline) and never make the H1's row taller.
   line-height: 1 keeps the badge's own line-box inside the H1's
   without pushing the row down; the 3px padding gives the tinted
   pill visible height without adding to the row. Font is 14px on
   this screen only per spec; the list-page .role-badge-sm keeps
   the 12px default. */
.role-badge-inline {
  font-size: 14px;
  line-height: 1;
  padding: 3px 10px;
  vertical-align: baseline;
}
.role-badge .role-badge-icon {
  width: 14px;
  height: 14px;
  flex: 0 0 14px;
}
.role-prospect  { background: var(--tint);        color: var(--muted);       border: var(--hairline) solid var(--line); }
.role-donor     { background: var(--success-bg);  color: var(--success-ink); border: var(--hairline) solid var(--success-border); }
.role-volunteer { background: var(--info-bg);     color: var(--info-ink);    border: var(--hairline) solid var(--info-border); }
.role-member    { background: var(--warning-bg);  color: var(--warning-ink); border: var(--hairline) solid var(--warning-border); }
.role-portal_user { background: #F3EBDD; color: #7A4A2A; border: var(--hairline) solid #E2D0B5; }
/* Round 10a fix 1 (badge next to the name). Red, same tokens as
   the DNC pill. */
.role-dnc { background: #FBE9E7; color: #8E1F17; border: var(--hairline) solid #EDB9B3; }

/* More-filters "Roles" group.

   The parent .filter-row uses flex: 1 1 150px on each .filter
   cell (see form.filters .filter above), so a 150px basis is the
   natural cell width. A single 150px cell is too narrow for four
   labelled checkboxes, so:
     - Give this cell a wider basis (about the width of two
       ordinary cells) so it sits in-line with the row's flow but
       has room for a 2x2 layout.
     - Lay the checkboxes out as a 2-column grid inside the cell,
       so the four labels always land as Prospect+Donor on the
       first row and Volunteer+Member on the second - never
       stacked one-per-line, never interleaved with other filters.

   The label above the grid matches the other .filter labels
   (15px, muted) so the cell looks like part of the row rather
   than a standalone widget. */
form.filters .filter.filter-roles {
  flex: 1 1 260px;
}
.filter-roles .filter-checks {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px 14px;
  padding-top: 6px;
  align-items: center;
}
.filter-roles .filter-checks .check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 400;
  margin: 0;
  min-height: var(--control-h);
  /* Override the parent "form.filters .filter label" rule which
     muted the label text - readable ink is right for a checkbox
     label a person is clicking on. Font size stays at 15px so it
     sits in the same visual scale as the other filter widgets. */
  color: var(--ink);
  font-size: 15px;
}

/* More-filters "Connection" group.

   Two 2x2 checkbox grids side by side inside a single filter cell,
   divided by a hairline. Left column is "Show" (include-any); right
   column is "Without" (exclude-any). Ticking a role on one side
   auto-unticks the same role on the other in the browser, and
   DonorRepo strips the collision at query time as well.

   Height match: the group cell is deliberately kept to roughly the
   same vertical footprint as the earlier single-set "Roles" cell.
   That's why the checkbox min-height sits at 30px (not the
   var(--control-h) 38px the earlier cell used) - the small
   "Show" / "Without" column headings need a row too, and the total
   only fits inside the pre-existing cell height when the checkbox
   rows are compressed the same amount as the added heading takes
   up. The 30px still comfortably clears the 18px checkbox, and
   the layout reads the same as the surrounding filter cells.

   Narrow window: below 720px viewport width the two columns stack
   instead of squashing; the vertical divider becomes a horizontal
   one, and the group grows a row - the trade you accept for
   narrow-window legibility. */
form.filters .filter.filter-connection {
  flex: 1 1 500px;
}
.filter-connection-cols {
  display: flex;
  gap: 14px;
  padding-top: 6px;
}
.filter-connection-col {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 12px;
  align-items: center;
}
/* Vertical hairline between the two columns. Uses the standard
   --line and --hairline variables so it reads as part of the
   surrounding panel, not a new divider style. */
.filter-connection-col:first-child {
  border-right: var(--hairline) solid var(--line);
  padding-right: 14px;
}
.filter-connection-heading {
  grid-column: 1 / -1;
  font-size: 15px;
  color: var(--muted);
  line-height: 1;
  padding-bottom: 2px;
}
.filter-connection-col .check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 400;
  margin: 0;
  min-height: 30px;
  color: var(--ink);
  font-size: 15px;
}
@media (max-width: 720px) {
  .filter-connection-cols {
    flex-direction: column;
    gap: 8px;
  }
  .filter-connection-col:first-child {
    border-right: 0;
    padding-right: 0;
    border-bottom: var(--hairline) solid var(--line);
    padding-bottom: 8px;
  }
}

/* -------------------- settings -------------------- */
/* Landed 22 September 2026 with the settings shell, module switches
   and the five live cards. Layout follows mockup-settings.html; the
   colours, borders, radii, text sizes and hairlines come from the
   admin.css tokens above so the settings page reads as part of the
   live shell rather than the mockup. */

.settings-page {
  /* Rob's rule: wider left and right margins than the dashboard.
     The enclosing main.page already carries the shell's 18px page
     padding; adding 26px inside it makes the settings gutter 44px
     each side (matching mockup-settings.html's `.wrap` padding).
     Falls back to the shell padding on narrow viewports. */
  max-width: none;
  margin: 0;
  padding: 0;
}
.settings-page .settings-group + .settings-group {
  margin-top: 24px;
}
.settings-group-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  border-bottom: var(--hairline) solid var(--line);
  padding-bottom: 6px;
  margin-bottom: 12px;
}
.settings-group-head h2 {
  font-size: 17px;
  font-weight: 500;
  margin: 0;
  color: var(--ink);
}
.settings-group-head p {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
}
.settings-group-lock .settings-group-head h2 {
  color: var(--accent);
}
.settings-lock-line {
  display: inline-block;
  margin: 0 0 12px;
  padding: 6px 12px;
  background: var(--accent-tint);
  color: var(--accent);
  border-radius: var(--radius-s);
  font-size: 15px;
}

.settings-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}
@media (max-width: 1100px) { .settings-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px)  { .settings-grid { grid-template-columns: 1fr; } }

.settings-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 16px 20px;
  color: var(--ink);
  text-decoration: none;
  min-height: 108px;
}
a.settings-card:hover {
  border-color: var(--accent);
  text-decoration: none;
}
.settings-card h3 {
  font-size: 16px;
  font-weight: 500;
  margin: 0;
  color: var(--ink);
}
.settings-card p {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.5;
}
.settings-card .settings-pill {
  margin-top: auto;
  align-self: flex-start;
  padding: 1px 10px;
  border-radius: 999px;
  font-size: 15px;
  line-height: 1.5;
}
.settings-pill-ok    { background: var(--success-bg); color: var(--success-ink); }
.settings-pill-later { background: var(--tint);       color: var(--muted); }
.settings-card-off {
  background: var(--surface-1);
}
.settings-card-off h3,
.settings-card-off p { color: var(--muted); }

/* Settings detail pages (General, Areas, Receipts, Needs
   attention, Alert emails, Financials, etc.). 25 October 2026 —
   "one width across the system": the per-page 720px cap +
   centring is removed so a settings page inherits `.page`'s
   1440px cap like every other CRM page. The sweep-R1 shared-
   standard block (nowrap on <th>/.kind-sub/a.lk, prose cap on
   intros + help + notes, max-width:none wrapper, phone-width
   stacking) is removed in the same change; the inline accordion
   that replaces the floating help pop-up on Financials stays. */
.settings-form {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 20px 22px;
}
.settings-intro {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 15px;
}
.settings-field {
  margin: 0 0 18px;
}
.settings-field label {
  display: block;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  margin-bottom: 6px;
}
.settings-field-switch label {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 400;
}
.settings-field-switch input[type=checkbox] {
  width: 18px;
  height: 18px;
  margin: 0;
}
.settings-field input[type=text],
.settings-field input[type=email],
.settings-field select,
.settings-field textarea {
  width: 100%;
  height: 38px;
  padding: 6px 10px;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  background: var(--surface-2);
  font: inherit;
  color: var(--ink);
}
.settings-field textarea {
  height: auto;
  min-height: 96px;
  line-height: 1.5;
  resize: vertical;
}
.settings-field input:focus,
.settings-field select:focus,
.settings-field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.settings-help {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.5;
}
.settings-help-tags {
  font-style: italic;
}
.settings-readonly {
  padding: 8px 10px;
  border: var(--hairline) solid var(--line-2);
  border-radius: var(--radius-s);
  background: var(--surface-1);
  color: var(--ink);
  font-size: 15px;
  min-height: 38px;
  white-space: pre-wrap;
}
.settings-actions {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 4px;
}
.settings-back {
  color: var(--muted);
  font-size: 15px;
}

/* --------------------------------------------------------------
   Your settings > Email (Stage 18 Round 2, 12 October 2026).
   Reuses .settings-detail / .settings-form / .settings-field /
   .btn / .badge / .flash-* from the shell. Only three bits of
   layout glue live here: the status / button row, the two-column
   email + password grid, and the muted "coming soon" span.
   -------------------------------------------------------------- */
.ys-email .ys-h {
  font-size: 16px;
  font-weight: 500;
  margin: 0 0 8px;
  color: var(--ink);
}
.ys-email .ys-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.ys-email .ys-status {
  margin-top: 10px;
}
.ys-email .ys-inline {
  display: inline;
  margin: 0;
}
.ys-email .ys-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0 14px;
}
@media (max-width: 600px) {
  .ys-email .ys-grid { grid-template-columns: 1fr; }
}
.ys-email .ys-coming {
  color: var(--muted);
  font-size: 15px;
}
.ys-email .ys-help {
  margin-top: 10px;
}

/* Module placeholder pages: Volunteers, Members, Events, Portal
   Control. The shell renders the page title; this container holds
   the "switched off" line when the module switch is off, and
   nothing when it is on. */
.module-placeholder {
  /* 25 October 2026 — "one width across the system": the 720px
     cap and auto-centring are removed; the placeholder sits in
     the shared `.page` 1440 shell like every other page. */
  color: var(--muted);
  font-size: 15px;
}

/* --------------------------------------------------------------
   Financials page (Stage 4E, 25 September 2026)
   --------------------------------------------------------------
   Named shell patterns Rob's Financials mockup asks for. Kept
   here (not on the page) because the design document requires
   any new pattern to land in admin.css as a shell pattern.

   Naming: fn-* is the whole Financials family. Two patterns are
   Rob's called-out ones:
     fn-verdict  — the navy verdict band ("N things to fix" or
                   "Everything is in order")
     fn-tick     — the tick-list row used under "All clear"
   Other selectors are per-section layout (to-fix rows, money
   cards, by-fund table); each is scoped so nothing here can
   leak onto another page. Fonts, colours and the 15px minimum
   match the rest of the admin shell.
   -------------------------------------------------------------- */

/* Header row: title + FY line on the left, download buttons on
   the right. Wraps under the header on narrow widths so a phone
   still fits without a horizontal scroll. */
.fn-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}
.fn-header h1 {
  margin: 0 0 6px;
  font-size: 23px;
  font-weight: 500;
}
.fn-header .fn-sub {
  color: var(--muted);
  font-size: 15px;
}
.fn-header .fn-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* Verdict band. Navy (#1F3A5F) matches the mockup exactly and
   ties the band to DonorFix's own brand navy already used for
   primary buttons. Two columns: the message on the left, the
   two small cards ("days to year end", "N of 13 checks clear")
   on the right. On narrow widths the right column drops under. */
.fn-verdict {
  background: var(--band-navy);
  color: #ffffff;
  border-radius: 12px;
  padding: 30px 34px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 32px;
  align-items: center;
  margin-bottom: 28px;
}
.fn-verdict .fn-v-eyebrow {
  font-size: 15px;
  color: var(--band-eyebrow);
  margin-bottom: 10px;
}
.fn-verdict .fn-v-headline {
  font-size: 28px;
  font-weight: 500;
  line-height: 1.3;
  margin-bottom: 10px;
}
.fn-verdict .fn-v-body {
  font-size: 17px;
  line-height: 1.55;
  color: var(--band-body);
}
.fn-verdict .fn-v-tick {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 22px;
  font-weight: 500;
  margin-bottom: 6px;
}
.fn-verdict .fn-v-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}
.fn-verdict .fn-v-card {
  background: var(--band-inner);
  border-radius: 10px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.fn-verdict .fn-v-card .fn-v-num {
  font-size: 22px;
  font-weight: 500;
}
.fn-verdict .fn-v-card .fn-v-label {
  font-size: 15px;
  color: var(--band-label);
}
@media (max-width: 720px) {
  .fn-verdict { grid-template-columns: minmax(0, 1fr); }
}

/* Filter row: chips on the left, Years dropdown on the right.
   Chip styling matches the mockup's 44px min-height, off-white
   background, quiet border; selected chip is navy on white.
   Chips are <a> so a click writes a filter into the URL and the
   page re-renders on the server side (no JS). */
.fn-filter {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}
.fn-filter .fn-chips {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.fn-filter .fn-chips .fn-chips-label {
  color: var(--muted);
  font-size: 15px;
  margin-right: 4px;
}
.fn-chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 999px;
  font-size: 15px;
  text-decoration: none;
  color: var(--ink);
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  box-sizing: border-box;
}
.fn-chip.fn-chip-on {
  background: var(--accent);
  color: #ffffff;
  border-color: var(--accent);
}
.fn-chip:hover {
  border-color: var(--accent);
}
.fn-filter .fn-years {
  display: flex;
  align-items: center;
  gap: 10px;
}
.fn-filter .fn-years label {
  color: var(--muted);
  font-size: 15px;
}
.fn-filter .fn-years select {
  min-height: 44px;
  padding: 0 12px;
  border-radius: 6px;
  border: var(--hairline) solid var(--line);
  background: var(--surface-2);
  font: inherit;
  font-size: 15px;
  color: var(--ink);
}

/* Card frame reused by every white panel on the page. */
.fn-card {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 28px;
}
.fn-card-head {
  padding: 20px 26px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.fn-card-head h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
}
.fn-card-head .fn-card-sub {
  color: var(--muted);
  font-size: 15px;
}

/* To-fix row: one row per check with count > 0. Four columns:
   label chip + area, plain statement + why line, money figure,
   button. The button is right-aligned. Mirrors the mockup's
   .row grid exactly.

   The label pill is either "Tax receipt" (red) or "Needs doing"
   (amber). Colours match the mockup's palette; the amber picks
   up the "warning" tone already used elsewhere. */
.fn-fix-row {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr) 170px 190px;
  gap: 24px;
  align-items: center;
  padding: 22px 26px;
  border-top: var(--hairline) solid var(--line-2);
}
.fn-fix-row:first-of-type { border-top: 0; }
.fn-fix-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}
.fn-pill {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 15px;
  line-height: 1.5;
}
.fn-pill-danger  { background: var(--danger-bg);  color: var(--danger-ink); }
.fn-pill-warning { background: var(--warning-bg); color: var(--warning-ink); }
.fn-fix-area {
  color: var(--muted);
  font-size: 15px;
}
.fn-fix-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.fn-fix-body .fn-fix-statement {
  font-size: 17px;
  font-weight: 500;
}
.fn-fix-body .fn-fix-why {
  color: var(--muted);
  font-size: 15px;
  line-height: 1.5;
}
.fn-fix-money {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.fn-fix-money .fn-fix-money-figure {
  font-size: 19px;
  font-weight: 500;
}
.fn-fix-money .fn-fix-money-note {
  color: var(--muted);
  font-size: 15px;
}
.fn-fix-action {
  display: flex;
  justify-content: flex-end;
}
@media (max-width: 900px) {
  .fn-fix-row { grid-template-columns: minmax(0, 1fr); }
  .fn-fix-action { justify-content: flex-start; }
}

/* Tick list — the "All clear" pattern. Three-column grid on wide
   widths, single column on narrow. The tick SVG is inline on the
   render side so a colour change here re-tints it. */
.fn-tick-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px 24px;
  padding: 4px 26px 22px;
}
@media (max-width: 900px) {
  .fn-tick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .fn-tick-grid { grid-template-columns: minmax(0, 1fr); }
}
.fn-tick {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  color: var(--ink);
}
.fn-tick svg { flex-shrink: 0; }

/* Money-in-so-far strip: four cards in a row. Each card carries a
   quiet label, a large figure, and up to two secondary lines. */
.fn-money-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 28px;
}
@media (max-width: 1100px) {
  .fn-money-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .fn-money-strip { grid-template-columns: minmax(0, 1fr); }
}
.fn-money-card {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: 12px;
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.fn-money-card .fn-money-label {
  color: var(--muted);
  font-size: 15px;
}
.fn-money-card .fn-money-figure {
  font-size: 26px;
  font-weight: 500;
}
.fn-money-card .fn-money-line {
  font-size: 15px;
  color: var(--muted);
}
.fn-money-card .fn-money-line-up   { color: var(--success-ink); }
.fn-money-card .fn-money-line-down { color: var(--danger-ink); }

/* By-fund table. Four columns: fund label, this year, last year
   at same point, change. Header row shaded. */
.fn-fund-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 170px 200px 110px;
  gap: 16px;
  padding: 13px 24px;
  border-top: var(--hairline) solid var(--line-2);
  font-size: 15px;
}
.fn-fund-row:first-of-type { border-top: 0; }
.fn-fund-head {
  background: var(--surface-1);
  color: var(--muted);
}
.fn-fund-total {
  background: var(--surface-1);
  font-weight: 500;
}
.fn-fund-num {
  text-align: right;
}
.fn-fund-change-up   { text-align: right; color: var(--success-ink); }
.fn-fund-change-down { text-align: right; color: var(--danger-ink); }
@media (max-width: 900px) {
  .fn-fund-row {
    grid-template-columns: minmax(0, 1fr) 1fr 1fr 80px;
    gap: 8px;
    padding: 13px 16px;
  }
}

/* Per-check list page: header block (what the check asks, why it
   matters) + records table + steps-to-fix. */
.fn-check-header {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: 12px;
  padding: 22px 26px;
  margin-bottom: 22px;
}
.fn-check-header h1 {
  margin: 0 0 8px;
  font-size: 21px;
  font-weight: 500;
}
.fn-check-header .fn-check-question {
  color: var(--muted);
  font-size: 15px;
  margin-bottom: 12px;
  line-height: 1.5;
}
.fn-check-steps {
  background: var(--surface-1);
  border: var(--hairline) solid var(--line-2);
  border-radius: 8px;
  padding: 14px 18px;
  font-size: 15px;
  line-height: 1.55;
}
.fn-check-steps h3 {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 500;
}
.fn-check-steps ol {
  margin: 0;
  padding-left: 20px;
}
.fn-check-steps li { margin-bottom: 4px; }

/* Records table on the per-check page. Same shape and 15px minimum
   as the gift list. */
.fn-records {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 22px;
}
.fn-records table {
  width: 100%;
  border-collapse: collapse;
  font-size: 15px;
}
.fn-records th, .fn-records td {
  text-align: left;
  padding: 12px 16px;
  border-bottom: var(--hairline) solid var(--line-2);
  vertical-align: top;
}
.fn-records tr:last-child td { border-bottom: 0; }
.fn-records th {
  font-weight: 500;
  color: var(--muted);
  background: var(--surface-1);
}
.fn-records td a {
  color: var(--accent);
  text-decoration: none;
}
.fn-records td a:hover { text-decoration: underline; }
.fn-records .fn-records-empty {
  padding: 30px;
  text-align: center;
  color: var(--muted);
  font-size: 15px;
}
.fn-records .fn-records-num { text-align: right; white-space: nowrap; }

/* --------------------------------------------------------------
   Reports (Stage 4D, 26 September 2026)
   --------------------------------------------------------------
   Every screen style the reports pages used to carry in their own
   inline <style> blocks now lives here, routing through the same
   tokens as the rest of the shell. The report renderer classes
   (`app/admin/*Report.php`) still emit an inline <style> block on
   the PDF path (Dompdf reads only inline styles); those blocks
   carry hex codes that match the tokens below.

   Class families:
     .rp-*   reports index (tabs, cards, panel) + one-report frame
             (crumb, head, question, actions, bar, warn, body, foot)
     .rr-*   Retention
     .sg-*   Second-gift conversion
     .ly-*   LYBUNT
     .cb-*   Gifts by campaign
     .gt-*   Giving tiers
     .lt-*   Lifetime giving
   -------------------------------------------------------------- */

/* Reports index — tabs, group headers, card grid, custom-reports
   panel. Was inline in `reports/index.php` before 26 September 2026. */
.rp-tabs {
  display: flex;
  gap: 22px;
  border-bottom: var(--hairline) solid var(--line);
  margin: 4px 0 18px;
}
.rp-tabs a {
  padding: 8px 0;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}
.rp-tabs a.on {
  color: var(--ink);
  font-weight: 500;
  border-bottom-color: var(--accent);
}
.rp-group { margin-bottom: 22px; }
.rp-group h2 {
  font-size: 16px;
  font-weight: 500;
  margin: 0 0 10px;
}
.rp-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}
.rp-card {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
  text-decoration: none;
  color: var(--ink);
  display: block;
}
.rp-card:hover { border-color: var(--accent); }
.rp-card b {
  font-weight: 500;
  display: block;
  margin-bottom: 2px;
  color: var(--accent);
}
.rp-card span { color: var(--muted); }
.rp-quiet {
  color: var(--muted);
  margin-top: 6px;
}
.rp-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 12px;
}
.rp-panel table { width: 100%; border-collapse: collapse; }
.rp-panel th, .rp-panel td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: var(--hairline) solid var(--line);
}
.rp-panel th { font-weight: 500; color: var(--muted); }
.rp-panel tr:last-child td { border-bottom: 0; }
.rp-panel td a { color: var(--accent); text-decoration: none; }
.rp-empty {
  color: var(--muted);
  padding: 22px 17px;
  text-align: center;
}

/* One-report frame — crumb, head, question, actions row, year/compare
   bar, duplicates warn, report body, foot definitions. Was inline in
   `reports/report.php`. */
.rp-crumb {
  color: var(--muted);
  margin: 0 0 6px;
  font-size: 14px;
}
.rp-crumb a { color: var(--accent); text-decoration: none; }
.rp-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: 0 0 6px;
}
.rp-title {
  font-size: 23px;
  font-weight: 500;
  margin: 0;
}
.rp-question {
  color: var(--muted);
  margin: 0 0 14px;
}
.rp-actions { display: flex; gap: 8px; }
.rp-actions button,
.rp-actions a.btn {
  font: inherit;
  font-size: 15px;
  border-radius: var(--radius-s);
  padding: 6px 12px;
  cursor: pointer;
  border: var(--hairline) solid var(--line);
  background: var(--surface-2);
  color: var(--ink);
  text-decoration: none;
  display: inline-block;
}
.rp-actions .primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}
.rp-bar {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 10px 17px;
  margin-bottom: 12px;
}
.rp-bar select {
  font: inherit;
  font-size: 15px;
  padding: 5px 8px;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-s);
  background: var(--surface-2);
}
.rp-bar .cmp { color: var(--muted); }
.rp-bar .spacer { flex: 1; }
.rp-warn {
  background: var(--warning-bg);
  color: var(--warning-ink);
  border-radius: var(--radius);
  padding: 10px 17px;
  margin-bottom: 12px;
}
.rp-warn a { color: inherit; text-decoration: underline; }
.rp-body {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 22px 17px;
  margin-bottom: 12px;
}
.rp-body.placeholder {
  color: var(--muted);
  text-align: center;
  font-style: italic;
  padding: 44px 17px;
}
.rp-foot {
  color: var(--muted);
  border-top: var(--hairline) solid var(--line);
  padding-top: 10px;
  margin-top: 14px;
  font-size: 14px;
}
.rp-foot dt {
  float: left;
  width: 110px;
  color: var(--ink);
  font-weight: 500;
}
.rp-foot dd { margin-left: 110px; margin-bottom: 4px; }

/* Retention — .rr-*. Emitted by RetentionReport::renderBodyHtml. */
.rr-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}
.rr-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
}
.rr-panel-label { color: var(--muted); }
.rr-big { font-size: 22px; font-weight: 500; margin: 2px 0; }
.rr-cmp { color: var(--muted); }
.rr-up { color: var(--success-ink); }
.rr-down { color: var(--danger-ink); }
.rr-bars {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
  margin-bottom: 12px;
}
.rr-bars h3 { font-size: 16px; font-weight: 500; margin: 0 0 10px; }
.rr-hbar {
  display: grid;
  grid-template-columns: 150px 1fr 90px;
  gap: 10px;
  align-items: center;
  margin: 8px 0;
}
.rr-track {
  background: var(--surface-1);
  border-radius: var(--radius-s);
  height: 22px;
  overflow: hidden;
}
.rr-fill {
  background: var(--accent);
  height: 100%;
  border-radius: var(--radius-s);
  min-width: 2px;
}
.rr-figure { text-align: right; font-variant-numeric: tabular-nums; }
.rr-table {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 12px;
}
.rr-table table { width: 100%; border-collapse: collapse; }
.rr-table th, .rr-table td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: var(--hairline) solid var(--line);
}
.rr-table th { font-weight: 500; color: var(--muted); }
.rr-table td.n, .rr-table th.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.rr-table tr.rr-overall td { font-weight: 500; background: var(--surface-1); }
.rr-table tr:last-child td { border-bottom: 0; }

/* Second gift conversion — .sg-*. Emitted by SecondGiftReport. */
.sg-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}
.sg-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
}
.sg-panel-label { color: var(--muted); }
.sg-big { font-size: 22px; font-weight: 500; margin: 2px 0; }
.sg-muted { color: var(--muted); }
.sg-table {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 12px;
}
.sg-table table { width: 100%; border-collapse: collapse; }
.sg-table th, .sg-table td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: var(--hairline) solid var(--line);
}
.sg-table th { font-weight: 500; color: var(--muted); }
.sg-table td.n, .sg-table th.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.sg-table tr.sg-overall td { font-weight: 500; background: var(--surface-1); }
.sg-table tr:last-child td { border-bottom: 0; }
.sg-tag { color: var(--warning-ink); margin-left: 4px; }

/* LYBUNT — .ly-*. Emitted by LybuntReport. */
.ly-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}
.ly-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
}
.ly-panel-label { color: var(--muted); }
.ly-big { font-size: 22px; font-weight: 500; margin: 2px 0; }
.ly-muted { color: var(--muted); }
.ly-cmp, .ly-bands {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 12px 17px;
  margin-bottom: 12px;
}
.ly-cmp table, .ly-bands table { width: 100%; border-collapse: collapse; }
.ly-cmp td, .ly-bands td, .ly-bands th { padding: 6px 8px; }
.ly-bands th { font-weight: 500; color: var(--muted); text-align: left; }
.ly-cmp td.n, .ly-bands td.n, .ly-bands th.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ly-bands tr.ly-band-total td {
  font-weight: 500;
  border-top: var(--hairline) solid var(--line);
}
.ly-list {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 12px;
}
.ly-list table { width: 100%; border-collapse: collapse; }
.ly-list th, .ly-list td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: var(--hairline) solid var(--line);
}
.ly-list th { font-weight: 500; color: var(--muted); }
.ly-list td.n, .ly-list th.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ly-list tr:last-child td { border-bottom: 0; }
.ly-list td a { color: var(--accent); text-decoration: none; }
.ly-tag { color: var(--warning-ink); margin-left: 4px; }
.ly-h3 { font-size: 16px; font-weight: 500; margin: 0 0 8px; }

/* Gifts by campaign — .cb-*. Emitted by CampaignReport
   (screen render — the PDF render carries its own inline block). */
.cb-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}
.cb-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
}
.cb-panel-label { color: var(--muted); }
.cb-big { font-size: 22px; font-weight: 500; margin: 2px 0; }
.cb-muted { color: var(--muted); }
.cb-list {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 12px;
}
.cb-list table { width: 100%; border-collapse: collapse; table-layout: auto; }
.cb-list th, .cb-list td {
  padding: 8px 10px;
  border-bottom: var(--hairline) solid var(--line);
  vertical-align: middle;
}
.cb-list th {
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
  text-align: right;
}
.cb-list th:first-child { text-align: left; }
.cb-list td.n, .cb-list th.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cb-list td.cb-name { white-space: normal; word-wrap: break-word; word-break: break-word; }
.cb-list tr.cb-child td.cb-name {
  padding-left: 30px;
  color: var(--muted);
  font-weight: 400;
}
.cb-list tr.cb-parent td.cb-name { font-weight: 500; }
.cb-list tr.cb-total td {
  font-weight: 600;
  border-top: 1px solid var(--ink);
  background: var(--surface-1);
}
.cb-list tr:last-child td { border-bottom: 0; }

/* Giving tiers — .gt-*. Emitted by GivingTiersReport. */
.gt-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}
.gt-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
}
.gt-panel-label { color: var(--muted); }
.gt-big { font-size: 22px; font-weight: 500; margin: 2px 0; }
.gt-muted { color: var(--muted); }
.gt-note { color: var(--muted); margin: 0 0 8px; font-size: 14px; }
.gt-list {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 12px;
}
.gt-list table { width: 100%; border-collapse: collapse; }
.gt-list th, .gt-list td {
  padding: 8px 10px;
  border-bottom: var(--hairline) solid var(--line);
  vertical-align: middle;
}
.gt-list th {
  font-weight: 500;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
}
.gt-list th:first-child, .gt-list td.gt-band { text-align: left; }
.gt-list td.n, .gt-list th.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.gt-list tr.gt-total td {
  font-weight: 600;
  border-top: 1px solid var(--ink);
  background: var(--surface-1);
}
.gt-list tr:last-child td { border-bottom: 0; }

/* Lifetime giving — .lt-*. Emitted by LifetimeReport. */
.lt-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}
.lt-panel {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px;
}
.lt-panel-label { color: var(--muted); }
.lt-big { font-size: 22px; font-weight: 500; margin: 2px 0; }
.lt-muted { color: var(--muted); }
.lt-note { color: var(--muted); margin: 0 0 8px; font-size: 14px; }
.lt-list {
  background: var(--surface-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 12px;
}
.lt-list table { width: 100%; border-collapse: collapse; }
.lt-list th, .lt-list td {
  padding: 8px 10px;
  border-bottom: var(--hairline) solid var(--line);
  vertical-align: middle;
}
.lt-list th {
  font-weight: 500;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
}
.lt-list th:first-child, .lt-list td.lt-name { text-align: left; }
.lt-list td.n, .lt-list th.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.lt-list td.lt-name a { color: var(--accent); text-decoration: none; }
.lt-list tr:last-child td { border-bottom: 0; }
.lt-dnc { color: var(--warning-ink); margin-left: 6px; font-size: 12px; }

/* ------------------------------------------------------------
   Donation page editor (admin/campaigns/page.php, Stage 14
   round 8b.1). Built from `campaign-setup-mockup.html` layout A.
   Tabs across the top, settings on the left, saved-state
   preview iframe on the right. Round 8b.2 brings the live
   preview and drag-and-drop polish.
   ------------------------------------------------------------ */
.dp-editor-topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 0 10px;
}
.dp-pill {
  font-size: 12px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 99px;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.dp-pill.pill-live   { background: var(--success-bg,   #e6f2ec); color: var(--success-ink, #1d6b45); }
.dp-pill.pill-closed { background: var(--warning-bg,   #faecd4); color: var(--warning-ink, #8a4b00); }
.dp-topbar-note { color: var(--muted); font-size: 13px; }

.dp-tabs {
  display: flex;
  gap: 2px;
  margin: 10px 0 16px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.dp-tab {
  min-height: 44px;
  padding: 0 16px;
  border: 0;
  background: none;
  font-weight: 600;
  font-size: 14px;
  color: var(--muted);
  border-bottom: 3px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}
.dp-tab[aria-selected="true"] {
  color: var(--text);
  border-bottom-color: var(--accent);
}

.dp-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: 24px;
  align-items: start;
}
.dp-settings { min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.dp-pane     { display: flex; flex-direction: column; gap: 16px; }
.dp-pane[hidden] { display: none; }

.dp-preview { position: sticky; top: 20px; min-width: 0; }
.dp-preview-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 10px; font-size: 13px;
}
.dp-preview-bar strong { color: var(--text); }
.dp-preview-note { color: var(--muted); }
.dp-preview-frame {
  width: 100%;
  height: calc(100vh - 220px);
  min-height: 480px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: #dfe2e9;
  box-shadow: 0 8px 28px rgba(20, 25, 40, .14);
}
.dp-preview-empty {
  border: 1px dashed var(--border);
  border-radius: 12px;
  padding: 24px;
  color: var(--muted);
  text-align: center;
  background: var(--bg);
}
.dp-auto-note {
  background: #eef4ff;
  color: #24427a;
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 13px;
  margin: 0;
}

/* Sections list (arrows for order, Where select, Show tick) */
.dp-sections-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.dp-sec-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fff;
}
.dp-sec-name { min-width: 0; }
.dp-sec-where select { min-height: 36px; padding: 4px 8px; }
.dp-sec-show { white-space: nowrap; font-size: 13px; display: flex; gap: 6px; align-items: center; }
.dp-sec-move { display: flex; gap: 4px; }

.dp-check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 6px;
}

.dp-radio-group { display: flex; flex-direction: column; gap: 6px; }

.dp-image-thumb {
  max-width: 180px;
  max-height: 120px;
  border-radius: 10px;
  border: 1px solid var(--border);
  display: block;
  margin-bottom: 8px;
}

@media (max-width: 1100px) {
  .dp-layout { grid-template-columns: minmax(0, 1fr); }
  .dp-preview { position: static; }
  .dp-preview-frame { height: 70vh; }
}

/* Campaign details page (admin/campaigns/edit.php round 8b.1):
   the Online donation page card with the on/off switch and a link
   to the editor. The card shows ONLY when the form is on. */
.dp-page-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  background: #f3f6fb;
  border: 1px solid #dfe6f2;
  border-radius: 12px;
  padding: 16px;
}
.dp-page-card-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ------------------------------------------------------------
   Donation page editor — round 8b.2 polish. Adds:
     - Unsaved-changes state in the top bar.
     - Computer / Phone device toggle above the live preview.
     - Segmented buttons (.dp-seg) for picture place/focus/shadow.
     - Chip editor (.dp-chip*) for suggested amounts.
     - Sections list with drag handle (.dp-sec-handle), fixed
       Heading + Form rows (.dp-sec-fixed, .dp-sec-form), and the
       is-dragging / drop-target states.
     - Split-screen scrolling on wide displays.
   ------------------------------------------------------------ */

.dp-state {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  margin: 0 8px;
  min-width: 150px;
  text-align: right;
}
.dp-state.is-dirty { color: var(--warning-ink, #8a4b00); }
.dp-state.is-saved { color: var(--success-ink, #1d6b45); }

.dp-device {
  display: inline-flex;
  background: var(--border);
  border-radius: 10px;
  padding: 3px;
  gap: 3px;
}
.dp-device-btn {
  min-height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.dp-device-btn.is-on {
  background: #fff;
  color: var(--text);
  box-shadow: 0 1px 2px rgba(20,25,40,.12);
}
.dp-preview-stage {
  background: #dfe2e9;
  border-radius: 12px;
  padding: 10px;
  display: flex;
  justify-content: center;
}
.dp-preview-stage.dp-stage-phone .dp-preview-frame {
  width: 390px;
  max-width: 100%;
  height: 760px;
  max-height: 85vh;
}
.dp-preview-stage .dp-preview-frame {
  width: 100%;
  height: calc(100vh - 220px);
  min-height: 480px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 8px 28px rgba(20, 25, 40, .14);
}

/* Segmented buttons (picture place / focus / form shadow). */
.dp-seg {
  display: inline-flex;
  flex-wrap: wrap;
  background: var(--border);
  border-radius: 10px;
  padding: 3px;
  gap: 3px;
}
.dp-seg-btn {
  min-height: 36px;
  padding: 0 14px;
  display: inline-flex;
  align-items: center;
  border-radius: 8px;
  background: transparent;
  font-weight: 600;
  font-size: 13.5px;
  color: var(--muted);
  cursor: pointer;
}
.dp-seg-btn.is-on {
  background: #fff;
  color: var(--text);
  box-shadow: 0 1px 2px rgba(20,25,40,.12);
}
.dp-seg-btn input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* Amount chips. */
.dp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  min-height: 36px;
}
.dp-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: #eef0f4;
  border-radius: 999px;
  padding: 6px 6px 6px 12px;
  font-weight: 600;
  font-size: 14px;
}
.dp-chip-x {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 0;
  background: #dfe2e8;
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
}
.dp-chip-add {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.dp-chip-add input,
.dp-chip-add select {
  max-width: 240px;
  min-height: 36px;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0 10px;
  background: #fff;
  font: inherit;
}
/* Stage 20a Round 5a fix 10 follow-up 3 item 8 (1 November 2026).
   On the campaign Team field the add-row stretches full width so
   it reads as the field's main input (like the other search
   boxes), and the empty state carries a light "No one on the
   team yet" line in the chips area instead of a blank gap.
   Border/background set explicitly so native select chrome shows
   consistently in Chrome, Firefox and the headless shot. */
.dp-chip-add-wide { width: 100%; }
.dp-chip-add-wide .dp-chip-add-input {
  max-width: none;
  width: 100%;
  border: 1px solid #d8dadf;
  border-radius: 8px;
  background: #fff;
  min-height: 36px;
  padding: 0 10px;
  font: inherit;
}
.dp-empty {
  color: var(--muted, #6a7380);
  font-size: 14px;
  line-height: 36px;
  font-style: italic;
}
/* Hide the empty-state line as soon as a chip appears, so the
   label stops showing beside the newly-added chip. */
.dp-chips:has(.dp-chip) .dp-empty { display: none; }

/* Sections list — mockup version. */
.dp-sections-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.dp-sec-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: #fff;
}
.dp-sec-fixed { background: #f6f7f9; }
.dp-sec-form  { background: #f3efe6; border-style: dashed; }
.dp-sec-row.is-dragging { opacity: .5; border: 2px dashed var(--accent); }
.dp-sec-handle {
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #f7f8fa;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  color: var(--muted);
  padding: 0;
}
.dp-sec-handle:active { cursor: grabbing; }
.dp-sec-handle-fixed { cursor: default; opacity: .4; }
.dp-sec-name strong { display: block; font-size: 14.5px; }
.dp-sec-sub { display: block; font-size: 12.5px; color: var(--muted); }
.dp-sec-show { white-space: nowrap; font-size: 13px; display: inline-flex; gap: 6px; align-items: center; }

/* On wide displays the whole page scrolls normally; the preview
   stays beside the settings column via `position: sticky` on
   .dp-preview (set earlier in this file) and its own viewport-
   capped frame height so it never grows taller than the screen.
   On narrow screens settings and preview stack via the
   @media (max-width: 1100px) rule earlier in this file. Round
   8b.2's `body.page-dp-editor { overflow: hidden }` lock is
   removed — it hid validation errors below a pinned-height
   layout and killed the page's main scroll. */

/* Error banner at the top of the donation page editor. Renders
   between the top bar and the tabs so staff see the reason a
   Save was refused before scrolling anywhere. */
.dp-flash-banner {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 12px;
}
.dp-flash-banner .flash {
  margin: 0;
}

/* ================================================================
   Portal Control (Stage 17 Round 4b).
   Local to /admin/portal-control/*. Styles do not read beyond this
   block, so a tweak here cannot ripple into the donation page editor
   or Settings.
   ================================================================ */
.pc-page { display: flex; flex-direction: column; gap: 20px; }
.pc-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.pc-title { margin: 0; font-size: 26px; font-weight: 500; }
.pc-head-row { display: flex; gap: 10px; align-items: center; margin-top: 6px; }
.pc-pill { display: inline-block; padding: 2px 11px; border-radius: 999px; font-size: 13px; border: 1px solid; }
.pc-pill-on  { background: #E7F1E9; color: #1E6B45; border-color: #B5D8C0; }
.pc-pill-off { background: #FBE9E9; color: #8A2E2E; border-color: #E7B6B6; }
.pc-pill-dim { display: inline-block; padding: 0 9px; border-radius: 999px; font-size: 12px; background: #EEF0F3; color: #5B6574; border: 1px solid #DDE1E7; margin-left: 6px; }

.pc-counts { background: #1F3A5F; border-radius: 14px; padding: 20px; display: flex; flex-wrap: wrap; gap: 14px; }
.pc-tile { flex: 1 1 200px; background: #2B4A73; border-radius: 10px; padding: 16px 18px; color: #fff; }
.pc-tile-l { color: #C9D5E6; font-size: 13px; }
.pc-tile-n { font-size: 30px; font-weight: 600; line-height: 1.2; }
.pc-tile-s { color: #DCE4EF; font-size: 13px; }

.pc-tabs { display: flex; gap: 24px; flex-wrap: wrap; border-bottom: 1px solid #E3E5EA; margin-bottom: 4px; }
.pc-tab  { padding: 10px 0; border-bottom: 2px solid transparent; color: #1C2530; text-decoration: none; display: inline-flex; gap: 8px; align-items: center; }
.pc-tab-on  { border-bottom-color: #1F3A5F; color: #1F3A5F; font-weight: 600; }
.pc-tab-off { color: #8A93A0; }

.pc-body { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }
.pc-form-card    { flex: 1 1 420px; max-width: 560px; background: #fff; border: 1px solid #E3E5EA; border-radius: 12px; }
.pc-preview-card { flex: 1 1 420px; background: #fff; border: 1px solid #E3E5EA; border-radius: 12px; overflow: hidden; }
.pc-sec { display: flex; flex-direction: column; gap: 14px; padding: 20px 22px; border-top: 1px solid #EDEFF2; }
.pc-sec:first-child { border-top: 0; }
.pc-sec h2 { margin: 0; font-size: 17px; font-weight: 600; }
.pc-field { display: flex; flex-direction: column; gap: 4px; }
.pc-lbl   { display: block; font-weight: 600; margin-bottom: 2px; }
.pc-inp   { width: 100%; box-sizing: border-box; min-height: 40px; border: 1px solid #D3D7DE; border-radius: 6px; padding: 0 12px; font: 15px Figtree, sans-serif; background: #fff; color: #1C2530; }
.pc-inp:disabled { background: #F6F7F8; color: #5B6574; }
.pc-ta    { padding: 8px 12px; min-height: 84px; resize: vertical; }
.pc-help  { color: #667080; margin: 4px 0 0; font-size: 13px; }
.pc-sub   { color: #667080; }
.pc-chk   { display: flex; gap: 10px; align-items: flex-start; }
.pc-chk input { width: 18px; height: 18px; margin-top: 3px; accent-color: #1F3A5F; }
.pc-fixed-rules { background: #FAFBFC; border: 1px solid #EDEFF2; border-radius: 8px; padding: 4px 14px; }
.pc-fx { display: flex; justify-content: space-between; gap: 12px; padding: 10px 0; border-top: 1px solid #EDEFF2; }
.pc-fx:first-child { border-top: 0; }
.pc-actions { padding: 16px 22px; border-top: 1px solid #EDEFF2; display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.pc-btn   { display: inline-flex; align-items: center; justify-content: center; min-height: 40px; padding: 0 16px; border-radius: 6px; border: 1px solid #1F3A5F; background: #1F3A5F; color: #fff; font: 600 15px Figtree, sans-serif; text-decoration: none; cursor: pointer; white-space: nowrap; }
.pc-btn2  { background: #fff; color: #1F3A5F; border-color: #D3D7DE; }

.pc-preview-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 18px; border-bottom: 1px solid #E3E5EA; background: #FAFBFC; }
.pc-preview-head-l { font-weight: 600; }
.pc-preview-head-tabs { display: flex; gap: 4px; }
.pc-preview-tab { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; padding: 0 14px; border-radius: 6px; border: 1px solid transparent; background: transparent; color: #1F3A5F; font: 600 14px Figtree, sans-serif; cursor: pointer; }
.pc-preview-tab-on { background: #E3EAF3; border-color: #E3EAF3; }
.pc-preview-body { padding: 0; background: #F4F5F7; }
.pc-preview-frame { width: 100%; min-height: 540px; border: 0; display: block; background: #F4F5F7; transition: max-width 0.15s ease; }
.pc-preview-frame.is-phone { max-width: 390px; margin: 0 auto; }

/* Contact page: Portal row, Portal user pill, Send portal link btn. */
.contact-portal-row { padding: 14px 10px; margin: 0 -10px; border-bottom: 1px solid #EDEFF2; }
.contact-portal-pill { display: inline-block; padding: 2px 11px; border-radius: 999px; font-size: 13px; background: #F3EBDD; color: #7A4A2A; border: 1px solid #E2D0B5; }
.btn.btn-portal-off { background: #F3F3F3; color: #8A93A0; cursor: not-allowed; }


/* Stage 18 Email, Round 4 (13 October 2026). Compose window and
   the contact's Emails tab. */
.df-compose-toolbar { display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
    padding: 6px; margin-bottom: 6px; background: #F4F5F7; border: 1px solid #E3E5EA; border-radius: 6px; }
.df-tb-btn { padding: 4px 10px; min-height: 30px; border: 1px solid #D3D7DE; background: #fff;
    border-radius: 4px; font: 600 13px Figtree, sans-serif; color: #1F3A5F; cursor: pointer; }
.df-tb-btn:hover { background: #EEF2F8; }
.df-tb-sep { color: #B2B9C4; margin: 0 2px; }
.df-tb-swatch { width: 22px; height: 22px; border: 1px solid #D3D7DE; border-radius: 50%;
    cursor: pointer; padding: 0; }
.df-compose-editor { min-height: 240px; padding: 10px 12px; border: 1px solid #D3D7DE;
    border-radius: 6px; background: #fff;
    font: 16px/1.6 Arial, Helvetica, sans-serif; }
.df-compose-editor:focus { outline: 2px solid #B6CBE8; outline-offset: -2px; }
.df-compose-editor p { margin: 0 0 10px 0; }
.df-compose-editor ul, .df-compose-editor ol { padding-left: 22px; }

.emails-tab .tl-email .pill { display: inline-block; padding: 1px 8px; border-radius: 999px;
    font-size: 12px; font-weight: 600; margin-left: 6px; }
.emails-tab .pill-sent   { background: #DFF3E4; color: #16653B; }
.emails-tab .pill-draft  { background: #FDF4D6; color: #7A6400; }
.emails-tab .pill-failed { background: #FBE2E2; color: #8A1818; }
.emails-tab .attachment-list { list-style: none; padding: 0; margin: 0 0 6px 0; }
.emails-tab .attachment-list li { padding: 2px 0; }

.tab-count { display: inline-block; padding: 0 7px; border-radius: 999px; background: #E3EAF3;
    color: #1F3A5F; font-size: 12px; font-weight: 700; margin-left: 4px; }

/* Stage 18 Email, Round 4 fix 3 (13 October 2026). Compose modal
   over the contact page, matching mockup 04b exactly. Tokens come
   from the admin palette (donorfix-design.md) where names match;
   the inside-modal row divider and the chip background come from
   04b-round4-compose-modal.html where design.md has no dedicated
   token. */
/* Round 4 fix 6 (14 October 2026). The whole admin UI relies on the
   HTML `hidden` attribute to show/hide floating things (modal
   backdrops, dropdowns, inline errors, panes). Any class rule
   setting `display` beats the UA stylesheet's `[hidden]{display:none}`
   with normal specificity, so a `.my-thing { display: flex }` would
   render it visible even with `hidden` set. The global rule below
   restores the attribute as the source of truth, project-wide, so
   every `element.hidden = true/false` toggle really works. */
.df-cm-scrim {
    position: fixed; inset: 0;
    background: rgba(20,30,45,.45);
    display: flex; align-items: flex-start; justify-content: center;
    padding: 40px 16px;
    z-index: 1000;
}
/* Round 4 fix 4 (14 October 2026). The form IS the modal, so the
   flex column rule reaches the header, middle and footer directly.
   Header + footer are flex-shrink:0 (always visible); only the
   middle scrolls. The modal is resizable via drag handles (CSS
   below) and an expand button in the header (JS). */
.df-cm-modal {
    width: var(--df-cm-w, min(760px, 100%));
    height: var(--df-cm-h, auto);
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 80px);
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(0,0,0,.25);
    display: flex; flex-direction: column;
    position: relative;
    overflow: hidden;
    font-family: Figtree, -apple-system, BlinkMacSystemFont, sans-serif;
}
.df-cm-modal.df-cm-expanded {
    width: calc(100vw - 32px);
    height: calc(100vh - 80px);
}
.df-cm-mh {
    flex-shrink: 0;
    display: flex; align-items: center; justify-content: space-between;
    padding: 18px 24px; border-bottom: 1px solid #E3E5EA;
}
.df-cm-mh h2 { margin: 0; font-size: 18px; font-weight: 600; color: #1C2530; }
.df-cm-mh-buttons { display: flex; align-items: center; gap: 4px; }
.df-cm-x, .df-cm-expand {
    width: 36px; height: 36px; border-radius: 8px;
    border: 0; background: transparent; cursor: pointer;
    color: #667080; font-size: 22px; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
}
.df-cm-x:hover, .df-cm-expand:hover { background: #F4F5F7; }
.df-cm-expand svg { width: 16px; height: 16px; }
.df-cm-mb {
    flex: 1 1 auto;
    min-height: 0;
    padding: 4px 24px 0;
    overflow: auto;
}

/* Resize handles. The south-east corner is a visible diagonal; the
   bottom edge and the right edge are thin invisible strips. */
.df-cm-resize { position: absolute; z-index: 2; }
.df-cm-resize-s  { left: 8px; right: 8px; bottom: 0; height: 8px; cursor: ns-resize; }
.df-cm-resize-e  { right: 0; top: 8px; bottom: 8px; width: 8px; cursor: ew-resize; }
.df-cm-resize-se { right: 0; bottom: 0; width: 14px; height: 14px; cursor: nwse-resize; background:
    linear-gradient(135deg, transparent 50%, #A9B0BA 50%, #A9B0BA 60%, transparent 60%, transparent 70%, #A9B0BA 70%, #A9B0BA 80%, transparent 80%);
}
.df-cm-modal.df-cm-expanded .df-cm-resize { display: none; }

.df-cm-row {
    display: grid; grid-template-columns: 72px 1fr;
    align-items: center; gap: 12px;
    min-height: 52px; border-bottom: 1px solid #EDEFF2;
}
.df-cm-row label { color: #667080; font-size: 14px; }
/* The chromeless input styling is for the main compose rows only
   (To, Subject, From). Round 9 fix 2/3/5: the signature layout
   pop-up re-uses `.df-cm-row` for Font / Organization line /
   Website / Extra link; it needs real boxed inputs. Scope the
   chromeless rule to the compose form, and give the signature
   editor its own box style below. */
.df-cm-modal .df-cm-row select,
.df-cm-modal .df-cm-row input[type="text"],
#df-cm-subject {
    width: 100%; border: 0; background: transparent;
    font: 15px Figtree, sans-serif; color: #1C2530;
    padding: 12px 0; outline: none;
}
.df-cm-to { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.df-cm-tag {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 10px 4px 12px; border-radius: 999px;
    background: #EEF2F8; color: #1F3A5F; font-weight: 500;
    margin: 4px 0; font-size: 14px;
}
.df-cm-tag small { color: #667080; font-weight: 400; font-size: 13px; }
.df-cm-tag.df-cm-tag-off { background: #F4F5F7; color: #A9B0BA; text-decoration: line-through; }
.df-cm-tag .df-cm-tag-x {
    width: 20px; height: 20px; border-radius: 50%;
    display: inline-grid; place-items: center;
    color: #667080; cursor: pointer; font-size: 16px; line-height: 1;
    background: transparent; border: 0;
}
.df-cm-ccl { margin-left: auto; color: #1F3A5F; font-weight: 500; font-size: 14px; text-decoration: none; cursor: pointer; }
.df-cm-ccl:hover { text-decoration: underline; }

.df-cm-chip-input { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; width: 100%; }
.df-cm-chip-input .df-cm-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.df-cm-chip-input .df-cm-chip-text { flex: 1 1 160px; min-width: 160px; }
.df-cm-dd {
    position: absolute; top: 44px; left: 0; right: 0;
    background: #fff; border: 1px solid #E3E5EA; border-radius: 8px;
    box-shadow: 0 10px 30px rgba(0,0,0,.08); z-index: 1010;
    max-height: 240px; overflow: auto;
}
.df-cm-dd .row {
    padding: 10px 14px; display: flex; justify-content: space-between; cursor: pointer;
}
.df-cm-dd .row:hover { background: #F4F5F7; }
.df-cm-dd .row.off { color: #A9B0BA; cursor: not-allowed; }
.df-cm-dd .row small { color: #667080; }

/* Toolbar */
.df-cm-tb {
    display: flex; align-items: center; gap: 2px; flex-wrap: wrap;
    padding: 10px 0; border-bottom: 1px solid #EDEFF2;
}
.df-cm-ib {
    height: 34px; min-width: 34px; padding: 0 8px;
    border-radius: 8px; border: 0; background: transparent;
    color: #3A4656;
    display: inline-flex; align-items: center; justify-content: center; gap: 4px;
    cursor: pointer; font: 500 14px Figtree, sans-serif;
}
.df-cm-ib:hover { background: #F4F5F7; }
.df-cm-ib.on { background: #E3EAF3; color: #1F3A5F; }
.df-cm-ib svg { width: 18px; height: 18px; }
.df-cm-sep { width: 1px; height: 22px; background: #E3E5EA; margin: 0 6px; }
.df-cm-pop { position: relative; }
.df-cm-colourbar { width: 14px; height: 4px; border-radius: 2px; display: inline-block; }
.df-cm-menu {
    position: absolute; top: 40px; left: 0;
    background: #fff; border: 1px solid #E3E5EA; border-radius: 8px;
    box-shadow: 0 10px 30px rgba(0,0,0,.1); padding: 6px; z-index: 1010;
    min-width: 150px;
}
.df-cm-menu .df-cm-menu-item {
    padding: 8px 12px; border-radius: 6px; cursor: pointer; color: #1C2530;
}
.df-cm-menu .df-cm-menu-item:hover { background: #F4F5F7; }
.df-cm-menu .df-cm-menu-item.on { background: #F4F5F7; }
.df-cm-menu .df-cm-palette { display: grid; grid-template-columns: repeat(5, 26px); gap: 8px; padding: 10px; }
.df-cm-menu .df-cm-palette .df-cm-swatch {
    width: 26px; height: 26px; border-radius: 50%;
    border: 2px solid #fff; box-shadow: 0 0 0 1px #E3E5EA; cursor: pointer;
}
.df-cm-menu .df-cm-palette small { grid-column: 1/-1; color: #667080; font-size: 12px; }

/* Editor body + attachments.
   Round 4 fix 7 (14 October 2026). Decision 41: the writing box
   and the arriving email both use Arial, 16px with the same
   line-height. Gmail's own default is Arial 14px with its own
   paragraph margins, so Rob saw the sent email as smaller text
   and bigger paragraph gaps than what he typed. We change the
   writing box here and wrap the sent HTML with a matching style
   block in EmailHtmlSanitizer::wrapForEmail. */
.df-cm-body {
    min-height: 220px; padding: 16px 0; line-height: 1.6;
    outline: none; font: 16px/1.6 Arial, Helvetica, sans-serif; color: #1C2530;
}
.df-cm-body p { margin: 0 0 10px 0; }
.df-cm-body ul, .df-cm-body ol { padding-left: 22px; margin: 6px 0; }
/* Fix 4 (14 October 2026). Links in the editor look like links —
   navy and underlined — so the author can see which text is linked. */
.df-cm-body a { color: #1F3A5F; text-decoration: underline; }
.df-cm-body a:hover { color: #132640; }
.df-cm-atts { padding: 6px 0 14px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.df-cm-att-list { display: flex; flex-wrap: wrap; gap: 8px; }
.df-cm-att {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 6px 8px 6px 12px; border: 1px solid #E3E5EA;
    border-radius: 10px; background: #FAFBFC; font-size: 14px;
}
.df-cm-att small { color: #667080; }
.df-cm-att-x {
    color: #667080; cursor: pointer; font-weight: 400;
    background: transparent; border: 0; font-size: 16px; line-height: 1;
}
.df-cm-att-size { color: #667080; font-size: 13px; }

/* Notes (blocked / sent / error) */
.df-cm-box { border-radius: 8px; padding: 12px 14px; margin: 14px 0 4px; font-size: 14px; }
.df-cm-red { background: #FBE7E6; color: #A3302A; }
.df-cm-amber { background: #FCEFD9; color: #7A4108; }
.df-cm-green { background: #E6F4EC; color: #1E6B43; }

/* Footer. flex-shrink:0 so it never collapses off a 768px laptop
   with 5 attachments (round-4 fix 4, 14 October 2026). */
.df-cm-mf {
    flex-shrink: 0;
    display: flex; align-items: center; gap: 10px;
    padding: 16px 24px; border-top: 1px solid #E3E5EA;
    background: #fff;
}
.df-cm-mf .df-cm-from { color: #667080; font-size: 13px; flex: 1; min-width: 0; }
.df-cm-mf .df-cm-from { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.df-cm-btn {
    display: inline-flex; align-items: center; gap: 8px;
    height: 40px; padding: 0 18px; border-radius: 8px;
    border: 1px solid #D3D7DE; background: #fff;
    color: #1F3A5F; font: 600 15px Figtree, sans-serif; cursor: pointer;
}
.df-cm-btn:hover { background: #F4F5F7; }
.df-cm-btn-primary {
    background: #1F3A5F; border-color: #1F3A5F; color: #fff;
}
.df-cm-btn-primary:hover { background: #132640; border-color: #132640; }
.df-cm-btn[disabled] { background: #A9B5C3; border-color: #A9B5C3; cursor: not-allowed; color: #fff; }

/* The "Write email" button on the contact page uses the primary
   navy style everywhere it appears (Rob, 13 October 2026). */
.emails-tab .btn.p,
.emails-tab a.btn.p { background: #1F3A5F; border-color: #1F3A5F; color: #fff; }
.emails-tab .btn.p:hover,
.emails-tab a.btn.p:hover { background: #132640; border-color: #132640; }

/* Round 4 fix 4 (14 October 2026). Link mini-popup inside the
   compose modal. Positioned by JS near the Link toolbar button,
   so the author gets a proper small pop-up instead of the browser
   prompt. Shows a Remove link button when the caret is on an
   existing <a>. */
.df-cm-linkpop {
    position: absolute;
    z-index: 1020;
    background: #fff;
    border: 1px solid #E3E5EA;
    border-radius: 10px;
    box-shadow: 0 10px 30px rgba(0,0,0,.12);
    padding: 12px 14px;
    width: 320px;
    display: flex; flex-direction: column; gap: 8px;
}
.df-cm-linkpop label { font-size: 12px; color: #667080; }
.df-cm-linkpop input[type="text"] {
    width: 100%; border: 1px solid #D3D7DE; border-radius: 6px;
    padding: 8px 10px; font: 14px Figtree, sans-serif; color: #1C2530;
    outline: none;
}
.df-cm-linkpop input[type="text"]:focus { border-color: #1F3A5F; }
.df-cm-linkpop-actions { display: flex; justify-content: flex-end; gap: 6px; }
.df-cm-btn-small { height: 32px; padding: 0 12px; font-size: 13px; }
.df-cm-linkpop-err { color: #A3302A; font-size: 13px; }

/* Round 4 fix 5 (14 October 2026). DonorFix dialogs used inside
   the compose modal in place of window.confirm / alert. The scrim
   is dark + flex-centred like .df-cm-scrim; the dialog itself is
   a small rounded card. */
.df-cm-dlg-scrim {
    position: fixed; inset: 0;
    background: rgba(20,30,45,.55);
    display: flex; align-items: center; justify-content: center;
    padding: 20px;
    z-index: 1100;
}
.df-cm-dlg {
    background: #fff; border-radius: 12px;
    box-shadow: 0 24px 60px rgba(0,0,0,.25);
    width: min(440px, 100%);
    padding: 20px 22px 18px;
    font-family: Figtree, -apple-system, BlinkMacSystemFont, sans-serif;
    color: #1C2530;
}
.df-cm-dlg-title { margin: 0 0 8px 0; font-size: 17px; font-weight: 600; }
.df-cm-dlg-body  { margin: 0 0 12px 0; color: #3A4656; font-size: 14px; line-height: 1.5; }
.df-cm-dlg-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* ================================================================
   Stage 18 Round 5 — group send (14 October 2026).
   Minimum styles for tick-boxes on the lists, the action bar, the
   group-mode compose pop-up, and the send-report tiles / chips.
   The one-to-one compose modal keeps its own styles above; shared
   .df-cm-* classes carry over. */
.df-tick-col { width: 32px; text-align: center; padding: 0 4px; }
.df-list-with-ticks td.df-tick-col,
.df-list-with-ticks th.df-tick-col { vertical-align: middle; }

.df-select-bar {
    position: sticky; top: 0; z-index: 20;
    background: #F2F6FA; border: 1px solid #B6CBE8; border-radius: 6px;
    padding: 10px 14px; margin: 10px 0;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.df-select-count { font-size: 15px; color: #1C2530; }
.df-select-count strong { font-size: 20px; margin-right: 4px; }
.df-select-actions { display: flex; gap: 8px; }

.df-cm-group .df-cm-group-caption {
    font-size: 15px; color: #1C2530;
}
.df-cm-group .df-cm-group-caption strong { display: block; margin-bottom: 2px; }
.df-cm-amber {
    background: #FFF7E0; border: 1px solid #E6C879; color: #3D2E07;
    padding: 10px 12px; border-radius: 6px; margin: 10px 0;
}
.df-cm-blue {
    background: #EAF2FB; border: 1px solid #B6CBE8; color: #0F2D52;
    padding: 10px 12px; border-radius: 6px; margin: 10px 0;
}
.df-cm-skipped-head { font-weight: 600; margin-bottom: 4px; }
.df-cm-skipped-list { margin: 0; padding-left: 20px; }
.df-cm-copy-row { align-items: center; }
.df-cm-copy-caveat { color: #667080; font-size: 13px; margin-left: 10px; }
.df-cm-limit-line {
    background: #F6F7F9; border: 1px solid #D3D7DE; color: #1C2530;
    padding: 8px 12px; border-radius: 6px; margin: 10px 0; font-size: 14px;
}

/* Send-report tiles + chip filters */
.df-tiles { display: flex; gap: 12px; flex-wrap: wrap; margin: 8px 0 4px; }
.df-tile {
    flex: 1 1 150px; background: #F6F7F9; border: 1px solid #D3D7DE; border-radius: 6px;
    padding: 10px 12px; min-width: 120px;
}
.df-tile-k { color: #667080; font-size: 13px; margin-bottom: 2px; }
.df-tile-v { color: #1C2530; font-size: 22px; font-weight: 600; }
.df-chips { display: flex; gap: 8px; flex-wrap: wrap; }
.df-chip {
    display: inline-block; padding: 4px 10px; border: 1px solid #D3D7DE;
    border-radius: 999px; color: #1C2530; background: #fff; text-decoration: none; font-size: 14px;
}
.df-chip.on { background: #1F3A5F; color: #fff; border-color: #1F3A5F; }

/* Round 5 fix 1g (14 October 2026): coloured status tags on the
   send report, matching mockup 04b. Sent green, Waiting and Skipped
   amber (both are "not sent yet / not sent"), Failed red, Cancelled
   grey. One shared base so the shape is identical. */
.df-tag {
    display: inline-block; padding: 2px 8px; border-radius: 999px;
    font-size: 12px; font-weight: 600; line-height: 18px;
    border: 1px solid transparent; white-space: nowrap;
}
.df-tag-green { background: #E6F4EA; color: #1E6B35; border-color: #BFE0CB; }
.df-tag-amber { background: #FFF4D6; color: #8A5A00; border-color: #F0D48A; }
.df-tag-red   { background: #FBE7E7; color: #8A1E1E; border-color: #F0B9B9; }
.df-tag-grey  { background: #EEF0F3; color: #4C566B; border-color: #D3D7DE; }
/* Round 6b-1 (15 October 2026): a Received email's status tag is
   blue. The same colour family is used for the "Pictures hidden"
   note and the Unread pill on the Emails tab. */
.df-tag-blue  { background: #E6ECF4; color: #1F3A5F; border-color: #B7C6DC; }

/* Round 6b-1: the "Show pictures" note sits at the top of the body
   when the received email has outside images and the user hasn't
   clicked Show pictures yet. Same soft blue as the Received tag. */
.df-cm-box.df-cm-blue {
    background: #E6ECF4; color: #1F3A5F; border: 1px solid #B7C6DC;
    border-radius: 8px; padding: 10px 14px; margin: 14px 0;
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.df-cm-show-pics .btn { margin-left: auto; }

/* Round 6b-1: Received label on the Emails tab, blue. Unread next
   to it is red so a staff member can see at a glance what hasn't
   been opened. */
.pill-received { background: #E6ECF4; color: #1F3A5F; }
.pill-unread   { background: #FBE7E7; color: #8A1E1E; }

/* Round 5 fix 1b (14 October 2026). Group mode uses the same
   attachments UX as one-to-one — shared file_path, one file on disk
   per group send, kept alive by a refcount check in
   EmailAttachments. The Attach button and the attachments tray stay
   visible in group mode. */

/* Worklist tick cell: inline the box inside the Donor cell so the
   .wl-tbl grid stays intact. */
.wl-donor-cell { display: flex; align-items: center; gap: 8px; }
.wl-donor-cell .wl-tick { flex: 0 0 auto; }

/* Round 6a (14 October 2026): the Open-email pop-up sits in the
   same scrim as the compose pop-up (`#df-compose-modal`). Its view
   shell (`#df-cm-view`) is a flex column that matches the write
   form's shape: header (shrink: 0), body (flex: 1, scroll),
   footer (shrink: 0). The Expand button on the view shell toggles
   `.df-cm-expanded`, the same class used on the compose form. */
.df-cm-view-shell {
    width: min(760px, 100%); background: #fff; border-radius: 12px;
    box-shadow: 0 12px 40px rgba(28, 37, 48, .25);
    display: flex; flex-direction: column;
    max-height: calc(100vh - 56px); overflow: hidden;
}
.df-cm-view-shell.df-cm-expanded {
    width: 100%; max-height: calc(100vh - 8px);
}
.df-cm-view-rows { display: block; }
.df-cm-view-rows .df-cm-row {
    display: grid; grid-template-columns: 72px minmax(0,1fr); gap: 10px;
    padding: 9px 0; border-bottom: 1px solid #EDEFF2; align-items: center;
}
.df-cm-view-rows .df-cm-row label { color: #667080; font-size: 15px; }
.df-cm-view-mail {
    font: 16px/1.6 Arial, Helvetica, sans-serif; color: #1C2530;
    padding: 16px 0 4px;
}
.df-cm-view-mail p { margin: 0 0 10px; }
.df-cm-view-mail a { color: #1F3A5F; text-decoration: underline; }
/* Round 6b-1 fix 5 (15 October 2026): never let a picture in a
   received email be wider than the pop-up. Height: auto preserves
   the picture's aspect ratio when max-width kicks in. The HTML
   width/height attributes (plain numbers only, kept by the
   sanitiser) still size a picture that fits. */
.df-cm-view-mail img { max-width: 100%; height: auto; }

/* Round 6c fix 4 (16 October 2026): nav bell, dashboard "New
   replies" row and the Emails hub page. Mockup 06c-emails-page.html,
   built on donorfix-design.md. */
/* Round 6c fix 4 (16 October 2026). Rob: "it spoils the demo just
   need a subtle icon next to email notifiction bell". Muted text
   plus a small line icon; hover title spells it out. The marker
   only renders when the demo switch is on. */
.df-mfa-off {
    display: inline-flex; align-items: center; gap: 4px;
    color: #667080; font-size: 13px; margin-right: 10px;
    user-select: none;
}
.df-mfa-off-icon { display: inline-flex; }
.df-mfa-off-icon svg {
    width: 14px; height: 14px; stroke: currentColor; fill: none;
    stroke-width: 1.5;
}
.df-mfa-off-text { line-height: 1; }

.df-nav-bell { position: relative; display: inline-flex; align-items: center; margin-right: 10px; }
.df-nav-bell-btn {
    width: 38px; height: 38px; border: 0; border-radius: 6px; background: transparent;
    color: #1F3A5F; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    position: relative;
}
.df-nav-bell-btn:hover,
.df-nav-bell-btn[aria-expanded="true"] { background: #eef1f5; }
.df-nav-bell-icon { display: inline-flex; }
.df-nav-bell-icon svg { width: 20px; height: 20px; stroke: currentColor; fill: none; }
.df-nav-bell-count {
    position: absolute; top: 2px; right: 0;
    min-width: 20px; height: 20px; padding: 0 5px; border-radius: 999px;
    background: #1F3A5F; color: #fff;
    font-size: 13px; font-weight: 500; line-height: 20px;
    display: inline-flex; align-items: center; justify-content: center;
}
.df-nav-bell[data-count="0"] .df-nav-bell-count { display: none; }
.df-nav-bell-dropdown[hidden] { display: none !important; }
.df-nav-bell-dropdown {
    position: absolute; top: calc(100% + 4px); right: 0;
    width: 380px; max-width: calc(100vw - 40px); max-height: 480px; overflow: auto;
    background: #fff; border: .5px solid #D6D2C8; border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); z-index: 90;
}
.df-nav-bell-head {
    padding: 10px 14px; background: #F8F7F3; border-bottom: .5px solid #E4E1D8;
}
.df-nav-bell-head b { font-weight: 500; }
.df-nav-bell-list { list-style: none; margin: 0; padding: 0; }
.df-nav-bell-item { border-top: .5px solid #E4E1D8; }
.df-nav-bell-item:first-child { border-top: 0; }
.df-nav-bell-item a {
    display: block; padding: 10px 14px; color: #1c2530; text-decoration: none;
}
.df-nav-bell-item a:hover { background: #eef1f5; }
.df-nav-bell-row-top {
    display: flex; justify-content: space-between; gap: 10px; align-items: baseline;
}
.df-nav-bell-row-who {
    font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.df-nav-bell-row-when { color: #667080; white-space: nowrap; font-size: 13px; }
.df-nav-bell-row-subject {
    display: block; color: #667080; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.df-nav-bell-empty { padding: 14px; color: #667080; }
.df-nav-bell-foot {
    display: block; padding: 10px 14px; text-align: left; background: #F8F7F3;
    color: #1F3A5F; text-decoration: none; border-top: .5px solid #E4E1D8;
}
.df-nav-bell-foot:hover { background: #eef1f5; }

/* Round 6c fix 4 (16 October 2026). The amber "New replies waiting"
   bar is gone — the dashboard row now lives INSIDE Needs attention
   with the same shape as the other rows (mockup 08). */
.dash-attention-row-new-emails { /* styled by .dash-attention-row */ }

/* Pill colours for the new email states that the Emails tab +
   Emails page use. */
.pill-unread  { background: #FFE2B2; color: #7A4100; }
.pill-waiting { background: #FFE2B2; color: #7A4100; }
.pill-received { background: #D5EAFF; color: #1F3A5F; }
.pill-sent    { background: #D7F0D9; color: #0F5A1C; }
.pill-draft   { background: #E3E5EA; color: #3D434D; }
.pill-failed  { background: #FBD0C1; color: #7A2210; }
.pill-forwarded { background: #E9DCF8; color: #4B1E89; }
.pill-queued  { background: #FFF1C8; color: #6F5000; }
.pill-cancelled { background: #E3E5EA; color: #3D434D; }
.pill-skipped { background: #E3E5EA; color: #3D434D; }

/* Round 6c fix 4 (16 October 2026): Emails hub page. Mockup
   06c-emails-page.html, built on donorfix-design.md's list-page
   style — navy list band with chips and search, time-column rows,
   shared pill colours. */
/* 25 October 2026 — the `.df-emails-page` 1400 cap is removed;
   the Emails hub inherits `.page`'s 1440 cap like every other
   page. */
.df-emails-page { }
.df-emails-band {
    background: #1F3A5F; border-radius: 12px; padding: 16px 18px;
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 18px;
    align-items: center; margin-bottom: 12px;
}
.df-emails-band-eyebrow { color: #C9D5E6; }
.df-emails-band-big { color: #fff; font-size: 22px; font-weight: 500; line-height: 1.2; }
.df-emails-band-sub { color: #E6ECF4; }
.df-emails-band-right { display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
.df-emails-search input[type="search"] {
    height: 38px; width: 340px; max-width: 100%;
    border: 1px solid #46618A; border-radius: 6px; background: #2B4A73;
    color: #fff; font: 15px Figtree, sans-serif; padding: 0 12px;
}
.df-emails-search input[type="search"]::placeholder { color: #C9D5E6; }
.df-emails-chips { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.df-ec-chip {
    font: 15px Figtree, sans-serif; height: 32px; padding: 0 14px;
    border-radius: 999px; border: 1px solid #46618A;
    background: transparent; color: #E6ECF4; text-decoration: none;
    display: inline-flex; align-items: center; gap: 8px;
}
.df-ec-chip:hover { background: #2B4A73; }
.df-ec-chip .df-ec-n {
    background: #2B4A73; border-radius: 999px; padding: 0 8px; line-height: 22px;
}
.df-ec-chip.is-on {
    background: #fff; border-color: #fff; color: #1F3A5F; font-weight: 500;
}
.df-ec-chip.is-on .df-ec-n { background: #DFE5EE; color: #1F3A5F; }

.df-emails-hint { margin: 0 0 12px; color: #667080; }

.df-emails-list {
    background: #fff; border: .5px solid #D6D2C8; border-radius: 12px; overflow: hidden;
}
.df-emails-row {
    display: grid; grid-template-columns: 14px 108px minmax(0, 1fr) auto;
    gap: 14px; align-items: start; padding: 12px 16px;
    border-top: .5px solid #E4E1D8; cursor: pointer; line-height: 1.55;
    color: #1c2530;
}
.df-emails-row:first-child { border-top: 0; }
.df-emails-row:nth-child(even) { background: #F8F7F3; }
.df-emails-row:hover { background: #eef1f5; }
.df-emails-row:focus-visible { outline: 2px solid #1F3A5F; outline-offset: -2px; }
.df-emails-dot {
    width: 9px; height: 9px; border-radius: 50%; background: transparent; margin-top: 7px;
}
.df-emails-row.is-unread .df-emails-dot { background: #1F3A5F; }
.df-emails-rel { font-weight: 500; }
.df-emails-abs { color: #667080; white-space: nowrap; }
.df-emails-what { min-width: 0; }
.df-emails-l1 { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
.df-emails-who { white-space: nowrap; }
.df-emails-subj { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.df-emails-row.is-unread .df-emails-who,
.df-emails-row.is-unread .df-emails-subj { font-weight: 500; }
.df-emails-l2 { color: #667080; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.df-emails-tags {
    display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
    justify-content: flex-end; padding-top: 1px;
}
.df-emails-pill {
    display: inline-block; padding: 0 10px; border-radius: 999px; border: 1px solid;
    white-space: nowrap; font-size: 14px;
}
.df-emails-pill-rec   { background: #E6ECF4; color: #1F3A5F; border-color: #B7C6DC; }
.df-emails-pill-sent  { background: #E7F1E9; color: #1E6B45; border-color: #B5D8C0; }
.df-emails-pill-draft { background: #eef1f5; color: #667080; border-color: #D6D2C8; }
.df-emails-pill-wait  { background: #FCEFD9; color: #7A4108; border-color: #E8CE8F; }
.df-emails-pill-fail  { background: #FBD0C1; color: #8E1F17; border-color: #E6B3A3; }
.df-emails-empty { padding: 18px 16px; color: #667080; }

@media (max-width: 1000px) {
    .df-emails-band { grid-template-columns: 1fr; }
    .df-emails-band-right { align-items: stretch; }
    .df-emails-chips { justify-content: flex-start; }
    .df-emails-search input[type="search"] { width: 100%; }
}
@media (max-width: 700px) {
    .df-emails-row { grid-template-columns: 12px minmax(0, 1fr); }
    .df-emails-when { display: none; }
    .df-emails-tags { grid-column: 2; justify-content: flex-start; }
}

.df-cm-view-atts { margin-top: 10px; }
.df-cm-view-att {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    border: 1px solid #E3E5EA; border-radius: 8px; padding: 8px 12px;
    margin-top: 8px;
}
.df-cm-view-att-name { font-weight: 500; }
.df-cm-view-att-note { margin: 6px 0 0; }

/* Forward mode: quote block under the editor. */
.df-cm-fwd-quote {
    border-top: 1px solid #EDEFF2; margin-top: 10px; padding-top: 10px;
    font: 16px/1.6 Arial, Helvetica, sans-serif; color: #1C2530;
}
.df-cm-fwd-quote .df-cm-fwd-hd { color: #4C566B; }

/* Attachment chips on a forward (× removable). */
.df-cm-att-chip {
    display: inline-flex; align-items: center; gap: 6px;
    background: #E6ECF4; color: #1F3A5F; border-radius: 999px;
    padding: 2px 6px 2px 12px; margin: 2px 4px 2px 0;
}
.df-cm-att-chip b { font-weight: 500; }
.df-cm-att-chip .df-cm-att-x {
    border: 0; background: none; color: #667080; cursor: pointer; font-size: 16px; line-height: 1;
}

/* Pills on the Emails tab for Round 6a labels. The existing .pill +
   .pill-sent / .pill-draft / .pill-failed stay. New ones match the
   send-report colour palette. */
.pill-queued    { background: #FFF4D6; color: #8A5A00; }
.pill-cancelled { background: #EEF0F3; color: #4C566B; }
.pill-forwarded { background: #E6ECF4; color: #1F3A5F; }
.pill-skipped   { background: #FFF4D6; color: #8A5A00; }

/* Round 6a fix 2 (14 October 2026): a DNC-blocked chip inside the
   Forward To row is red (mockup 07a state 7), overriding the grey
   `.df-cm-tag-off` style the CC/BCC rows use. Scoped so only the
   forward chip row sees red. */
.df-cm-row-fwdto .df-cm-tag.df-cm-tag-off {
    background: #FBE7E6;
    color: #A3302A;
    text-decoration: none;
}
.df-cm-row-fwdto .df-cm-tag.df-cm-tag-off small { color: #A3302A; }

/* ================================================================
   Stage 18 Round 7a Part B fix 1 (17 October 2026).
   Template editor pop-up — wider version of the compose shell.
   See app/admin/_template-editor-modal.inc.php and
   templates-list.js. Decision 66: pop-up, not a page.
   ================================================================ */
.df-cm-modal.df-cm-wide { width: var(--df-cm-w, min(1024px, calc(100vw - 32px))); }
.df-tpl-seg { display: inline-flex; border: 1px solid #D6D2C8; border-radius: 999px; overflow: hidden; }
.df-tpl-seg-btn {
    appearance: none; border: 0; background: transparent;
    padding: 6px 16px; cursor: pointer; color: #1F3A5F;
    font: 15px Figtree, sans-serif;
}
.df-tpl-seg-btn.on { background: #1F3A5F; color: #fff; font-weight: 500; }
.df-tpl-radios { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.df-tpl-help   { color: #667080; margin-left: 10px; font-size: 13px; }
.df-tpl-footnote { color: #667080; font-size: 13px; margin: 2px 0 10px; }
.df-tpl-footnote code { background: #F4F5F7; padding: 1px 4px; border-radius: 4px; }
.df-tpl-sentto { font-size: 14px; }
.df-tpl-delete { color: #8E1F17; border-color: #D6D2C8; background: transparent; }
.df-tpl-delete:hover { background: #FBE7E6; }
.df-tpl-prev-shell {
    padding: 14px; background: #F8F7F3;
    border-radius: 8px; margin-top: 10px;
}
.df-tpl-prev-body {
    margin-top: 10px; background: #fff; padding: 14px;
    border: 1px solid #D6D2C8; border-radius: 8px;
}
.df-tpl-prev-body mark.df-miss {
    background: #FCEFD9; color: #7A4108; padding: 1px 4px; border-radius: 4px;
}
.df-tpl-pick-row input[type="text"] { width: 100%; }

/* Decision 66 (17 Oct 2026): the template editor pop-up has
   z-index 1000 (.df-cm-scrim). The shared dfConfirm modal at
   #df-modal-confirm needs to sit above it. */
#df-modal-confirm { z-index: 2000; }

/* ================================================================
   Stage 18 Round 7a Part B fix 2 (17 October 2026).
   Template editor pop-up — Squire toolbar, Arial 16px writing box,
   merge-field panel, df-tok tags. See _template-editor-modal.inc.php
   and templates-list.js.
   ================================================================ */
.df-tpl-subject {
    min-height: 40px; padding: 8px 12px;
    border: 1px solid #D6D2C8; border-radius: 6px;
    background: #fff; color: #1C2530;
    font: 16px Arial, Helvetica, sans-serif; line-height: 1.6;
    white-space: nowrap; overflow-x: auto; overflow-y: hidden;
}
.df-tpl-subject:focus { outline: 2px solid #1F3A5F; outline-offset: -2px; }
.df-tpl-subject:empty::before { content: attr(data-placeholder); color: #9DA7B6; }

.df-tpl-tb { margin-top: 10px; }

.df-tpl-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 240px;
    gap: 12px;
    align-items: stretch;
}
.df-tpl-body {
    min-height: 240px;
    /* Inherits the Arial 16px / line-height 1.6 writing-box look
       from .df-cm-body in the compose section. */
}
.df-tpl-fields {
    border: 1px solid #E3E5EA; border-radius: 8px;
    padding: 12px; background: #F8F7F3;
    display: flex; flex-direction: column; gap: 6px;
    min-width: 0;
}
.df-tpl-fields h3 { margin: 0 0 2px; font-size: 14px; font-weight: 600; color: #1C2530; }
.df-tpl-fields-help { margin: 0 0 6px; color: #667080; font-size: 13px; }
.df-tpl-fbtns { display: flex; flex-direction: column; gap: 4px; }
.df-tpl-fbtn {
    appearance: none; text-align: left; cursor: pointer;
    border: 1px solid #D6D2C8; border-radius: 6px;
    background: #fff; color: #1F3A5F; padding: 7px 10px;
    font: 14px Figtree, sans-serif;
    display: block; width: 100%;
}
.df-tpl-fbtn:hover { background: #EEF1F5; border-color: #B7C6DC; }
.df-tpl-later {
    color: #667080; font-size: 13px; margin-top: 6px;
    padding: 7px 10px; border-top: 1px dashed #D6D2C8;
}

/* The labelled tag the editor inserts. Non-selectable half-edits,
   atomic from the browser's point of view because of
   contenteditable="false". */
.df-tok {
    display: inline-block;
    background: #DFE5EE; color: #1F3A5F;
    border: 1px solid #B7C6DC; border-radius: 4px;
    padding: 0 6px; margin: 0 1px;
    font-weight: 500; font-size: 14px; line-height: 1.4;
    user-select: all;
}

/* Server's amber marker for a missing-detail preview. */
.df-miss {
    background: #FCEFD9; color: #7A4108;
    padding: 1px 4px; border-radius: 4px;
}

/* Stage 18 Round 7b (18 October 2026) — 7a fix 6 (decision 81).
   Merge-field panel pins to the top of the grid cell as the body
   scrolls, so all seven buttons stay visible while the writer
   scrolls through the message. */
.df-tpl-fields {
    position: sticky;
    top: 0;
    align-self: start;
}

/* Scheduled chip + amber Scheduled pill (Round 7b, decision 79). */
.df-emails-pill-sched {
    background: #FCEFD9; color: #7A4108;
    border: 1px solid #E8CE8F;
}
.pill-queued { background: #FCEFD9; color: #7A4108; border: 1px solid #E8CE8F; }

/* Schedule panel inside the compose pop-up (mockup 10b screen E). */
.df-cm-sched {
    border: 1px solid #E8CE8F; background: #FFFBEE;
    border-radius: 8px; padding: 12px 14px; margin: 14px 0 0;
}
.df-cm-sched-h { margin: 0 0 8px; font-size: 15px; font-weight: 500; color: #7A4108; }
.df-cm-sched-qp { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.df-cm-sched-qpbtn.is-on { background: #1F3A5F; border-color: #1F3A5F; color: #fff; }
.df-cm-sched-own { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
.df-cm-sched-own input[type="date"],
.df-cm-sched-own select { height: 34px; padding: 0 10px; border: 1px solid #D6D2C8; border-radius: 6px; }
.df-cm-sched-tzlabel { color: #667080; font-size: 13px; }
.df-cm-sched-says { color: #1C2530; font-size: 14px; margin: 6px 0 10px; }
.df-cm-sched-err  { color: #8E1F17; }
.df-cm-sched-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* 7a fix 4 (decision 71): missing-detail amber note + "Fix missing
   detail" button in the editor pop-up. */
.df-cm-box.df-cm-amber.df-tpl-miss { margin: 10px 0 4px; }

/* 7a fix 5 (decision 83): the rest-days help line reads from the
   value in the box, kept live by settings/email.php JS. */
.se-help-live { color: #667080; margin-top: 4px; }

/* ================================================================
   Stage 18 Round 8 (19 October 2026). Names and signatures —
   Your settings cards, Settings › Email Signatures card, layout
   editor pop-up, and the dashed sig box in Write email.
   ================================================================ */
.r8-card { margin-top: 12px; }
.r8-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px; align-items: flex-start; }
.r8-left { min-width: 0; }
.r8-right { min-width: 0; }

/* Inbox preview on Name on your emails. */
.r8-inbox { border: 1px solid #D6D2C8; border-radius: 10px; overflow: hidden; font-family: Arial, sans-serif; }
.r8-row { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: 10px; padding: 9px 12px; border-top: 1px solid #EDEFF2; font-size: 14px; }
.r8-row:first-child { border-top: 0; }
.r8-row.r8-me { background: #FFFBEA; }
.r8-row b { font-weight: bold; }
.r8-s { color: #555; }

/* Signature preview container in Your settings and Settings. */
.r8-mail { border: 1px solid #D6D2C8; border-radius: 10px; padding: 14px 16px; font: 16px/1.55 Arial, sans-serif; background: #fff; }

/* Settings Signatures card radio and checkbox rows. */
.r8-radio { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border: 1px solid #D6D2C8; border-radius: 8px; margin-bottom: 8px; cursor: pointer; }
.r8-radio.is-on { border-color: #1F3A5F; background: #DFE5EE; }
.r8-chk { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }

/* Dashed sig box in Write email. */
.df-cm-sigbox {
    border: 1px dashed #D6D2C8; border-radius: 8px; padding: 10px 12px;
    background: #F8F7F3; margin: 10px 18px 12px;
}
.df-cm-sigbox-top { display: flex; justify-content: space-between; color: #667080; font-size: 14px; margin-bottom: 6px; font-family: Figtree, sans-serif; }
.df-cm-sigbox-link { color: #1F3A5F; text-decoration: none; cursor: pointer; }
.df-cm-sigbox-link:hover { text-decoration: underline; }
.df-cm-sigbox-body { font: 14px/1.4 Arial, sans-serif; color: #1C2530; }

/* Signature layout editor pop-up. */
.df-sig-ed { display: grid; grid-template-columns: 360px minmax(0, 1fr); min-height: 420px; }
.df-sig-ctl { padding: 14px 18px; border-right: 1px solid #E4E1D8; background: #F8F7F3; }
.df-sig-ctl h3 { font-size: 15px; font-weight: 500; margin: 14px 0 8px; color: #1C2530; }
.df-sig-ctl h3:first-child { margin-top: 0; }
.df-sig-pv  { padding: 14px 18px; }
.df-sig-pv-mail { border: 1px solid #D6D2C8; border-radius: 10px; padding: 14px 16px; font: 16px/1.55 Arial, sans-serif; }
.df-sig-pv-sigline { border-top: 1px dashed #D6D2C8; padding-top: 10px; margin-top: 4px; }
.df-sig-sw { display: flex; gap: 6px; flex-wrap: wrap; }
.df-sig-sw span { width: 26px; height: 26px; border-radius: 6px; cursor: pointer; border: 2px solid #fff; box-shadow: 0 0 0 1px #D6D2C8; }
.df-sig-sw span.on { box-shadow: 0 0 0 2px #1F3A5F; }
.df-sig-order { list-style: none; margin: 0; padding: 0; }
.df-sig-order li { padding: 6px 10px; border: 1px solid #D6D2C8; border-radius: 6px; margin-bottom: 4px; background: #fff; cursor: grab; }
.df-sig-logo-row { display: flex; gap: 10px; align-items: center; margin-bottom: 8px; }

/* Round 9 Round 8 fixes 1-5: signature layout pop-up — real inputs
   with a visible box, a single Upload button, and a wrappy row for
   Extra link so the two inputs don't overlap. */
.df-sig-ctl .df-cm-row {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 10px; min-height: 44px; border-bottom: 0; padding: 4px 0;
}
.df-sig-ctl .df-cm-row > label { min-width: 120px; color: #667080; font-size: 14px; }
.df-sig-ctl .df-cm-row input[type="text"],
.df-sig-ctl .df-cm-row select {
    flex: 1 1 180px; min-width: 180px; max-width: 100%;
    border: 1px solid #D6D2C8; background: #fff; border-radius: 6px;
    font: 15px Figtree, sans-serif; color: #1C2530;
    padding: 8px 10px; outline: none;
}
.df-sig-ctl .df-cm-row input[type="text"]:focus,
.df-sig-ctl .df-cm-row select:focus { border-color: #1F3A5F; box-shadow: 0 0 0 2px #DFE5EE; }
/* Fix 4: the Extra link row carries a label + two inputs; let them
   stack if there's no horizontal room, with the label on its own
   first line and each input flexing to width. */
#df-sig-xt-label, #df-sig-xt-url { flex: 1 1 160px; }
/* Fix 1: the raw <input type=file> is visually hidden; the
   "Upload PNG or JPG" button proxies the click. */
.df-sig-ctl .df-cm-row input[type="file"] { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

/* Round 9 bounce indicator on the contact page (mockup A). */
.bounce-indicator {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
    padding: 12px 14px; margin-bottom: 12px;
    background: #FBE9E7; color: #8E1F17; border: 1px solid #EDB9B3; border-radius: 8px;
}
.bounce-indicator-body { flex: 1; min-width: 220px; }
.bounce-indicator-body b { font-weight: 500; }
.bounce-indicator-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.bounce-indicator-actions .btn.secondary { background: #fff; }
.bounce-strike { text-decoration: line-through; color: #8E1F17; }
.pill.pill-bounced { background: #FBE9E7; color: #8E1F17; border: 1px solid #EDB9B3; }
.df-tag-red { background: #FBE9E7; color: #8E1F17; border: 1px solid #EDB9B3; }
.df-tile-bounced .df-tile-v { color: #8E1F17; }

/* Round 9 mockup B: Compose amber bounce note above the body. */
.df-cm-bounce {
    padding: 10px 12px; margin: 10px 0; border-radius: 8px;
    background: #FCEFD9; color: #7A4108; border: 1px solid #E8CE8F;
}
.df-cm-bounce b { font-weight: 500; }

/* =========================================================
   Stage 19 Round 1 (21 October 2026).
   Settings › Financials, the shared help pop-over, and the
   "Set up by the system" fund badge. Nothing below 15px
   (round-file correction 8 and donorfix-design.md).
   ========================================================= */

/* The help icon "?" and its anchored pop-over. */
.hi {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    margin-left: 6px;
    border-radius: 50%;
    border: 1px solid var(--info-border);
    background: var(--info-bg);
    color: var(--info-ink);
    font: 500 15px var(--font-body);
    line-height: 1;
    cursor: pointer;
    vertical-align: middle;
}
.hi:hover,
.hi:focus-visible { background: var(--accent); color: #fff; border-color: var(--accent); outline: none; }
.hi[aria-expanded="true"] { background: var(--accent); color: #fff; border-color: var(--accent); }

.help-popovers { position: static; }

/* Shared settings-page standard — inline accordion for help. Any
   opted-in settings page loads its own `*-help.js` (the Round 1
   shape is `settings-financials-help.js`); a click on a `.hi[data-help]`
   inserts a `.help-inline-active` panel as a sibling of the nearest
   row / card header / form row / note. The panel is in page flow,
   so nothing is covered — the next row pushes down. Round 2 pages
   adopt by loading their own `settings-<key>-help.js` with the same
   shape. The panel body caps at a reading column so a long help
   text stays readable even inside a wide-screen table row. */
.settings-detail[data-settings] .help-inline-active {
    position: relative;
    max-width: 720px;
    margin: 10px 0;
    padding: 12px 14px 12px 16px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-s);
    background: var(--surface-1);
    color: var(--ink);
    font-size: 15px;
    line-height: 1.5;
}
.settings-detail[data-settings] .help-inline-h {
    margin: 0 32px 6px 0;
    font-size: 16px;
    font-weight: 500;
}
.settings-detail[data-settings] .help-inline-b {
    margin: 0 0 8px;
    font-size: 15px;
}
.settings-detail[data-settings] .help-inline-ex {
    margin: 0;
    padding: 6px 10px;
    background: var(--surface-2);
    border-radius: var(--radius-s);
    font-size: 15px;
    color: var(--muted);
}
.settings-detail[data-settings] .help-inline-close {
    position: absolute;
    top: 6px;
    right: 8px;
    border: 0;
    background: transparent;
    font-size: 20px;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    padding: 2px 6px;
}
.settings-detail[data-settings] .help-inline-close:hover,
.settings-detail[data-settings] .help-inline-close:focus-visible {
    color: var(--accent);
    outline: none;
}
.settings-detail[data-settings] tr.help-inline-row > td {
    padding: 0;
    background: transparent;
}
.settings-detail[data-settings] tr.help-inline-row .help-inline-active {
    margin: 2px 0 10px;
}

.help-pop {
    position: absolute;
    z-index: 30;
    width: 320px;
    max-width: calc(100vw - 24px);
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 14px 16px 16px;
    box-shadow: 0 8px 24px rgba(28, 37, 48, 0.12);
    color: var(--ink);
    font-size: 15px;
    line-height: 1.5;
}
.help-pop[hidden] { display: none; }
.help-pop .help-pop-close {
    position: absolute;
    top: 6px;
    right: 8px;
    border: 0;
    background: transparent;
    font-size: 22px;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    padding: 4px 8px;
}
.help-pop .help-pop-close:hover,
.help-pop .help-pop-close:focus-visible { color: var(--accent); outline: none; }
.help-pop .help-pop-h { margin: 0 24px 6px 0; font-size: 16px; font-weight: 500; }
.help-pop .help-pop-b { margin: 0 0 10px; font-size: 15px; }
.help-pop .help-pop-ex {
    margin: 0;
    padding: 8px 10px;
    background: var(--surface-1);
    border-radius: var(--radius-s);
    font-size: 15px;
}

/* The inline notice used on Settings pages (Decision 10 warn, Areas
   you use info line, info strip at the bottom of Settings › Financials
   pointing at Donations › Funds). Not the top-of-page flash — that
   is `.flash-*`. */
.note {
    border-radius: var(--radius-s);
    padding: 10px 14px;
    border: 1px solid var(--line);
    background: #fff;
    color: var(--ink);
    margin: 12px 0;
    font-size: 15px;
}
.note-warn { background: var(--warning-bg); color: var(--warning-ink); border-color: var(--warning-border); }
.note-info { background: var(--info-bg);    color: var(--info-ink);    border-color: var(--info-border); }
.note .lk, .note a.lk { color: inherit; text-decoration: underline; font-weight: 500; }

/* Shared settings-page standard — inline link style for any `.lk`
   outside a `.note`. Round 2 pages adopt by setting the opt-in
   attribute on their wrapper. Inside a `.note`, the earlier
   `.note .lk` rule inherits the note's colour (unchanged). */
.settings-detail[data-settings] a.lk {
    color: var(--accent);
    text-decoration: underline;
    font-weight: 500;
}
.settings-detail[data-settings] a.lk:hover,
.settings-detail[data-settings] a.lk:focus-visible {
    color: var(--accent-2);
    outline: none;
}

/* Pills used on Settings › Financials (and reusable later). */
.pill-ok   { background: var(--success-bg); color: var(--success-ink); border-color: var(--success-border); }
.pill-grey { background: var(--tint);       color: var(--muted);       border-color: var(--line); }

/* Settings page layout. Non-sticky by default (unchanged for the
   eight non-Financials settings pages). */
.settings-form-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin: 4px 0 10px;
}
.settings-form-head .settings-intro { margin: 0; }

/* Shared settings-page standard: sticky save row sits at the top of
   the content column so Save is always one click away. Opts in via
   the `data-settings` attribute on the `.settings-detail` wrapper. */
.settings-detail[data-settings] .settings-form-head {
    position: sticky;
    top: var(--topbar-h, 56px);
    z-index: 5;
    align-items: center;
    margin: 4px 0 14px;
    padding: 10px 14px;
    background: var(--surface-2);
    border: var(--hairline) solid var(--line);
    border-radius: var(--radius-s);
}

.settings-financials-table .kind-name { font-weight: 500; }
.settings-financials-table .kind-sub  { color: var(--muted); font-size: 15px; margin-top: 2px; }
.settings-financials-table .row-off td { color: var(--muted); }
.settings-financials-acct {
    width: 140px;
    max-width: 100%;
    font: 15px var(--font-body);
    padding: 0 10px;
    height: 36px;
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    background: #fff;
    color: var(--ink);
}
.settings-financials-acct:disabled { background: var(--tint); color: var(--muted); }
.settings-changed-line {
    margin-top: 4px;
    font-size: 15px;
    color: var(--muted);
}
/* Stage 19 Round 4 — Help cover our costs row group under the
   Operational costs card. Three small inputs stacked, each with
   label + hint below. Matches the voice of the existing financials
   card (same font sizes, same colours). */
.card-b-sub { border-top: 1px solid var(--line); margin-top: 8px; padding-top: 14px; }
.card-sub-h { font-size: 15px; font-weight: 600; margin: 0 0 6px; }
.settings-form-rows { display: grid; gap: 14px; max-width: 560px; margin-top: 10px; }
.settings-form-row  { display: grid; gap: 4px; }
.settings-form-row > label { font-weight: 500; }
.settings-form-row .hint { color: var(--muted); font-size: 14px; }
.settings-financials-line { width: 100%; font: 15px var(--font-body); padding: 0 10px; height: 36px; border: 1px solid var(--line); border-radius: var(--radius-s); background: #fff; color: var(--ink); }

/* 25 October 2026 — Fix 2's phone-width `[data-settings]`
   stacking block (data-label::before pseudo-cells, thead hiding,
   sticky-bottom save row) is removed in the "one width across
   the system" change. Settings tables scroll horizontally on
   phone the same way every other admin table does. */

/* Field Lists (Round C, 26 October 2026). Inline flash the save
   endpoint writes to via settings-field-lists.js. Sits at the top
   of the page, under the intro. */
.df-fl-flash {
    display: inline-block;
    padding: 6px 12px;
    border: 1px solid transparent;
    border-radius: 4px;
    font-size: 14px;
    margin-bottom: 10px;
}
.df-fl-flash-ok {
    background: var(--success-bg);
    color: var(--success-ink);
    border-color: var(--success-border);
}
.df-fl-flash-error {
    background: var(--warning-bg);
    color: var(--warning-ink);
    border-color: var(--warning-border);
}
.df-fl-group h2 { color: var(--ink); }
.df-fl-list { padding-top: 4px; }
.df-fl-list h3 { color: var(--ink); }
.df-fl-table thead th {
    background: var(--surface-1);
    font-weight: 500;
    font-size: 13px;
    color: var(--muted);
    text-align: left;
    padding: 6px 8px;
}
.df-fl-table tbody td {
    padding: 6px 8px;
    vertical-align: middle;
}
.df-fl-label-input:focus {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.df-fl-switch input[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}
.df-fl-add .btn {
    padding: 6px 14px;
}

/* -------------------- Round 2b: CRM footer + sign-in shell -------------------- */

/* CRM footer: a thin always-on strip at the bottom of every signed-in
   admin page. Dots mark + "Powered by Nonprofio" + link to
   settings.footer_link_target. Sits inside .app-main under
   main.page, so it is in the content column (right of the sidebar). */
.admin-powered-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 20px;
    border-top: var(--hairline) solid var(--line-2);
    background: var(--surface-2);
    color: var(--muted);
    font-size: 12px;
    line-height: 1;
}
.admin-powered-footer .mark { display: inline-flex; align-items: center; }
.admin-powered-footer .np-dots { height: 10px; width: auto; display: block; }
.admin-powered-footer .word {
    color: var(--ink);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-decoration: none;
}
.admin-powered-footer .word:hover,
.admin-powered-footer .word:focus-visible { text-decoration: underline; }

/* Narrow (sign-in / password reset / accept-invite / MFA) shell —
   signin-mockup.html. Body is light grey, logo centred above a
   white card with a coral top line; "Powered by Nonprofio" at the
   bottom. The card is .page-header + .page-body stacked and
   visually joined by border radii: heading inside the top, form
   inside the bottom, one continuous card to the eye. */
body.narrow {
    background: #F3F4F6;
    min-height: 100vh;
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 72px 16px 32px;
    box-sizing: border-box;
}
/* The logo above the card. Reuses .brand-narrow from the sidebar
   helper (admin_render_brand('narrow')). Sizes to the mockup:
   220px default width, 300px max width, 48px max height. */
body.narrow .signin-logo-wrap {
    margin: 0 0 28px;
    display: flex;
    justify-content: center;
}
body.narrow .signin-logo-wrap .brand-narrow {
    display: inline-flex;
    align-items: center;
}
body.narrow .signin-logo-wrap .logo-slot {
    display: inline-flex;
    align-items: center;
    width: auto;
    max-width: 300px;
}
body.narrow .signin-logo-wrap .logo-slot img {
    display: block;
    width: auto;
    height: auto;
    max-width: 300px;
    max-height: 48px;
}
/* Nonprofio default (vector) sits at 220px like the mockup. A
   client's uploaded raster still scales to the 48px cap. */
body.narrow .signin-logo-wrap .logo-slot img[src$="nonprofio-logo.svg"] {
    width: 220px;
}
/* The card itself — .page becomes the card wrapper. */
body.narrow .page {
    width: 100%;
    max-width: 360px;
    padding: 0;
    margin: 0;
}
/* .page-header is the top of the card: heading + optional subtitle,
   centred, coral top line, rounded top corners. */
body.narrow .page-header {
    background: #FFFFFF;
    border: 1px solid #E3E6E8;
    border-top: 3px solid #D9735B;
    border-bottom: 0;
    border-radius: 12px 12px 0 0;
    padding: 28px 28px 0;
    margin: 0;
    display: block;
}
body.narrow .page-header .ph-title { flex: initial; }
body.narrow .page-header h1 {
    text-align: center;
    font-size: 20px;
    font-weight: 600;
    color: #133436;
    margin: 0 0 4px;
    line-height: 1.25;
}
body.narrow .page-header .subtitle {
    text-align: center;
    font-size: 13px;
    color: #6B7B7B;
    margin: 0;
}
/* .page-body is the bottom of the card: form inside .panel, flat
   borders on top so the two blocks read as one. */
body.narrow .page-body {
    background: #FFFFFF;
    border: 1px solid #E3E6E8;
    border-top: 0;
    border-radius: 0 0 12px 12px;
    padding: 22px 28px 24px;
}
/* Flashes above the form (page_header_inner emits .flashes INSIDE
   .page-header). Make them read inside the top of the card. */
body.narrow .page-header .flashes { padding: 0 0 14px; }
body.narrow .page-header .flashes .flash {
    border-radius: 8px;
    margin: 0 0 10px;
    font-size: 13px;
}
/* The existing .panel inside .page-body loses its chrome — the
   card is the panel now. */
body.narrow .panel {
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: 0;
    margin: 0;
}
/* Full-width inputs and dark submit per the mockup. */
body.narrow .row { margin-bottom: 14px; }
body.narrow .row label {
    display: block;
    font-size: 13px;
    color: #2B3A3C;
    margin-bottom: 6px;
    font-weight: 500;
}
body.narrow .row input[type="email"],
body.narrow .row input[type="password"],
body.narrow .row input[type="text"] {
    width: 100%;
    height: 40px;
    border: 1px solid #D7DBD9;
    border-radius: 8px;
    padding: 0 12px;
    font: inherit;
    font-size: 14px;
    box-sizing: border-box;
}
body.narrow .row input:focus {
    outline: none;
    border-color: #4C7A78;
    box-shadow: 0 0 0 3px rgba(76, 122, 120, 0.15);
}
body.narrow .form-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: stretch;
    margin-top: 4px;
}
body.narrow .form-actions .btn {
    width: 100%;
    background: #133436;
    color: #FFFFFF;
    font-weight: 600;
    font-size: 14px;
    padding: 11px 0;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
    text-align: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    box-sizing: border-box;
}
body.narrow .form-actions .btn.secondary {
    background: transparent;
    color: #4C7A78;
    font-size: 13px;
    font-weight: 500;
    padding: 6px 0;
}
body.narrow .form-actions .btn.secondary:hover,
body.narrow .form-actions .btn.secondary:focus-visible {
    text-decoration: underline;
}
body.narrow .panel .note {
    color: #6B7B7B;
    font-size: 13px;
    margin: 0 0 14px;
    line-height: 1.5;
}
body.narrow .panel .hint {
    color: #6B7B7B;
    font-size: 12px;
    margin: 4px 0 0;
}

/* "Powered by Nonprofio" at the very bottom of the sign-in page. */
.signin-powered {
    margin: 40px 0 0;
    font-size: 12px;
    color: #6B7B7B;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    line-height: 1;
}
.signin-powered .mark { display: inline-flex; align-items: center; }
.signin-powered .np-dots { height: 9px; width: auto; display: block; }
.signin-powered a {
    color: #133436;
    text-decoration: none;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.signin-powered a:hover,
.signin-powered a:focus-visible { text-decoration: underline; }

/* MFA enrol page's three-step walkthrough also lives inside the
   signin shell. The .walkthrough list expands past the standard
   card width — unlock it. */
body.narrow .page:has(.walkthrough) { max-width: 480px; }
body.narrow .walkthrough .lede {
    color: #6B7B7B;
    font-size: 13px;
    margin: 0 0 16px;
}
body.narrow .walkthrough .steps { padding-left: 18px; }
body.narrow .walkthrough .steps > li { margin-bottom: 20px; }
body.narrow .walkthrough .steps h2 {
    font-size: 15px;
    font-weight: 600;
    color: #133436;
    margin: 0 0 6px;
}
body.narrow .walkthrough .steps p { color: #2B3A3C; font-size: 13px; margin: 0 0 8px; }
body.narrow .walkthrough .qrcode-box { padding: 10px 0; display: flex; justify-content: center; }
body.narrow .walkthrough .fallback summary { color: #4C7A78; font-size: 13px; cursor: pointer; }
body.narrow .walkthrough .big-secret {
    font-family: var(--font-mono);
    font-size: 14px;
    background: #F3F4F6;
    padding: 8px 12px;
    border-radius: 6px;
    display: inline-block;
    margin-top: 6px;
}
body.narrow .walkthrough .app-links { display: flex; gap: 8px; flex-wrap: wrap; }
body.narrow .walkthrough .app-links .btn.secondary {
    width: auto;
    padding: 8px 14px;
    background: #FFFFFF;
    border: 1px solid #D7DBD9;
    color: #133436;
    font-size: 13px;
    font-weight: 500;
}

@media (max-width: 420px) {
    body.narrow { padding: 48px 12px 24px; }
    body.narrow .page-header,
    body.narrow .page-body { padding-left: 20px; padding-right: 20px; }
}

