  /* "Pastel Dreams" — one blue family, five steps, replacing the old
     mixed-hue system entirely (2026-08-01, Wendy's reference palette). */
  :root {
    --bg: #ffffff;
    --surface: #ffffff;
    --ink: #20242b;
    --ink-soft: #5b6470;
    --line: rgba(27, 36, 48, 0.22);
    --sidebar-bg: #5465ff;
    --sidebar-bg-soft: #788bff;
    --sidebar-text: #ffffff;
    --sidebar-text-soft: rgba(255, 255, 255, 0.75);
    --accent: #5465ff;
    --gold: #ffb30f;
    --purple: #9381ff;
    --sage: #9bc1bc;
    --rose: #d9556e;
    --orange: #f2793c;
    --danger: #c0392b;
    --ink-strong: #000000;
  }
  /* Sidebar/accent/gold/purple/sage/danger stay constant across themes on purpose —
     only the content area (bg/surface/ink/line) flips, same technique as the map. */
  html[data-theme="dark"] {
    color-scheme: dark;
    --bg: #14181d;
    --surface: #1c222b;
    --ink: #e7eaee;
    --ink-soft: #9aa7b3;
    --line: rgba(255, 255, 255, 0.22);
    --ink-strong: #ffffff;
  }
  html[data-theme="light"] { color-scheme: light; }
  * { box-sizing: border-box; }
  body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI", system-ui, sans-serif;
    background: var(--bg);
    color: var(--ink);
  }
  input, select, textarea {
    font-family: inherit;
  }
  button {
    font-family: inherit;
    cursor: pointer;
    border-radius: 999px;
    border: 1px solid var(--line);
    background: var(--surface);
    padding: 0.6rem 1.2rem;
    font-size: 0.9rem;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, opacity 0.12s ease;
  }
  /* Every plain button gets a visible reaction on hover by default — a
     filled variant darkens instead, and a disabled one shows neither. */
  button:hover:not(:disabled) { background: var(--bg); border-color: var(--accent); }
  button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
  button.primary:hover:not(:disabled) { background: var(--sidebar-bg-soft); border-color: var(--sidebar-bg-soft); }
  /* Purple filled button (DNC "how many records" Continue). Dims to the shared
     disabled look until the entered count is a valid number (Wendy 2026-10-04). */
  button.dnc-continue-btn { background: var(--purple); border-color: var(--purple); color: #fff; font-weight: 600; }
  /* Keep the purple fill and white text on hover; the generic button:hover rule
     repaints the background to the page color, which would hide the white label
     (Wendy 2026-10-04). Same in dark mode, since --purple and the text are
     constant across themes. */
  button.dnc-continue-btn:hover:not(:disabled) { background: var(--purple); border-color: var(--purple); color: #fff; filter: brightness(1.08); }
  /* DNC Status button: a slightly deeper purple than the Continue button so it
     reads as its own thing (Wendy 2026-10-04). Same hover-fix as above. */
  button.dnc-status-btn { background: #6f5ce6; border-color: #6f5ce6; color: #fff; font-weight: 600; }
  button.dnc-status-btn:hover:not(:disabled) { background: #6f5ce6; border-color: #6f5ce6; color: #fff; filter: brightness(1.08); }
  button.danger { color: var(--danger); border-color: var(--danger); }
  button.danger:hover:not(:disabled) { background: var(--danger); color: #fff; }
  button:disabled { cursor: default; opacity: 0.5; }
  /* Hotel FTD "Save" + "Send Hotel Docs": keep the greyed/disabled signal, but
     as a SOLID, full-opacity grey button instead of the faded 50% look, so the
     two buttons match each other's height and visual weight. "Send Hotel Docs"
     greys out once a Date Sent is stamped (docs already sent); clearing the
     group lights it back up. Uses --ink-soft so it stays a clear muted grey in
     both light and dark themes. Scoped to these two buttons by id (Wendy 2026-10-05). */
  button.primary#vFtdHotelSaveBtn:disabled,
  button.primary#vFtdHotelSendBtn:disabled {
    opacity: 1;
    background: var(--ink-soft);
    border-color: var(--ink-soft);
    color: #fff;
  }
  /* Compact button (Wendy prefers small buttons) -- smaller padding + font. */
  button.btn-compact { padding: 0.28rem 0.7rem; font-size: 0.78rem; }

  .app-shell { display: flex; min-height: 100vh; gap: 0.8rem; padding: 0.8rem; }

  /* Vacation Kiosk mode: a locked-down, single-purpose tab opened from a
     Customer's Vacations card (data-vacation link, target=_blank) so a
     coworker can look up just that customer's vacation record(s) without
     wandering into the rest of the app. No sidebar, no way back to the
     full app — .content expanding to fill the freed space is automatic
     since .sidebar is its flex sibling in .app-shell. */
  body.vacation-kiosk-mode .sidebar,
  body.vacation-kiosk-mode #vacationBackBtn,
  body.vacation-kiosk-mode #vEditCustomerProfileBtn { display: none; }

  /* Appointment Kiosk mode: same locked-down single-purpose tab, opened from
     a Customer's Appointments card instead (Wendy, 2026-09-20). */
  body.appointment-kiosk-mode .sidebar,
  body.appointment-kiosk-mode #appointmentBackBtn,
  body.appointment-kiosk-mode #apptEditCustomerBtn { display: none; }

  /* Customer Kiosk mode: opened from another customer's Family card so staff
     can glance at a linked record without leaving the one they're working
     (Wendy, 2026-09-22). #customerV2TopBar carries both the Exit button and
     the Previous/Next group, so hiding it in one rule takes both out. Also
     hides the "Linked to" banner so this locked tab can't be used to hop to
     a third record. */
  body.customer-kiosk-mode .sidebar,
  body.customer-kiosk-mode #customerV2TopBar,
  body.customer-kiosk-mode #customerLinkedLine { display: none; }

  /* Customer Detail "Version 2" (Wendy, 2026-09-20): an additive Apple-
     internal-tool-style skin over the exact same #customerDetailSection
     markup and ids -- no duplicated fields, no new functionality, just a
     toggleable restyle so she can click back and forth against Version 1
     and confirm nothing was lost. Hides the sidebar same as the kiosk-mode
     pattern above (.content is .sidebar's flex sibling in .app-shell, so
     it expands to fill the freed space automatically). */
  body.customer-detail-v2-mode .sidebar { display: none; }
  /* Soft gray canvas so the white section cards read as cards (the mockup's
     Apple-settings look). Derived from the theme tokens so dark mode gets a
     correspondingly lifted-not-white canvas automatically. */
  body.customer-detail-v2-mode { background: color-mix(in srgb, var(--ink) 5%, var(--bg)); }

  /* Own header row for the Exit/Version-toggle/Prev-Next nav (Wendy,
     2026-09-20): pinned to the top of the scroll area with a background and
     hairline so it reads as a distinct toolbar, not just three buttons
     floating above the record content. */
  body.customer-detail-v2-mode #customerDetailSection #customerV2TopBar {
    position: sticky; top: 0; z-index: 5; background: var(--surface);
    margin: -2rem -2.5rem 1.5rem; padding: 1rem 2.5rem;
    border-bottom: 1px solid var(--line);
  }

  .cd-v2-rail { display: none; }
  body.customer-detail-v2-mode .cd-v2-body { display: flex; gap: 2.4rem; align-items: flex-start; }
  /* Vertical hairline separates the record-internal nav from the actual
     record content (Wendy, 2026-09-20), same divider language as the
     hairlines used between sections below. */
  body.customer-detail-v2-mode .cd-v2-rail {
    display: flex; flex-direction: column; gap: 1.5rem;
    width: 190px; flex-shrink: 0; position: sticky; top: 1.2rem;
    border-right: 1px solid var(--line); padding-right: 1.6rem;
  }
  .cd-v2-rail-label { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 0.4rem; }
  /* Mockup-style rail items (Wendy, 2026-09-20, from her circled screenshot):
     icon + label + right-aligned count. Every item is a jump-to link over
     the stacked sections -- nothing tracks a selected state since the
     Profile tab group was removed (2026-09-20, "we now have it stacked"). */
  .cd-v2-rail-item {
    display: flex; align-items: center; gap: 0.55rem; width: 100%; text-align: left;
    background: transparent; border: none; border-radius: 8px;
    padding: 0.45rem 0.6rem; margin: 0; font-size: 0.88rem; color: var(--ink);
  }
  .cd-v2-rail-item svg { flex: 0 0 auto; opacity: 0.75; }
  .cd-v2-rail-text { flex: 1 1 auto; min-width: 0; }
  .cd-v2-rail-item:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); color: var(--ink); }
  .cd-v2-rail-badge {
    font-size: 0.72rem; font-weight: 700; color: var(--ink-soft); background: var(--line);
    border-radius: 999px; padding: 0.05rem 0.45rem; margin-left: 0.5rem;
  }
  /* Same purple as the app's standard .unread-badge -- red here read as an
     error/alarm, not "you have unread texts" (Wendy, 2026-09-20). */
  .cd-v2-rail-badge-unread { background: var(--sidebar-bg); color: #fff; }

  /* Notification banner for v2 (Wendy, 2026-09-20): first pass stripped the
     pink card entirely to save space, but she wants the "cute pink sticky
     note" kept -- so this keeps the real pink/tape-corner look (same
     ::before strip, unchanged) and only drops the rotation + tall padding
     that made it eat vertical space, then centers the row instead of
     leaving it flush left under the title. */
  body.customer-detail-v2-mode #customerDetailSection #customerNotificationRow {
    display: flex; justify-content: center; flex-wrap: wrap; gap: 0.6rem;
  }
  body.customer-detail-v2-mode #customerDetailSection .customer-notification-sticky {
    display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
    min-width: 0; max-width: 480px; width: fit-content;
    padding: 0.5rem 2.1rem 0.5rem 0.8rem; margin: 0;
    box-shadow: 0 4px 10px rgba(20,20,10,0.15); transform: none; font-size: 0.82rem;
  }
  body.customer-detail-v2-mode #customerDetailSection .customer-notification-date-row { margin-bottom: 0; }
  body.customer-detail-v2-mode #customerDetailSection .customer-notification-edit-btn { top: 50%; transform: translateY(-50%); }

  /* Header extras, v2 only (Wendy, 2026-09-20, from the mockup header):
     initials avatar next to the name, Status/Sentiment as pills up by
     Options, and a 3-cell summary strip under the header. All hidden in
     Version 1; filled by updateCdV2HeaderBits/loadCustomerSmsHistory/
     loadCustomerRelated from data the page already fetches. */
  .cd-v2-avatar { display: none; }
  /* The avatar and the centered notification both anchor to .detail-header
     (NOT .cd-v2-header-block -- it must stay unpositioned or it would
     capture them and the sticky couldn't center across the full page). */
  body.customer-detail-v2-mode #customerDetailSection .detail-header { position: relative; }
  body.customer-detail-v2-mode #customerDetailSection .cd-v2-header-block { padding-left: 74px; }
  body.customer-detail-v2-mode .cd-v2-avatar {
    display: flex; align-items: center; justify-content: center;
    position: absolute; left: 0; top: 0.9rem; width: 56px; height: 56px; border-radius: 50%;
    background: linear-gradient(135deg, var(--sidebar-bg-soft), var(--accent)); color: #fff;
    font-weight: 700; font-size: 1.15rem; letter-spacing: 0.02em;
  }
  /* Name drops down to sit vertically centered against the avatar, the
     Customer ID moves to its own right-aligned line under Options (the
     left-column original is just hidden, Version 1 keeps it), and the pink
     sticky centers in the header (Wendy's annotated screenshot, 2026-09-20). */
  body.customer-detail-v2-mode #customerDetailSection #customerDetailTitle { margin-top: 0.9rem; }
  body.customer-detail-v2-mode #customerDetailSection .cd-v2-header-block .id-row { display: none; }
  body.customer-detail-v2-mode #customerDetailSection #customerNotificationRow {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); margin-top: 0;
  }
  .cd-v2-id-row { display: none; }
  body.customer-detail-v2-mode .cd-v2-id-row {
    display: flex; flex: 1 1 100%; justify-content: flex-end; align-items: center; gap: 0.3rem;
  }
  /* Pills/refresh/Options line hugs the right edge, same as the ID line
     under it (Wendy's "move to the right" arrow, 2026-09-20). */
  body.customer-detail-v2-mode #customerDetailSection .detail-header .opt-row { justify-content: flex-end; }
  body.customer-detail-v2-mode #cdV2IdDisplay {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-weight: 500;
  }
  .cd-v2-pills { display: none; }
  body.customer-detail-v2-mode .cd-v2-pills { display: inline-flex; gap: 0.5rem; align-items: center; }
  .cd-v2-pill {
    display: inline-flex; align-items: center; gap: 0.4rem; border-radius: 999px;
    padding: 0.3rem 0.85rem; font-size: 0.8rem; font-weight: 600;
    background: color-mix(in srgb, var(--sage) 28%, transparent); color: var(--ink);
  }
  .cd-v2-pill::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--sage); }
  .cd-v2-pill-gold { background: color-mix(in srgb, var(--gold) 20%, transparent); }
  .cd-v2-pill-gold::before { background: var(--gold); }
  .cd-v2-summary { display: none; }
  body.customer-detail-v2-mode .cd-v2-summary {
    display: grid; grid-template-columns: repeat(3, 1fr);
    background: var(--surface); border-radius: 14px; box-shadow: 0 1px 2px rgba(0,0,0,0.06);
    margin-bottom: 1.6rem; overflow: hidden;
  }
  .cd-v2-summary-cell { padding: 1rem 1.4rem; border-left: 1px solid var(--line); min-width: 0; }
  .cd-v2-summary-cell:first-child { border-left: none; }
  .cd-v2-summary-label { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 0.35rem; }
  .cd-v2-summary-value { font-weight: 700; font-size: 1.02rem; min-height: 1.3em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .cd-v2-summary-sub { color: var(--ink-soft); font-size: 0.82rem; margin-top: 0.15rem; }

  /* Profile fields as label-left / value-right settings rows (the mockup's
     Profile card). Pure styling over the existing .field wrappers: when the
     .row stops being flex, the inline flex-basis on each .field goes inert
     and every field becomes its own full-width hairlined grid row, label in
     the left column, the untouched input/combo-box in the right -- so edit
     mode, dirty tracking, DNC gears and copy buttons all keep working. */
  body.customer-detail-v2-mode #customerDetailSection .tab-panel .row { display: block; }
  body.customer-detail-v2-mode #customerDetailSection .tab-panel .field {
    display: grid; grid-template-columns: 190px minmax(0, 1fr); align-items: center; column-gap: 1.2rem;
    padding: 0.55rem 0; border-bottom: 1px solid color-mix(in srgb, var(--line) 45%, transparent);
  }
  body.customer-detail-v2-mode #customerDetailSection .tab-panel .field > label { margin: 0; }
  /* V1's "Status & Sentiment" sub-divider (label + trailing line) reads as
     stray clutter once fields stack full-width in V2 (Wendy, 2026-09-20). */
  body.customer-detail-v2-mode #customerDetailSection .tab-panel .section-divider { display: none; }
  /* Read mode shows only answered fields (Wendy, 2026-09-20); edit mode
     still shows every field so blanks can be filled in. v2-only -- V1 keeps
     empty fields visible on purpose (see setCustomerEditMode in core.js). */
  body.customer-detail-v2-mode #customerDetailSection .tab-panel.read-mode .field.cd-v2-field-empty { display: none; }
  /* Save Profile floats at the bottom of the view while editing, so the
     long stacked form never hides it (Wendy, 2026-09-20). */
  body.customer-detail-v2-mode #customerDetailSection #customerSaveRow {
    position: sticky; bottom: 0.8rem; z-index: 4;
    background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
    padding: 0.7rem 0.9rem; box-shadow: 0 6px 22px rgba(0,0,0,0.14);
  }

  /* Re-skin the same #customerDetailSection elements as white rounded cards
     on the gray canvas (Wendy, 2026-09-20, matching her mockup screenshots),
     applied directly to the real markup rather than a parallel
     implementation. */
  body.customer-detail-v2-mode #customerDetailSection .admin-tool {
    border: none; border-radius: 14px; background: var(--surface);
    box-shadow: 0 1px 2px rgba(0,0,0,0.06);
    padding: 1.2rem 1.4rem; margin-bottom: 1.1rem;
  }
  body.customer-detail-v2-mode #customerDetailSection details.admin-tool summary {
    font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft);
  }
  /* Continuous scroll (Wendy, 2026-09-20): all three profile panels render
     stacked with their own headings instead of tabbed. The real tab buttons
     are only hidden, not removed -- Version 1 still uses them, and the rail's
     Profile items still .click() them so V1 state stays coherent. The edit
     pencil lives in the same .tab-row, so only the button group is hidden. */
  body.customer-detail-v2-mode #customerDetailSection .tab-panel { display: block; }
  /* Hide the buttons themselves, NOT their wrapper div -- the wrapper
     carries an inline display:flex, which beats any stylesheet rule, so
     targeting it silently failed (caught by Wendy's screenshot 2026-09-20). */
  body.customer-detail-v2-mode #customerDetailSection .tab-row .tab-btn { display: none; }
  body.customer-detail-v2-mode #customerDetailSection .tab-row { justify-content: flex-end; border-bottom: none; margin-bottom: 0; }
  body.customer-detail-v2-mode #customerDetailSection .tab-panel::before {
    display: block; font-size: 1.12rem; font-weight: 700; color: var(--ink); margin-bottom: 0.5rem;
  }
  body.customer-detail-v2-mode #customerDetailSection #customerTabPanel::before { content: "Customer"; }
  body.customer-detail-v2-mode #customerDetailSection #demographicsTabPanel::before { content: "Demographics"; margin-top: 1.7rem; }
  body.customer-detail-v2-mode #customerDetailSection #ownerTabPanel::before { content: "Owner"; margin-top: 1.7rem; }
  body.customer-detail-v2-mode #customerDetailSection #grayCardTabPanel::before { content: "Gray Card"; margin-top: 1.7rem; }
  body.customer-detail-v2-mode #customerDetailSection #relatedToTabPanel.has-linked-family::before { content: "Family"; margin-top: 1.7rem; }
  /* Keep jump-to targets from hiding under the sticky top toolbar. */
  body.customer-detail-v2-mode #customerDetailSection .admin-tool,
  body.customer-detail-v2-mode #customerDetailSection .tab-panel { scroll-margin-top: 76px; }
  body.customer-detail-v2-mode #customerDetailSection #customerIdDisplay {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-weight: 500;
  }
  body.customer-detail-v2-mode #customerDetailSection input,
  body.customer-detail-v2-mode #customerDetailSection select,
  body.customer-detail-v2-mode #customerDetailSection textarea { border-radius: 8px; }

  /* Full-screen boot cover for both kiosk tabs above: overviewSection is the
     hardcoded default-active page in index.html, so without this it flashes
     on screen for a moment before the async auth/route-restore chain swaps
     in the actual kiosk view. The inline <script> at the top of <body> adds
     kiosk-loading the instant the URL hash matches, before anything else can
     paint; hideKioskBootOverlay() (core.js) removes it once the kiosk view
     has actually finished rendering. */
  #kioskBootOverlay { display: none; position: fixed; inset: 0; z-index: 9999; background: var(--bg); align-items: center; justify-content: center; }
  html.kiosk-loading #kioskBootOverlay { display: flex; }

  /* Full-page loading screen on every browser refresh (Wendy, 2026-10-02).
     The same gold star and glow as .blast-spinner-star, bigger and turning
     slowly (one turn every 4 seconds). hideBootLoader() (core.js) adds
     is-done to fade it out once the sign-in screen or the app is ready. */
  #scBootLoader {
    position: fixed; inset: 0; z-index: 10000; background: var(--bg);
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
    transition: opacity 0.4s ease;
  }
  #scBootLoader.is-done { opacity: 0; pointer-events: none; }
  #scBootLoader .sc-boot-star {
    font-size: 7rem; line-height: 1; color: var(--gold); display: inline-block;
    animation: sc-boot-turn 4s linear infinite, blast-glow 1.6s ease-in-out infinite;
  }
  #scBootLoader .sc-boot-text { font-size: 1rem; font-weight: 600; letter-spacing: 0.02em; color: var(--ink-soft); }
  @keyframes sc-boot-turn { to { transform: rotate(360deg); } }
  @media (prefers-reduced-motion: reduce) { #scBootLoader .sc-boot-star { animation: blast-glow 1.6s ease-in-out infinite; } }

  /* Same gold spinner, used to cover a record-to-record navigation (e.g.
     Appointment Detail -> Edit Customer Profile) while it loads, so staff
     see one clean spinner instead of fields/tabs visibly assembling
     (Wendy, 2026-09-21: "I don't want to watch the system build its path"). */
  #navLoadingOverlay { position: fixed; inset: 0; z-index: 9999; background: var(--bg); align-items: center; justify-content: center; }

  .sidebar {
    width: 220px;
    flex-shrink: 0;
    background: var(--sidebar-bg);
    color: var(--sidebar-text);
    display: flex;
    flex-direction: column;
    transition: width 0.18s ease;
    border-radius: 22px;
    overflow: hidden;
    /* .app-shell's min-height:100vh is a floor, not a ceiling -- a long list
       (.content, .sidebar's flex sibling) grows the shell taller than the
       viewport, and align-items:stretch was growing the sidebar right along
       with it, pushing Settings/Admin/Sign Out below the fold (Wendy,
       2026-09-03). Sticky pins it to the viewport regardless of page length;
       the page still scrolls normally, only the sidebar stays put. */
    position: sticky;
    top: 0.8rem;
    height: calc(100vh - 1.6rem);
  }
  .sidebar.collapsed { width: 68px; }
  .sidebar-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.1rem 1rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  }
  .sidebar-word { display: flex; align-items: center; gap: 0.5rem; font-weight: 800; font-size: 1rem; white-space: nowrap; overflow: hidden; }
  .sidebar.collapsed .sidebar-word span { display: none; }
  .sidebar-toggle { background: transparent; border: none; border-radius: 999px; color: var(--sidebar-text-soft); padding: 0.3rem; line-height: 0; flex-shrink: 0; }
  /* Scoped to .sidebar so this outweighs the generic button:hover:not(:disabled)
     rule — that one was winning on specificity and painting a light circle
     behind the icon, washing it out to near-invisible (same bug class as the
     earlier nav-item hover fix, different button). */
  .sidebar .sidebar-toggle:hover { color: var(--sidebar-text); background: transparent; border-color: transparent; }
  .text-size-btn {
    background: transparent; border: 1px solid rgba(255,255,255,0.28); border-radius: 6px;
    color: var(--sidebar-text-soft); flex-shrink: 0; font-family: inherit; font-weight: 700;
    font-size: 11px; line-height: 1; padding: 0.28rem 0.4rem;
  }
  .sidebar .text-size-btn:hover { color: var(--sidebar-text); border-color: var(--sidebar-text); background: transparent; }
  .sidebar.collapsed #themeToggle, .sidebar.collapsed #textSizeDown, .sidebar.collapsed #textSizeUp { display: none; }
  /* min-height:0 lets this flex:1 child actually shrink and scroll instead
     of forcing .sidebar taller than the viewport — without it, a short
     window pushes Settings/Admin/theme-toggle/Sign Out below the fold with
     no way to reach them, since .sidebar itself clips via overflow:hidden
     (kept for the rounded corners). Nav list scrolls; top/bottom stay put. */
  .sidebar-nav { flex: 1; min-height: 0; overflow-y: auto; padding: 0.8rem 0.6rem; display: flex; flex-direction: column; gap: 0.2rem; scrollbar-width: thin; }
  .sidebar-nav::-webkit-scrollbar { width: 6px; }
  .sidebar-nav::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.25); border-radius: 999px; }
  .sidebar-nav::-webkit-scrollbar-track { background: transparent; }
  .nav-item {
    display: flex; align-items: center; gap: 0.75rem;
    background: transparent; border: none; color: var(--sidebar-text-soft);
    padding: 0.65rem 0.7rem; border-radius: 999px; font-size: 0.9rem;
    text-align: left; white-space: nowrap; overflow: hidden;
  }
  .nav-item svg { flex-shrink: 0; width: 18px; height: 18px; }
  .sidebar .nav-item:hover { background: var(--sidebar-bg-soft); border-color: transparent; color: var(--sidebar-text); }
  .nav-item.active { background: #ffffff; color: #20242b; }
  .nav-item.active svg { color: var(--gold); }
  .sidebar.collapsed .nav-item span:not(.nav-unread-badge) { display: none; }
  .sidebar-settings { padding: 0.6rem; border-top: 1px solid rgba(255, 255, 255, 0.08); }
  .sidebar-bottom { padding: 1rem 1rem 46px; border-top: 1px solid rgba(255, 255, 255, 0.08); font-size: 0.82rem; }
  .sidebar-bottom label { display: block; color: var(--sidebar-text-soft); font-size: 0.75rem; letter-spacing: 0.04em; margin-bottom: 0.4rem; }
  .sidebar-bottom input {
    width: 100%; font-family: inherit; font-size: 0.85rem; padding: 0.3rem 0;
    border: none; border-bottom: 1px solid rgba(255,255,255,0.18); background: transparent; color: #fff;
  }
  /* Once real login resolves, this field goes read-only and doubles as the
     "My Info" trigger -- styled as a pill so it visually reads as something
     clickable, not just an inert label (Wendy, 2026-08-20). Only applies
     once .readonly is set (initRealLogin), so it doesn't look falsely
     clickable during the brief pre-login free-text-entry state. */
  .sidebar-bottom input[readonly] {
    border: none; border-radius: 999px; background: rgba(255,255,255,0.12);
    padding: 0.35rem 0.8rem; cursor: pointer; text-align: center; font-weight: 600;
  }
  .sidebar-bottom input[readonly]:hover { background: rgba(255,255,255,0.2); }
  .sidebar-bottom .hint { margin-top: 0.5rem; color: var(--sidebar-text-soft); line-height: 1.4; }
  /* Sized/styled to match .nav-item (e.g. the Settings button) per Wendy's
     request — was a tiny underlined text link before, easy to miss and
     fiddly to click. Now a full-size button with an icon, same as any
     other sidebar action. */
  .sidebar-signout-btn { width: 100%; margin-top: 0.7rem; justify-content: flex-start; }
  /* Collapsed sidebar has no room for "Signed in as" / email / role — the
     email in particular always got cut off (Wendy, 2026-08-20). Show just
     the username instead, compact and centered like the collapsed nav icons. */
  .sidebar-collapsed-username { display: none; }
  .sidebar.collapsed .sidebar-bottom label,
  .sidebar.collapsed .sidebar-bottom input,
  .sidebar.collapsed .sidebar-bottom .hint,
  .sidebar.collapsed .sidebar-signout-btn { display: none; }
  .sidebar.collapsed .sidebar-collapsed-username {
    display: block; text-align: center; font-weight: 700; font-size: 0.78rem;
    color: var(--sidebar-text); background: var(--sidebar-bg-soft);
    border-radius: 999px; padding: 0.4rem 0.3rem; margin: 0; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap;
  }

  .content { flex: 1; padding: 2rem 2.5rem 3.6rem; max-width: 1600px; }

  /* Login screen (Supabase Auth), sitting behind Cloudflare Access. Access
     is the front-door lock; this is what actually scopes database access per
     person (see RLS policies). No password box since 2026-09-30: it only
     shows when the Access email-code handoff fails, with a Try Again button. */
  #authScreen {
    min-height: 100vh; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 1.6rem; padding: 2rem;
    background: linear-gradient(135deg, var(--sidebar-bg) 0%, var(--sidebar-bg-soft) 100%);
  }
  #loginView {
    max-width: 360px; width: 100%; padding: 2rem; border: 1px solid var(--line);
    border-radius: 22px; background: var(--surface); box-shadow: 0 20px 55px rgba(20,25,30,0.3);
  }
  #loginView h2, #loginView p:not(.muted) { color: var(--ink); }
  #loginError { color: var(--danger) !important; font-size: 0.85rem; margin-top: 0.6rem; min-height: 1.2em; }

  .activity-ticker {
    position: fixed; left: 0; right: 0; bottom: 0; height: 34px; z-index: 40;
    background: #1b1e30; overflow: hidden;
    display: flex; align-items: center;
  }
  .ticker-track {
    display: inline-flex; white-space: nowrap; animation: ticker-scroll 140s linear infinite;
  }
  .ticker-track span {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; letter-spacing: 0.06em;
    color: #ffffff; text-shadow: 0 0 6px rgba(120,139,255,0.75), 0 0 12px rgba(120,139,255,0.4);
    padding-right: 4rem;
  }
  @keyframes ticker-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
  @media (prefers-reduced-motion: reduce) { .ticker-track { animation: none; } }
  /* Off hides the entire bar now, not just the scrolling text (Wendy,
     2026-08-20) -- its own toggle lives in the sidebar instead of on the
     bar itself, specifically so it's still reachable once this is hidden. */
  .activity-ticker.mode-off { display: none; }

  /* Ask Nova: floating help chat. Sits above the activity ticker bar
     (34px tall, fixed to the very bottom) rather than overlapping it. */
  #novaChatLauncherBtn {
    position: fixed; bottom: 3.4rem; right: 1.5rem; z-index: 250;
    width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer;
    background: linear-gradient(135deg, #7c3aed, #f5b301); color: #fff;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 8px 24px rgba(124, 58, 237, 0.45);
  }
  #novaChatLauncherBtn:hover { transform: scale(1.06); }
  #novaChatPanel {
    display: none; flex-direction: column; position: fixed; bottom: 7.2rem; right: 1.5rem; z-index: 250;
    width: 360px; max-width: calc(100vw - 2rem); height: 500px; max-height: calc(100vh - 10rem);
    background: var(--surface); border-radius: 18px; box-shadow: 0 16px 44px rgba(20,35,42,0.3);
    overflow: hidden; border: 1px solid var(--line);
  }
  #novaChatPanel.show { display: flex; }
  #novaChatHeader {
    background: linear-gradient(135deg, #7c3aed, #a855f7); color: #fff; cursor: move;
    padding: 0.9rem 1.1rem; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0;
  }
  /* The generic .icon-btn:hover rule swaps in a light background, which
     against this button's own white text makes it disappear on hover --
     same recurring specificity bug as the nav-item/settings-tab hover
     fixes, different button (Wendy, 2026-08-20). */
  #novaChatHeader .icon-btn:hover { background: rgba(255,255,255,0.22); color: #fff; }
  #novaChatMessages { flex: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.6rem; }
  .nova-chat-bubble {
    max-width: 82%; padding: 0.55rem 0.8rem; border-radius: 14px; font-size: 0.88rem;
    line-height: 1.4; white-space: pre-wrap; word-wrap: break-word;
  }
  .nova-chat-user { align-self: flex-end; background: #7c3aed; color: #fff; border-bottom-right-radius: 4px; }
  .nova-chat-assistant { align-self: flex-start; background: var(--bg); color: var(--ink); border-bottom-left-radius: 4px; }
  .nova-chat-typing { opacity: 0.6; font-style: italic; }
  /* Starter-question pills under the greeting (Wendy, 2026-09-19) -- click
     sends that question immediately, same as typing it and hitting enter. */
  .nova-chat-pill-row { display: flex; flex-wrap: wrap; gap: 0.4rem; align-self: flex-start; max-width: 92%; }
  .nova-chat-pill {
    background: var(--bg); color: var(--accent); border: 1px solid var(--accent);
    border-radius: 999px; padding: 0.35rem 0.75rem; font-size: 0.8rem; cursor: pointer;
  }
  /* Scoped to #novaChatMessages to out-specificity the global
     button:hover:not(:disabled) rule above, which otherwise wins on
     `background` and leaves white hover text on a white (light-mode)
     background. */
  #novaChatMessages .nova-chat-pill:hover { background: var(--accent); color: #fff; }
  #novaChatInputRow { display: flex; gap: 0.5rem; padding: 0.8rem; border-top: 1px solid var(--line); flex-shrink: 0; }
  #novaChatInputRow input { flex: 1; }
  #novaChatSendBtn {
    width: 40px; height: 40px; border-radius: 50%; border: none; background: #7c3aed; color: #fff;
    display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0;
  }
  #novaChatSendBtn:hover { background: #6b2fd6; }
  .page { display: none; }
  .page.active { display: block; }
  .page h2 { font-size: 1.3rem; font-weight: 800; margin: 0 0 1.4rem; }

  .page-header-bar {
    display: flex; align-items: center; gap: 0.6rem; background: var(--sidebar-bg); color: #fff;
    font-size: 1.05rem; font-weight: 700; padding: 0.9rem 1.3rem; border-radius: 14px; margin-bottom: 1.3rem;
  }
  .page-header-bar svg { color: var(--gold); flex-shrink: 0; }
  #overviewRefreshBtn, #settingsRefreshBtn { color: #fff; }
  #overviewRefreshBtn:hover, #settingsRefreshBtn:hover { opacity: 0.8; }
  .spin-anim { animation: ov-spin 0.7s linear infinite; transform-origin: 50% 50%; }
  @keyframes ov-spin { to { transform: rotate(360deg); } }

  .status-pill {
    display: inline-block; font-size: 0.72rem; font-weight: 700; padding: 0.2rem 0.65rem; border-radius: 999px;
    white-space: nowrap;
  }
  /* The pill's own left padding stacks on top of the cell's padding, pushing
     Status text further right than every other column's plain text and out
     of line with the "Status" header above it (Wendy, 2026-09-20). Pull the
     cell padding back by the pill's own inset so the two line up. */
  #vendorsSection table td:nth-child(2) { padding-left: 0.35rem; }
  .status-pill.active { background: #e4f5ec; color: #1a7a4c; }
  .status-pill.inactive { background: var(--line); color: var(--ink-soft); }
  .status-pill.neutral { background: var(--line); color: var(--ink); }
  .status-pill.refundable { background: #e2edfb; color: #1f5aa8; }
  .status-pill.family-relationship { background: #e2edfb; color: #1f5aa8; }
  /* Tour Status pill -- colors set inline per-status via tourStatusColor()
     (core.js), this just supplies the shape (Wendy, 2026-08-20). */
  .tour-status-pill { display: inline-block; font-size: 0.78rem; font-weight: 600; padding: 0.15rem 0.6rem; border-radius: 999px; }
  .status-pill.disabled-access { background: #fbe4e1; color: #b0392b; }
  /* Same red family as the active notification bell (--danger, #c0392b) so
     the pill and the bell read as the same signal (Wendy, 2026-09-21). */
  .status-pill.notification { background: #fbe4e1; color: #b0392b; }
  /* Same pale-pink/deep-magenta pairing as the Overview pink rail (Wendy:
     pure pastel-on-pastel "gets drowned out," so the text uses the deeper
     #db4d75 instead of the literal pastel hex). Requested 2026-09-09 for
     Hotel Bookings' "Need To Cancel" status specifically. */
  .status-pill.needs-cancel { background: #ffd9e3; color: #db4d75; }
  /* Vacation Status — 5-hue set, contrast/CVD-checked (dataviz skill validator):
     all pairs pass, worst adjacent (violet<->blue) sits in the 8-12 CVD floor band,
     which is legal because every pill always carries its text label (secondary
     encoding), never color alone. */
  .status-pill.vac-open-dated { background: #e2edfb; color: #1f5aa8; }
  .status-pill.vac-dated { background: #fdf0d9; color: #8a5c04; }
  .status-pill.vac-confirmed { background: #e4f5ec; color: #1a7a4c; }
  .status-pill.vac-traveled { background: #ece7fb; color: #4a3aa7; }
  .status-pill.vac-canceled { background: #fbe4e1; color: #b0392b; }

  /* Overview stat cards, quieted to an Apple-widget register (Wendy,
     2026-09-06): the NUMBER leads, big and light, with a whisper of a label
     beneath; hairline border instead of a floating shadow; zero counts fade
     to gray so the eye goes only where there's work. */
  .stat-card { background: var(--surface); border: 1px solid var(--line); border-radius: 16px; padding: 1.15rem 1.35rem 1.05rem 1.55rem; box-shadow: none; display: flex; flex-direction: column; position: relative; overflow: hidden; }
  /* Title top-left, number below (Wendy, 2026-09-07: Option C, icons dropped) */
  .stat-card .stat-label { font-size: 0.78rem; letter-spacing: 0.01em; color: var(--ink-soft); font-weight: 400; order: 1; margin: 0; }
  .stat-card .stat-num { font-size: 2.3rem; font-weight: 600; line-height: 1.1; letter-spacing: -0.02em; color: var(--ink-strong); order: 2; margin-top: 0.3rem; font-variant-numeric: tabular-nums; }
  .stat-card .muted { order: 3; }
  /* Colored left rail — codes which part of the app a card belongs to, same
     idea as the row/tab-badge dot colors elsewhere in the app. */
  .stat-rail { position: absolute; left: 0; top: 0; bottom: 0; width: 4px; }
  .stat-rail.rail-accent { background: var(--accent); }
  .stat-rail.rail-purple { background: var(--purple); }
  .stat-rail.rail-gold { background: var(--gold); }
  .stat-rail.rail-sage { background: var(--sage); }
  .stat-rail.rail-pink { background: #ffd9e3; }
  .stat-rail.rail-rose { background: var(--rose); }
  .stat-rail.rail-orange { background: var(--orange); }
  /* Glowing dot standing in for an icon on cards that mean "something new
     landed" — reuses the same green="new RRF" language as the vacation list
     dot, plus a new orange for "new message," both with a soft pulse so they
     read as alive/live data, not just decoration. */
  .stat-label-glow { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; animation: glow-pulse 2s ease-in-out infinite; }
  .stat-label-glow.glow-green { background: #1a9e5c; color: #1a9e5c; }
  .stat-label-glow.glow-orange { background: #f2793c; color: #f2793c; }
  .stat-label-glow.glow-purple { background: var(--purple); color: var(--purple); }
  /* Read/acknowledged state of a toggleable dot -- static, no pulse, since
     nothing needs attention anymore (Wendy, 2026-09-17). */
  .stat-label-glow.glow-gray { background: var(--ink-soft); color: var(--ink-soft); animation: none; }
  @keyframes glow-pulse {
    0% { box-shadow: 0 0 0 0 currentColor; }
    70% { box-shadow: 0 0 0 6px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
  }
  @media (prefers-reduced-motion: reduce) { .stat-label-glow { animation: none; } }
  /* Pointer-based drag now (Wendy, 2026-09-07), not the draggable="true"
     attribute -- cursor styling switched to a plain class so it doesn't
     depend on that attribute being set. user-select:none is required here:
     native draggable used to suppress text selection automatically during a
     drag gesture; plain mouse tracking doesn't, so without this a
     click-and-drag just highlighted the card's text instead of moving it
     (Wendy, 2026-09-07). */
  .stat-card { cursor: grab; user-select: none; -webkit-user-select: none; }
  .stat-card.stat-card-dragging { opacity: 0.45; cursor: grabbing; }
  .stat-card.stat-card-drop-target { border-color: var(--accent); }
  /* 6x4 = 24 open "lanes" (Wendy, 2026-09-07): invisible filler cells so the
     grid always has room to drag a card into, past however many real cards
     exist today. Only shown while actually dragging, so the empty layout
     doesn't clutter the page the rest of the time. */
  .ov-ghost-cell {
    border: 1.5px dashed transparent; border-radius: 16px; min-height: 108px;
    opacity: 0; pointer-events: none; transition: opacity 0.15s ease, border-color 0.15s ease;
  }
  #overviewCards.ov-dragging .ov-ghost-cell {
    opacity: 1; border-color: var(--line); background: color-mix(in srgb, var(--accent) 4%, transparent);
    pointer-events: auto;
  }
  /* Preview highlight for whichever cell would actually receive the card
     right now -- separate from the live move itself (Wendy, 2026-09-07: a
     different card kept landing than the one she visually dropped on,
     because the old version moved the real card on every hover instead of
     only on release). */
  .ov-ghost-cell.stat-card-drop-target { border-color: var(--accent); border-style: solid; background: color-mix(in srgb, var(--accent) 10%, transparent); }
  .stat-card-zero .stat-num { color: var(--line); font-weight: 500; }

  .sentiment-face-lg svg { width: 26px; height: 26px; }

  table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 0.9rem; background: var(--surface); border-radius: 18px; overflow: hidden; box-shadow: 0 4px 20px rgba(27, 36, 48, 0.06); }
  /* Hotel Bookings has more columns than most lists (Wendy, 2026-09-09:
     "maybe we need a smaller font for this workflow") -- smaller base size
     so the wide table needs less horizontal scroll. */
  #hotelBookingsSection table { font-size: 0.8rem; }
  /* RRF/Quotes/Payment/Hotel/Entertainment tab tables: fixed to always show
     the header + ~3 rows, not a collapsible/expanding card -- anything past
     3 rows scrolls inside this box instead of growing the card. */
  .tab-table-scroll { max-height: 205px; overflow-y: auto; }
  /* More visible scrollbar on the Pick-from-Pricelist browse list so staff
     notice there are more rows to scroll to (Wendy, 2026-10-05). */
  .ent-item-dialog-browse-scroll { scrollbar-width: thin; scrollbar-color: var(--accent) transparent; }
  .ent-item-dialog-browse-scroll::-webkit-scrollbar { width: 10px; }
  .ent-item-dialog-browse-scroll::-webkit-scrollbar-thumb { background: var(--accent); border-radius: 999px; border: 2px solid var(--surface); }
  .ent-item-dialog-browse-scroll::-webkit-scrollbar-track { background: var(--line); border-radius: 999px; }
  /* The 5 tabs have different amounts of content above their table (Payment
     and Entertainment have an extra button/form RRF and Quotes don't), so
     the card was visibly growing/shrinking as you switched tabs even with
     each table capped -- a fixed min-height on every panel keeps the whole
     card the same size regardless of which tab is active. */
  #vacationSubTabsCard .tab-panel { min-height: 280px; }
  th, td { text-align: left; padding: 0.8rem 1rem; border-bottom: 1px solid var(--line); vertical-align: top; }
  tr:last-child td { border-bottom: none; }
  th { color: var(--ink-soft); font-weight: 500; font-size: 0.82rem; letter-spacing: normal; background: var(--bg); }
  th.sortable { cursor: pointer; user-select: none; text-decoration: none; }
  th.sortable:hover { color: var(--ink); }
  /* Regular-weight headers with a persistent sort-direction indicator (Wendy,
     2026-09-07, reference screenshot: a bank statement's transaction table)
     -- every sortable column shows a faint neutral up/down glyph at rest;
     whichever one is actually sorted swaps it for a single-direction arrow
     (added by wireSortableHeaders in core.js) plus an underline. Piloted on
     Vacations only first, then rolled out app-wide once she confirmed she
     liked it. */
  th.sortable:not(.sort-active)::after { content: "\21D5"; margin-left: 0.35rem; opacity: 0.45; font-weight: 400; }
  th.sortable.sort-active { text-decoration: underline; text-decoration-color: currentColor; text-underline-offset: 3px; }
  /* Vacations tables are about to grow a lot of columns (Wendy, 2026-08-17:
     "I expect this list to get wider so we will need every inch") — long
     header titles stack onto 2 lines instead of forcing the column wide
     enough to fit on one, scoped here rather than the global th rule so
     other pages' tables aren't affected. */
  /* Tightened from 0.6rem/0.7rem (Wendy, 2026-08-18: "too spread out") --
     more columns keep getting added to these tables and every inch counts. */
  #vacationsSection table th { white-space: normal; max-width: 100px; line-height: 1.25; padding: 0.45rem 0.45rem; }
  #vacationsSection table td { padding: 0.45rem 0.45rem; }
  /* Fatter rows on Personnel specifically (Wendy, 2026-08-20) -- easier to
     read/click a specific row, worth the extra vertical space on a list
     that isn't hundreds of rows long. */
  #personnelPanel table td { padding: 1.3rem 1rem; }
  /* Tighter rows on Wave Allotment specifically (Wendy, 2026-09-14) -- this
     list runs to hundreds of waves at once, so a little less vertical
     padding per row goes a long way. */
  #waveAllotmentSection table th, #waveAllotmentSection table td { padding: 0.45rem 0.6rem; }
  /* Personnel edit form (Wendy, 2026-08-21): a skinny Profile column with the
     Modules box beside it, then Marketing Center / Showrooms / Sales Position
     as three fixed-height scrolling cards across the bottom. Slightly tighter
     input padding; the layout (not font size) is what compacts it. Scoped to
     #personnelForm so no other form is affected. */
  #personnelForm input, #personnelForm select { padding: 0.35rem 0.6rem; }
  #personnelForm .field label { margin-bottom: 0.15rem; }
  /* Shorter list boxes here so three across don't dominate the page; they
     still scroll internally and stay user-resizable. */
  #personnelForm .checkbox-list { height: 190px; }
  /* Modules is a fixed-size box (Wendy, 2026-08-21) -- taller so all modules
     show, and not user-resizable so it stays consistent. */
  #personnelForm .p-modules-list { height: 300px; resize: none; }
  /* Admin-protected: a non-Admin viewing an Admin record can look but not touch
     the fields (Wendy, 2026-08-21). Nav/Cancel stay clickable so they can leave;
     Save is disabled in JS and the DB enforces it for real (migration-105). */
  #personnelForm.form-locked .field,
  #personnelForm.form-locked .checkbox-list,
  #personnelForm.form-locked #pAccessActionsWrap { pointer-events: none; opacity: 0.6; }
  th.sortable .sort-arrow { margin-left: 0.3rem; opacity: 0.5; }
  th.sortable.sort-active .sort-arrow { opacity: 1; }
  .recordLink { color: var(--accent); font-weight: 600; text-decoration: none; }
  .recordLink:hover { text-decoration: underline; }
  /* Customer Contacts list: Customer ID always underlined so it reads as a
     link (Wendy, 2026-08-23, CLAUDE.md rule 9a). Scoped to this list only —
     covers both Main View and Contact View's tables (2026-09-02). */
  #customersBody .recordLink, #customersContactBody .recordLink, #customersAdminBody .recordLink { text-decoration: underline; }
  /* Most Recent Event text on the Customers list: smaller and wrapped so a
     long "code • status • date" label doesn't stretch the column (Wendy,
     2026-09-30). The div carries the width because table cells ignore max-width. */
  .mre-label-cell { font-size: 0.82rem; line-height: 1.35; max-width: 200px; white-space: normal; overflow-wrap: anywhere; }
  .mre-date-cell { font-size: 0.82rem; white-space: nowrap; }
  /* Compact lists: about half the default cell padding, so more rows fit on
     screen (Wendy, 2026-09-30, Customers first, then rolled out to the other
     lists). Vacations, Wave Allotment and Personnel keep their own rules. */
  #customersMainViewWrap th, #customersMainViewWrap td,
  #customersContactViewWrap th, #customersContactViewWrap td,
  #customersNotificationViewWrap th, #customersNotificationViewWrap td,
  #appointmentsSection th, #appointmentsSection td,
  #leadsSection th, #leadsSection td,
  #vendorsSection th, #vendorsSection td,
  #hotelBookingsSection th, #hotelBookingsSection td,
  #paymentTransactionsSection th, #paymentTransactionsSection td,
  #emailHistorySection th, #emailHistorySection td { padding: 0.45rem 0.6rem; }
  /* Same label on the customer page summary card: wrap instead of "…" so the
     whole text shows, matching the list. */
  #cdV2SumEvent { white-space: normal; overflow-wrap: anywhere; }
  /* Same look as .recordLink, but its own class — the Customer Contacts
     list re-wires every .recordLink on the page on each render, so sharing
     that class would double-bind clicks on these admin-table ID links too. */
  .admin-id-link { color: var(--accent); font-weight: 600; text-decoration: none; vertical-align: middle; }
  .admin-id-link:hover { text-decoration: underline; }
  .dnc-tag {
    display: inline-block; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 9px; font-weight: 700;
    letter-spacing: 0.04em; color: #fff; background: var(--danger); padding: 1px 5px; border-radius: 4px; margin-left: 5px; vertical-align: middle;
  }

  /* Vacations Calendar View (Wendy, 2026-09-18) — arrivals-by-day/city month
     grid, modeled on an EZ Texting-style calendar reference. */
  .vac-calendar-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
  .vac-calendar-nav { display: flex; align-items: center; gap: 0.6rem; }
  .vac-calendar-nav b { min-width: 150px; text-align: center; }
  .vac-calendar-grid {
    display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px;
    background: var(--line); border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
  }
  .vac-cal-dow {
    background: var(--surface); text-align: center; font-weight: 600; font-size: 0.8rem;
    color: var(--ink-soft); padding: 0.5rem 0;
  }
  .vac-cal-cell {
    background: var(--surface); min-height: 108px; padding: 0.4rem 0.4rem 0.5rem;
    display: flex; flex-direction: column; gap: 0.25rem;
  }
  .vac-cal-cell-outside { background: rgba(120,128,140,0.10); }
  .vac-cal-cell-outside .vac-cal-daynum { color: var(--ink-soft); }
  .vac-cal-cell-outside .vac-cal-pills { opacity: 0.6; }
  .vac-cal-daynum { font-size: 0.8rem; font-weight: 600; color: var(--ink); }
  .vac-cal-pills { display: flex; flex-direction: column; gap: 3px; overflow: hidden; }
  .vac-cal-pills:has(> .vac-cal-count) { flex: 1 1 auto; align-items: center; justify-content: center; }
  .vac-cal-pill, .vac-cal-more, .vac-cal-count {
    border: none; border-radius: 999px; font-size: 0.72rem; font-weight: 600; cursor: pointer;
    text-align: left; padding: 2px 7px; line-height: 1.5;
  }
  .vac-cal-pill { background: #fdf0d9; color: #8a5c04; display: flex; align-items: center; gap: 5px; }
  .vac-cal-pill-badge {
    display: inline-flex; align-items: center; justify-content: center; min-width: 15px; height: 15px;
    border-radius: 999px; background: #8a5c04; color: #fff; font-size: 0.65rem; font-weight: 700; padding: 0 3px;
  }
  .vac-cal-more { background: none; color: var(--ink-soft); padding: 2px 2px; }
  .vac-cal-more:hover { text-decoration: underline; }
  .vac-cal-count {
    background: #fdf0d9; color: #8a5c04; font-size: 1.6rem; font-weight: 700; padding: 6px 16px;
  }

  #customersActionsRow { display: flex; justify-content: space-between; gap: 0.8rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
  .filters-row { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: center; }

  /* Faceted sidebar filters — "shopping site" style drill-down, each group
     independent and OR'd within itself, AND'd against the other groups. */
  .customers-layout { display: flex; gap: 1.4rem; align-items: flex-start; }
  .customers-main { flex: 1; min-width: 0; }
  .customer-filters {
    width: 210px; flex-shrink: 0; background: var(--surface); border: 1px solid var(--line);
    border-radius: 16px; padding: 1rem 1.1rem; position: sticky; top: 1rem;
    box-shadow: 0 4px 20px rgba(27,36,48,0.06);
  }
  /* Collapsed state: the filter panel hides entirely and the list takes the
     full width (Wendy, 2026-08-21: more real estate for the list). Toggled by
     the "Filters" button; the active-filter chips above the table still show
     what's applied while it's closed. */
  .customers-layout.filters-collapsed .customer-filters { display: none; }
  .filter-toggle-btn {
    display: inline-flex; align-items: center; gap: 0.4rem; cursor: pointer;
    background: var(--surface); border: 1px solid var(--line); border-radius: 999px;
    padding: 0.45rem 0.9rem; font-size: 0.85rem; font-weight: 600; color: var(--ink);
  }
  /* button.-scoped so these beat the generic button:hover rule, which changes
     the background out from under the text and washed the label out on hover
     (Wendy, 2026-08-21). Explicit color on every state keeps it legible. */
  button.filter-toggle-btn:hover { background: var(--surface); border-color: var(--accent); color: var(--ink); }
  button.filter-toggle-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
  button.filter-toggle-btn.active:hover { background: var(--sidebar-bg-soft); border-color: var(--sidebar-bg-soft); color: #fff; }
  .filter-toggle-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
    background: var(--gold); color: #20242b; font-size: 0.7rem; font-weight: 800; line-height: 1;
  }
  .filters-panel-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.4rem; }
  .filters-panel-head h3 { margin: 0; font-size: 0.95rem; }

  /* Settings gets the same sidebar-plus-content shape as Customer Contacts —
     one sub-nav item today (SMS Templates), built so adding the next admin
     table later is just another button, not a layout change. */
  .settings-layout { display: block; }
  .settings-main { width: 100%; }
  /* One row of tabs under the header bar, instead of a left sidebar — the
     highlighted tab itself is the "where am I" cue. */
  .settings-subnav {
    display: flex; flex-wrap: wrap; gap: 0.4rem;
    margin-bottom: 1.3rem; padding-bottom: 0.8rem; border-bottom: 1px solid var(--line);
  }
  .settings-subnav-btn {
    text-align: center; background: transparent; border: none; border-radius: 10px;
    padding: 0.6rem 1rem; font-weight: 600; font-size: 0.88rem; color: var(--ink-soft);
  }
  /* Scoped to .settings-subnav so this outweighs the generic
     button:hover:not(:disabled) rule — same specificity bug as the earlier
     hamburger/nav-item fixes, just resurfacing on a third button. */
  .settings-subnav .settings-subnav-btn:hover { background: rgba(0,0,0,0.05); color: var(--ink); border-color: transparent; }
  .settings-subnav-btn.active { background: var(--ink-soft); color: #fff; }

  /* Same visual language as .settings-subnav (row of tabs, filled pill for
     the active one) but a separate class so its click handler never
     collides with Settings' own tab-switching logic. Used for a table's
     different saved "Views" (e.g. Vacations: Main/FTD/Confirmation/...). */
  .view-tabs {
    display: flex; flex-wrap: wrap; gap: 0.4rem;
    margin-bottom: 1rem; padding-bottom: 0.7rem; border-bottom: 1px solid var(--line);
  }
  .view-tab-btn {
    text-align: center; background: transparent; border: none; border-radius: 10px;
    padding: 0.55rem 1rem; font-weight: 600; font-size: 0.88rem; color: var(--ink-soft);
  }
  .view-tabs .view-tab-btn:hover { background: rgba(0,0,0,0.05); color: var(--ink); border-color: transparent; }
  .view-tab-btn.active { background: var(--ink-soft); color: #fff; }
  .view-tabs .view-tab-btn.active:hover { background: var(--ink-soft); color: #fff; border-color: transparent; }
  .settings-subnav .settings-subnav-btn.active:hover { background: var(--ink-soft); color: #fff; border-color: transparent; }
  /* Locked while a record's edit form is open (2026-08-08, Wendy) -- greyed
     out and unclickable so a mid-edit click can't lose unsaved changes by
     jumping to a different table. Lit back up once back in list view. */
  .settings-subnav-btn:disabled { opacity: 0.4; cursor: not-allowed; }
  .settings-subnav .settings-subnav-btn:disabled:hover { background: transparent; opacity: 0.4; }
  .settings-subnav .settings-subnav-btn.active:disabled:hover { background: var(--ink-soft); color: #fff; }
  .settings-panel { display: none; }
  .settings-panel.active { display: block; }
  /* Admin: a tile-grid launcher (like a phone's home screen) instead of a
     tab bar — click a tile and it fully replaces the grid with that one
     setting's own page, same list/detail formatting as everything else. */
  .admin-tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; margin-top: 1.4rem; }
  .admin-tile {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0.6rem; text-align: left;
    padding: 1.25rem; border: 1px solid var(--line); border-radius: 14px; background: var(--surface);
    cursor: pointer; transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
    font-family: inherit; color: var(--ink);
  }
  .admin-tile:hover { box-shadow: 0 6px 18px rgba(20, 24, 29, 0.1); transform: translateY(-2px); border-color: var(--accent); }
  .admin-tile-icon {
    width: 42px; height: 42px; border-radius: 11px; background: var(--ink-soft); color: #fff;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  }
  .admin-tile-title { font-weight: 700; font-size: 1rem; }
  .admin-tile-desc { font-size: 0.82rem; color: var(--ink-soft); line-height: 1.4; }
  .admin-immersed-page { margin-top: 1rem; }
  .applies-to-badge {
    display: inline-block; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.02em;
    text-transform: uppercase; background: var(--sidebar-bg-soft); color: #fff;
    padding: 0.15rem 0.5rem; border-radius: 999px; margin-right: 0.3rem; margin-bottom: 0.2rem;
  }
  .filter-group { border-top: 1px solid var(--line); padding: 0.65rem 0; }
  .filter-group:first-of-type { border-top: none; }
  .filter-group summary {
    cursor: pointer; font-weight: 700; font-size: 0.85rem; list-style: none;
    display: flex; align-items: center; gap: 0.35rem;
  }
  .filter-group summary::-webkit-details-marker { display: none; }
  .filter-group summary::before {
    content: "\25B8"; display: inline-block; font-size: 0.65rem; color: var(--ink-soft);
    transition: transform 0.15s ease;
  }
  .filter-group[open] summary::before { transform: rotate(90deg); }
  .filter-options { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.55rem; }
  .filter-options label { display: flex; align-items: center; gap: 0.45rem; font-size: 0.82rem; font-weight: 500; cursor: pointer; }
  .filter-options:empty::after { content: "Nothing to filter yet."; font-size: 0.78rem; color: var(--ink-soft); font-weight: 400; }
  .filter-options label.filter-label-input { flex-direction: column; align-items: stretch; gap: 0.25rem; cursor: default; }
  /* Full-width so stacked From/To (and other date-range inputs) line up in a
     clean vertical column instead of each rendering at the native widget's
     ragged default width (Wendy, 2026-08-21). */
  .filter-options label.filter-label-input input { font-size: 0.8rem; padding: 0.35rem 0.5rem; width: 100%; box-sizing: border-box; }

  /* Active-filter chips — so nobody scrolls past a filter they forgot was
     still on; each is removable without needing another Apply click. */
  .active-filter-chips { display: flex; flex-wrap: wrap; gap: 0.45rem; }
  .active-filter-chips:not(:empty) { margin-bottom: 0.9rem; }
  .filter-chip {
    display: inline-flex; align-items: center; gap: 0.4rem; background: var(--sidebar-bg-soft); color: #fff;
    font-size: 0.78rem; font-weight: 600; padding: 0.32rem 0.5rem 0.32rem 0.75rem; border-radius: 999px;
  }
  .filter-chip button {
    background: none; border: none; color: #fff; font-weight: 800; cursor: pointer; padding: 0; line-height: 1; font-size: 0.95rem;
    width: 1.15rem; height: 1.15rem; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex: none;
  }
  .filter-chip button:hover { background: rgba(0, 0, 0, 0.25); }
  .content input, .content select, .content textarea {
    font-family: inherit; font-size: 0.9rem; padding: 0.5rem 0.9rem;
    border-radius: 999px; border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  }
  /* A 999px radius clamps to half the element's own height, which looks
     right on a single-line pill input but balloons into a lozenge/eye shape
     on a multi-line textarea several lines tall -- capped to a normal
     rounded-box radius instead (Wendy, 2026-08-26). */
  /* Same native-appearance issue as the disabled <select> fix below, just on
     textarea: without stripping native appearance, Safari/Chrome-on-Mac draw
     their own native focus chrome around a textarea that ignores
     border-radius/outline/box-shadow entirely, rendering as a big rounded
     halo regardless of what CSS says -- only visible on click, which is why
     the border-radius fix alone didn't do it (Wendy, 2026-08-26). */
  .content textarea { border-radius: 10px; appearance: none; -webkit-appearance: none; -moz-appearance: none; }
  .content textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(84,101,255,0.25); }
  /* :read-only matches checkboxes/radios too (they're never "read-write" per
     spec, having no text to edit) -- excluded here or every checkbox in the
     app would render with a disabled look/cursor despite being enabled. */
  .content input:read-only:not([type="checkbox"]):not([type="radio"]), .content input:disabled, .content select:disabled, .content textarea:read-only, .content textarea:disabled {
    background: var(--line); color: var(--ink-soft); cursor: not-allowed;
  }
  /* A disabled <select> renders with the browser's own native disabled-widget
     look (square corners, its own gray) that ignores border-radius/background
     entirely on most platforms -- unlike a readonly <input>, which stays
     fully custom-styled. Turning off native appearance here makes a locked
     select match a locked input pill for pill, e.g. Personnel's Work Email +
     Privilege Access while login access is active. */
  .content select:disabled {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
  }
  /* Combo-box inputs are readonly on purpose (selection-only, not free-
     typed) but they ARE fully editable via the dropdown — the generic
     read-only styling above made them look dead/disabled, misleading
     users into thinking they couldn't touch them. Selector specificity here
     must stay >= the .content input:read-only:not(...) rule above (same
     :not() clauses added) or that rule wins the cascade and this override
     silently stops applying. */
  .content .combo-box input:read-only:not([type="checkbox"]):not([type="radio"]) {
    background: var(--surface); color: var(--ink); cursor: pointer;
  }
  /* Modals/popovers (customerPopoverOverlay, hotel booking steps, etc.) sit
     outside .content, same as this app's other overlays — so anything using
     the .field/.row form pattern inside a .custom-confirm-overlay needs its
     own copy of the same input styling instead of silently falling back to
     the browser default. Scoped to the shared overlay class so every modal
     built this way gets it automatically, not just customer's. */
  .custom-confirm-overlay .field input, .custom-confirm-overlay .field select,
  .draggable-overlay .field input, .draggable-overlay .field select {
    font-family: inherit; font-size: 0.9rem; padding: 0.5rem 0.9rem; margin: 0;
    border-radius: 999px; border: 1px solid var(--line); background: var(--surface); color: var(--ink);
    box-sizing: border-box; width: 100%;
  }
  /* Same light-gray "can't touch this" look as .content input:disabled, for
     fields inside a draggable overlay (e.g. View Quote's Read-Only mode) --
     the blanket rule above always paints them var(--surface)/var(--ink), so
     a disabled field there stayed indistinguishable from an editable one. */
  .custom-confirm-overlay .field input:disabled, .custom-confirm-overlay .field select:disabled,
  .draggable-overlay .field input:disabled, .draggable-overlay .field select:disabled,
  .draggable-overlay .field textarea:disabled {
    background: var(--line); color: var(--ink-soft); cursor: not-allowed;
  }

  /* Pick from Pricelist dialog (Wendy, 2026-09-20 mockup): less-round fields
     than the app's default pill inputs. Scoped to this one dialog rather
     than the shared .draggable-overlay rule above, which every other
     dialog in the app still relies on. */
  #entItemDialogForm .field input, #entItemDialogForm .field select {
    border-radius: 8px;
  }

  .admin-tool { border: 1px solid var(--line); border-radius: 18px; padding: 1.3rem; margin-bottom: 1.2rem; background: var(--surface); box-shadow: 0 4px 20px rgba(27, 36, 48, 0.06); }
  /* Cards whose data lives on ANOTHER record (e.g. the Customer card on a
     Vacation Detail: name/address/phone/callback all belong to the customer,
     not this vacation) get a faint shade so staff read them as "sourced
     elsewhere, edit at the source." Theme-aware: a few percent of ink mixed
     into the surface tints subtly in both light and dark. */
  .admin-tool.card-external { background: color-mix(in srgb, var(--ink) 5%, var(--surface)); }
  /* Read-only demographics view inside the Customer card's Demographics tab. */
  .demo-readonly-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.8rem 1.2rem; }
  .demo-readonly-grid .demo-k { display: block; font-size: 0.7rem; letter-spacing: 0.03em; text-transform: uppercase; color: var(--ink-soft); }
  .demo-readonly-grid .demo-v { display: block; font-weight: 600; margin-top: 0.12rem; min-height: 1.1em; }
  @media (max-width: 640px) { .demo-readonly-grid { grid-template-columns: 1fr; } }
  .admin-tool h3 { margin-top: 0; font-size: 0.95rem; }
  .row { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: end; }
  .field { display: flex; flex-direction: column; gap: 0.3rem; }
  .field label { font-size: 0.75rem; color: var(--ink-soft); letter-spacing: 0.04em; font-weight: 600; }
  .detail-layout { display: flex; gap: 1.2rem; align-items: start; flex-wrap: wrap; }
  /* Vacation Info card: fixed field widths so rows of mismatched-length
     values (a date vs. a short code vs. an em-dash) still line up into a
     clean grid instead of ragged flex-wrap sizing to each field's content. */
  .detail-layout > div { flex: 1 1 420px; min-width: 0; }
  /* Vacation Detail's two "paired" rows (Vacation Info/Customer, then Hotel
     Accommodations/Final Travel Docs) -- unlike .detail-layout's two
     independent stacked columns, align-items:stretch here makes each pair
     match the taller card's height, per Wendy's request (2026-08-08). */
  .detail-row-equal { display: flex; gap: 1.2rem; align-items: stretch; flex-wrap: wrap; }
  .detail-row-equal > .admin-tool { flex: 1 1 420px; min-width: 0; margin-bottom: 0; }
  .muted { color: var(--ink-soft); font-size: 0.85rem; }
  /* A reference ID that isn't clickable shouldn't look clickable (Wendy,
     2026-08-20, same reasoning as Wave Allotment's ID column) -- plain text
     color, no underline, instead of the browser-default blue link look. */
  .id-link-plain { color: var(--ink); text-decoration: none; }
  #apptVacationPeekCard {
    background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
    padding: 0.6rem 0.9rem; min-width: 200px;
  }
  /* Fixed-width fields so columns line up instead of ragged flex-wrap sizing
     to content — but NOT flex-grow, on purpose: a field alone in its own row
     (e.g. Check In stacked under Check Out) should stay the same compact
     size as every other field, not stretch to fill the leftover row width.
     Widen a specific field only where it genuinely needs it, via its own
     inline flex/width override (see Package Name, MktgCtrCode, etc.). */
  .field-grid .row { align-items: start; }
  .field-grid .field { flex: 0 1 190px; min-width: 150px; }
  .field-grid .field input, .field-grid .field select { width: 100%; box-sizing: border-box; }
  /* .custom-confirm-box's own p/input[type=text] rules (used for simple
     one-field confirm dialogs) fought with this grid when reused on bigger
     forms like the Hotel Booking modal — a negative top margin on text
     inputs and a 1.3rem bottom margin on every <p> pulled fields out of
     alignment with their row siblings. Neutralized wherever a form opts
     into .field-grid instead. */
  .custom-confirm-box.field-grid .field p, .custom-confirm-box.field-grid .field input[type="text"] { margin: 0; }
  /* Same fix as field-grid above, for popovers using plain .field label+input
     rows (e.g. the Customer popover) without opting into the whole grid —
     the legacy negative top margin below was sliding inputs up over their
     own labels, clipping the label text. */
  .custom-confirm-box .field input[type="text"] { margin: 0; }
  /* Gives a plain read-only <p class="muted"> value the same filled-pill
     look a real disabled/readonly <input> gets (.content input:disabled
     above) — for cards where the value is display-only and was never a
     real form field, so it still reads as "a field" rather than bare text. */
  .field-box {
    display: inline-block; margin: 0; box-sizing: border-box;
    background: var(--line); color: var(--ink-soft);
    border-radius: 999px; padding: 0.5rem 0.9rem; font-size: 0.85rem;
  }
  .field-box.field-box-light { background: #e9ecfe; color: #3d4bc4; font-weight: 700; font-size: 0.95rem; }
  /* Hotel Booking modal: label-left, content-sized fields (per Wendy's own
     AMG Connect reference screenshot) instead of label-above stretched-width
     fields — a $ amount or a 3-letter code doesn't need a 190px-wide box,
     and stacking labels above every field is what made this read as one
     long paragraph instead of a form. */
  #hotelBookingStep2Overlay .custom-confirm-box p, #hotelBookingStep2Overlay .custom-confirm-box input[type="text"] { margin: 0; }
  .hb2-form { display: flex; gap: 2.2rem; flex-wrap: wrap; }
  .hb2-col { flex: 1 1 300px; min-width: 260px; }
  .hb2-row { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.5rem; }
  .hb2-row label { flex: 0 0 118px; font-size: 0.75rem; letter-spacing: 0.04em; color: var(--ink-soft); font-weight: 600; }
  .hb2-row input, .hb2-row select {
    margin: 0; box-sizing: border-box; width: 180px !important;
    font-family: inherit; font-size: 0.9rem; padding: 0.5rem 0.9rem;
    border-radius: 999px; border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  }
  .hb2-row p.field-box { margin: 0; box-sizing: border-box; width: 180px; }
  /* Compact overrides for the Vacation Detail summary cards (Wendy, 2026-08-21).
     The hb2-row/field-box sizing above was built for the wide Hotel Booking
     modal; in these narrow 210px columns the fixed 118px labels + 180px value
     boxes overflowed and wrapped, making rows tall and overlapping. Scoped to
     .detail-row-4 only, so the modal and everything else keep their sizing. */
  .detail-row-4 > .admin-tool { padding: 0.9rem; display: flex; flex-direction: column; }
  /* Card body fills the card; the FTD block is pinned to the bottom so the FTD
     sections line up across all four cards regardless of how much summary sits
     above them (Wendy, 2026-08-21). */
  .detail-row-4 .card-body { flex: 1 1 auto; display: flex; flex-direction: column; }
  .detail-row-4 .ftd-block { margin-top: auto; }
  /* When a card has no primary record, we hide all its fields and show one big
     grey icon centered in the card (Wendy, 2026-08-21). */
  .card-empty-icon { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; color: #c4cad6; padding: 1.5rem 0; }
  .detail-row-4 .hb2-row label { white-space: nowrap; }
  .detail-row-4 .hb2-row { margin-top: 0.3rem; gap: 0.5rem; align-items: center; }
  .detail-row-4 .hb2-row label { flex: 0 0 84px; font-size: 0.68rem; }
  /* Value boxes fill the same fixed column (flex:1 basis:0) so they always
     start and end at the same place regardless of text length -- nothing
     shifts horizontally between fields/records (Wendy, 2026-08-21). */
  /* min-height matches the FTD inputs so an empty read-only value box (e.g.
     the script-filled Appointment FTD) keeps the same height instead of
     collapsing -- otherwise its FTD block is shorter and sits lower than the
     Hotel/Entertainment ones (Wendy, 2026-08-21). */
  .detail-row-4 .hb2-row p.field-box { flex: 1 1 0; min-width: 0; width: auto; max-width: none; padding: 0.24rem 0.55rem; font-size: 0.8rem; min-height: 1.95rem; display: flex; align-items: center; box-sizing: border-box; }
  .detail-row-4 .field-box { padding: 0.24rem 0.55rem; font-size: 0.8rem; }
  .detail-row-4 .hb2-row .combo-box { flex: 1 1 auto; min-width: 0; }
  .detail-row-4 .hb2-row input, .detail-row-4 .hb2-row select { width: 100% !important; padding: 0.28rem 0.55rem; }
  /* Bare date inputs in the FTD cards (Dt Sent) were rendering wider than the
     sibling combo pills and carried the native calendar icon, breaking the
     column alignment. Flex them like the field boxes and drop the icon so the
     Delivery / Dt Sent / Sent By fields line up (Wendy, 2026-08-22). */
  .detail-row-4 .hb2-row input[type="date"] { flex: 1 1 0; min-width: 0; width: auto !important; }
  .detail-row-4 .hb2-row input[type="date"]::-webkit-calendar-picker-indicator { display: none; }
  .detail-row-4 .section-divider { margin-top: 0.7rem; margin-bottom: 0.15rem; font-size: 0.72rem; }
  /* Hide the native "mm/dd/yyyy" text on an empty date input until it's
     focused (Wendy, 2026-08-21 -- doesn't like the placeholder). A .date-blank
     class is toggled in JS when the field has no value. */
  input.date-blank::-webkit-datetime-edit { color: transparent; }
  input.date-blank:focus::-webkit-datetime-edit { color: var(--ink); }
  .detail-row-4 .field label { font-size: 0.68rem; margin-bottom: 0.1rem; }
  /* The generic ".hb2-row label" rule above is sized/styled for this row's
     one outer field label (e.g. "Booking Cost $") — but it's a descendant
     selector, so it was also grabbing #hb2Requests's per-checkbox labels,
     squeezing "1 Bed", "Smoking", etc. into that same fixed 118px column
     and splitting each checkbox from its own text. */
  #hb2Requests label { flex: 0 0 auto; text-transform: none; font-size: 0.85rem; font-weight: 500; color: var(--ink); }
  .hb2-stepper { display: flex; align-items: center; gap: 0.4rem; width: 180px; }
  .hb2-stepper input { width: 60px !important; text-align: center; }
  .hb2-stepper button { padding: 0.3rem 0.7rem; }
  .empty-note { font-size: 0.85rem; color: var(--ink-soft); font-style: italic; padding: 0.4rem 0; }

  /* Was scoped to #notesList only (Customer Detail's own list), so every
     other list reusing this exact .note-row/.note-meta markup (Vacation
     Detail's Notes/Email History/SMS History) silently got none of it. */
  .note-row { margin-bottom: 0.8rem; font-size: 0.88rem; }
  .note-row:last-child { margin-bottom: 0; }
  .note-meta { color: var(--ink-soft); font-size: 0.7rem; font-weight: 700; letter-spacing: 0.04em; margin-bottom: 0.3rem; }
  .note-row p { margin: 0 0 0.4rem; }
  .note-row ul { margin: 0 0 0.4rem; padding-left: 1.2rem; }
  /* Notes has no pagination -- cap it at a fixed height with an internal
     scroll once a vacation accumulates a long history, rather than letting
     the whole card (and page) grow indefinitely. */
  #vacationNotesList { max-height: 420px; overflow-y: auto; }

  .note-toolbar { display: flex; align-items: center; gap: 0.3rem; margin-top: 0.7rem; }
  /* min-width/min-height + flex-centering keeps every toolbar button (B/I/U/
     bullets/emoji) the same footprint regardless of glyph -- the emoji
     character's own font metrics render shorter than plain letters otherwise,
     making that one button look smaller (Wendy, 2026-09-20). */
  .note-fmt-btn {
    padding: 0.35rem 0.7rem; font-size: 0.85rem; border-radius: 8px;
    min-width: 34px; min-height: 34px; box-sizing: border-box;
    display: inline-flex; align-items: center; justify-content: center; line-height: 1;
  }
  .note-fmt-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
  .note-editor {
    width: 100%; min-height: 4.5rem; margin-top: 0.4rem; font-family: inherit; font-size: 1rem;
    border-radius: 16px; border: 1px solid var(--line); padding: 0.6rem 0.9rem; background: var(--surface); color: var(--ink);
  }
  .note-editor:empty::before { content: attr(data-placeholder); color: var(--ink-soft); opacity: 0.6; }
  /* Vacation Detail's Notes card is full-width now -- fill the row up to
     Add Note (grow enabled) rather than sitting fixed at 720px and leaving
     a dead gap before the button. */
  #vacationNotesPanel .note-editor, #notesDetails .note-editor, #apptNotesPanel .note-editor { width: auto; flex: 1 1 auto; margin-top: 0; }
  .note-editor p { margin: 0 0 0.4rem; }
  .note-editor ul { margin: 0 0 0.4rem; padding-left: 1.2rem; }

  details.admin-tool summary { cursor: pointer; font-weight: 700; list-style: none; display: flex; align-items: center; gap: 0.45rem; }
  details.admin-tool summary::-webkit-details-marker { display: none; }
  details.admin-tool summary .chevron { flex-shrink: 0; transition: transform 0.15s ease; color: var(--ink-soft); }
  details.admin-tool[open] summary .chevron { transform: rotate(90deg); }
  .change-log-list { margin-top: 0.8rem; font-size: 0.85rem; }
  #vacationLogList { font-size: 0.72rem; }
  .change-log-list .log-row { border-bottom: 1px solid var(--line); padding: 0.4rem 0; }
  .change-log-list .log-row:last-child { border: none; }

  .checkbox-list {
    /* Solid, not the ~91%-opaque .filters-panel background it sits inside
       (that panel's color-mix blends in --line's own rgba alpha, invisible
       against plain list rows but very visible bleeding through against
       something bold sitting close behind, like the Analytics bar chart's
       "N sent" labels -- Wendy, 2026-09-08). */
    background: var(--surface);
    border: 1px solid var(--line); border-radius: 10px; padding: 0.6rem 0.8rem;
    height: 230px; overflow-y: auto; resize: vertical; display: flex; flex-direction: column; gap: 0.45rem;
  }
  /* .filters-panel label (below) ties this on specificity (one class + one
     element, same as this rule) and sits later in the file, so on a
     checkbox-list living inside a filters-popover (Marketing Center/Package/
     Template filters, Wendy, 2026-09-08) that rule's display:block was
     winning and stacking each checkbox above its own label text instead of
     beside it. Qualified with .filters-panel too so this always wins
     regardless of source order, not just where it happens to nest today. */
  .checkbox-list label, .filters-panel .checkbox-list label {
    display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; font-weight: 400;
    text-transform: none; letter-spacing: normal; color: var(--ink); cursor: pointer;
  }
  /* Agent/Manager don't use "Marketing Centers this user can manage" at all
     (Wendy, 2026-08-20) -- greyed out rather than just disabled inputs, so
     it visibly reads as "not applicable" for this role, not "temporarily
     broken." */
  .checkbox-list-disabled { opacity: 0.45; background: var(--bg); }
  .checkbox-list-disabled label { cursor: not-allowed; }

  #winToast {
    /* Above .draggable-overlay/.custom-confirm-overlay (both z-index: 200) —
       otherwise a validation/status toast fired while any popover is open
       renders invisibly behind it, looking like nothing happened at all. */
    position: fixed; top: 1.4rem; right: 1.4rem; z-index: 300;
    display: flex; align-items: center; gap: 0.5rem;
    background: var(--sidebar-bg); color: #fff; padding: 0.7rem 1.1rem; border-radius: 999px;
    box-shadow: 0 10px 30px rgba(20,35,42,0.35);
    opacity: 0; transform: translateY(-8px) scale(0.9); pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
  }
  #winToast.show { opacity: 1; transform: translateY(0) scale(1); }
  #winToast .twinkle-star { color: var(--gold); font-size: 1.1rem; animation: twinkle-pop 0.6s ease; }
  @keyframes twinkle-pop {
    0% { transform: scale(0.4) rotate(-20deg); opacity: 0; }
    50% { transform: scale(1.3) rotate(10deg); opacity: 1; }
    100% { transform: scale(1) rotate(0deg); opacity: 1; }
  }

  /* Live new-text alert toast (Wendy, 2026-08-21). Sits just below where the
     win/info toast appears, styled as a clickable card (not a pill) since it
     carries a From line, a snippet, and its own View/dismiss controls. */
  #smsToast {
    position: fixed; top: 4.6rem; right: 1.4rem; z-index: 300;
    display: flex; align-items: flex-start; gap: 0.7rem;
    width: 340px; max-width: calc(100vw - 2.8rem);
    background: var(--surface); color: var(--ink);
    border-left: 4px solid var(--accent); border-radius: 12px;
    padding: 0.85rem 1rem; box-shadow: 0 14px 40px rgba(20,35,42,0.28);
    transform: translateY(-8px); opacity: 0; pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
  }
  #smsToast.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
  /* Live new-payment alert toast (Wendy, 2026-09-17). Same card as #smsToast
     above, offset lower so it doesn't overlap if both fire close together. */
  #paymentToast {
    position: fixed; top: 9.4rem; right: 1.4rem; z-index: 300;
    display: flex; align-items: flex-start; gap: 0.7rem;
    width: 340px; max-width: calc(100vw - 2.8rem);
    background: var(--surface); color: var(--ink);
    border-left: 4px solid var(--accent); border-radius: 12px;
    padding: 0.85rem 1rem; box-shadow: 0 14px 40px rgba(20,35,42,0.28);
    transform: translateY(-8px); opacity: 0; pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
  }
  #paymentToast.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
  .sms-toast-icon { font-size: 1.2rem; line-height: 1.4; flex-shrink: 0; }
  .sms-toast-body { flex: 1 1 auto; min-width: 0; }
  .sms-toast-title { font-size: 0.7rem; letter-spacing: 0.04em; font-weight: 700; color: var(--ink-soft); }
  .sms-toast-from { font-weight: 700; font-size: 0.92rem; margin-top: 0.15rem; }
  .sms-toast-snippet { font-size: 0.85rem; color: var(--ink-soft); margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* #smsToast/#paymentToast-scoped so it outweighs the generic
     button:hover:not(:disabled) rule, which otherwise flips the background
     to near-white while the text stays white on hover (white-on-white
     "drowned out" letters). */
  #smsToast .sms-toast-view, #paymentToast .sms-toast-view {
    flex-shrink: 0; align-self: center; background: var(--accent); color: #fff;
    border: none; border-radius: 999px; padding: 0.4rem 0.9rem; font-weight: 600;
    font-size: 0.82rem; cursor: pointer;
  }
  #smsToast .sms-toast-view:hover, #paymentToast .sms-toast-view:hover { background: var(--sidebar-bg-soft); color: #fff; border-color: transparent; }
  .sms-toast-close {
    position: absolute; top: 0.35rem; right: 0.5rem; background: none; border: none;
    color: var(--ink-soft); font-size: 1.1rem; line-height: 1; cursor: pointer; padding: 0.1rem 0.3rem;
  }
  .sms-toast-close:hover { color: var(--ink); }

  /* Live unread-SMS count on the Customer Contacts sidebar item. */
  .nav-item { position: relative; }
  .nav-unread-badge {
    display: none; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px; margin-left: auto;
    border-radius: 999px; background: var(--gold); color: #20242b;
    font-size: 0.7rem; font-weight: 800; line-height: 1;
  }
  /* Collapsed sidebar has no room in the row for icon + badge side by side
     (68px width < icon + gap + badge), which squished them together.
     Pin the badge as a corner dot on the icon instead, off the flex flow. */
  .sidebar.collapsed .nav-unread-badge {
    position: absolute; top: 0.2rem; right: 0.3rem; margin-left: 0;
    min-width: 16px; height: 16px; padding: 0 4px; font-size: 0.62rem;
  }

  /* Overview panels (e.g. New SMS Messages box). */
  .overview-panel {
    background: var(--surface); border-radius: 16px; padding: 1.1rem 1.2rem;
    box-shadow: 0 4px 20px rgba(27,36,48,0.06); margin-bottom: 1.3rem;
  }
  .overview-panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.6rem; }
  .overview-panel-title { font-size: 0.95rem; font-weight: 800; display: flex; align-items: center; gap: 0.5rem; }
  .overview-sms-row {
    display: flex; align-items: center; gap: 0.8rem; padding: 0.6rem 0.4rem;
    border-top: 1px solid var(--line); cursor: pointer; border-radius: 8px;
  }
  .overview-sms-row:hover { background: var(--bg); }
  .overview-sms-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
  .overview-sms-who { font-weight: 700; font-size: 0.9rem; }
  .overview-sms-snippet { font-size: 0.83rem; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .overview-sms-time { flex-shrink: 0; font-size: 0.75rem; color: var(--ink-soft); }

  .custom-confirm-overlay {
    display: none; position: fixed; inset: 0; z-index: 200;
    background: rgba(20, 24, 29, 0.45); align-items: center; justify-content: center; padding: 1rem;
  }
  .custom-confirm-overlay.show { display: flex; }
  /* "Save this search" opens this name box from inside the Filters window
     (z-index 400), so it has to sit above that window, not behind it
     (Wendy, 2026-09-29). */
  #customPromptOverlay { z-index: 450; }
  .custom-confirm-box {
    background: var(--surface); color: var(--ink); border-radius: 18px; padding: 1.6rem;
    max-width: 420px; width: 100%; box-shadow: 0 20px 50px rgba(20,35,42,0.3);
  }
  .custom-confirm-box p { margin: 0 0 1.3rem; font-size: 0.95rem; line-height: 1.5; }
  .custom-confirm-actions { display: flex; justify-content: flex-end; gap: 0.6rem; }
  .custom-confirm-box input[type="text"] { width: 100%; margin: -0.6rem 0 1.3rem; box-sizing: border-box; }
  #hipWebsite a { color: var(--accent); font-weight: 600; }

  /* A movable window, not a fixed modal box — same dimmed backdrop
     convention as .custom-confirm-overlay, but the box itself can be
     dragged anywhere by its title bar so staff can peek at what's behind it
     without closing it. Position (top/left) is set/updated by JS. */
  .draggable-overlay {
    display: none; position: fixed; inset: 0; z-index: 200; background: rgba(20, 24, 29, 0.45);
  }
  .draggable-overlay.show { display: block; }
  /* Entertainment Pricelist weekly-schedule editor (Wendy, 2026-10-05):
     a resting list of day rows -- pencil on the left, then the day, then its
     showtimes read only -- each row opening a focused draggable day editor. */
  .ep-day-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.45rem 0.2rem; border-top: 1px solid var(--line); }
  .ep-day-row:first-child { border-top: none; }
  .ep-day-pencil { flex: none; width: 2rem; height: 2rem; display: grid; place-items: center; border: none; background: transparent; color: var(--accent); border-radius: 50%; cursor: pointer; padding: 0; }
  .ep-day-pencil:hover { background: rgba(84, 101, 255, 0.12); }
  .ep-day-pencil svg { width: 1.1rem; height: 1.1rem; }
  .ep-day-name { flex: none; width: 6.5rem; font-weight: 600; }
  .ep-day-row.dark .ep-day-name { color: var(--ink-soft); font-weight: 500; }
  .ep-day-times { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
  .ep-day-dark { color: var(--ink-soft); font-size: 0.85rem; }
  .ep-day-opennote { color: var(--ink-soft); font-size: 0.85rem; font-style: italic; }
  /* On/off switch used inside the day editor for Open vs Dark. */
  .ep-switch { position: relative; width: 46px; height: 28px; flex: none; display: inline-block; }
  .ep-switch input { opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
  .ep-switch .ep-track { position: absolute; inset: 0; border-radius: 999px; background: var(--line); transition: background 0.2s; pointer-events: none; }
  .ep-switch .ep-knob { position: absolute; top: 2px; left: 2px; width: 24px; height: 24px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); transition: transform 0.2s; pointer-events: none; }
  /* Lighter, fresher "on" green that pairs with the app's purples instead of
     the deep status-pill green (Wendy, 2026-10-06). Shared by every .ep-switch
     slider -- Offers Active, Entertainment Active, Show days/times -- so they
     all stay consistent. */
  .ep-switch input:checked ~ .ep-track { background: #34c759; }
  .ep-switch input:checked ~ .ep-knob { transform: translateX(18px); }
  .draggable-window {
    position: absolute; background: var(--surface); color: var(--ink); border-radius: 16px;
    box-shadow: 0 20px 50px rgba(20,35,42,0.35); width: 640px; max-width: calc(100vw - 2rem);
    max-height: calc(100vh - 2rem); display: flex; flex-direction: column; overflow: hidden;
    resize: both; min-width: 480px; min-height: 420px;
  }
  .draggable-window-header {
    display: flex; align-items: center; justify-content: space-between; flex-shrink: 0;
    padding: 0.9rem 1.1rem; border-bottom: 1px solid var(--line); cursor: move; user-select: none;
  }
  .draggable-window-body { padding: 1.1rem; overflow-y: auto; flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
  /* Payment Transaction popup is intentionally short (one field, two
     buttons) -- the shared 420px floor above left a big empty gap in the
     middle (Wendy, 2026-09-18: "can you make this dialog not so tall?"). */
  #paymentActionWindow { min-height: 280px; }

  /* Explicit scroll buttons for long popup content (Wendy, 2026-08-24 --
     scrolling with the mouse wheel over a popup was bleeding through to the
     page behind it, and she'd rather staff have an obvious click target
     than rely on the wheel at all). Floats over the bottom-right corner of
     whichever .draggable-window it's placed in. */
  .notes-scroll-controls { position: absolute; right: 1.1rem; bottom: 1.1rem; display: flex; flex-direction: column; gap: 0.5rem; z-index: 5; }
  .notes-scroll-btn {
    width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--line); background: var(--surface);
    color: var(--ink-soft); box-shadow: 0 4px 14px rgba(20,35,42,0.18); cursor: pointer; font-size: 0.85rem;
    display: flex; align-items: center; justify-content: center;
  }
  .notes-scroll-btn:hover { background: var(--bg); color: var(--ink); }

  /* Showtime chips: a real clickable list of specific times (Wendy,
     2026-08-24 -- both on the Entertainment Pricelist admin form, where
     they're built/removed, and on the item dialog's picker, where clicking
     one fills Event Time). Deliberately its own small pill style rather than
     .nav-link-btn or .status-pill -- those read as a link/badge, not
     something you tap to pick. */
  .showtime-chip-btn {
    display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.35rem 0.8rem; border-radius: 999px;
    border: 1.5px solid var(--accent); background: var(--surface); color: var(--accent); font-weight: 600;
    font-size: 0.85rem; cursor: pointer;
  }
  /* Scoped under its container so it outweighs the generic
     button:hover:not(:disabled) rule (same recurring specificity bug as
     .settings-subnav-btn above) -- that rule was winning on
     background/border-color but not color, leaving white text (my rule)
     on a near-white background (the generic rule's), i.e. invisible on
     hover. */
  #entItemDialogShowtimesChips .showtime-chip-btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }

  /* New Appointment booking wizard: day-strip / full-calendar date picker
     and the wave/time selection list. Days and waves with zero available
     seats are shown (not hidden) but disabled, so staff can see demand
     rather than wondering why a date is missing. */
  .appt-day-strip { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .appt-day-tile {
    flex: 1 1 60px; min-width: 60px; text-align: center; padding: 0.5rem 0.3rem;
    border-radius: 10px; border: 1px solid var(--line); cursor: pointer; font-size: 0.78rem;
  }
  .appt-day-tile .appt-day-label { font-weight: 600; }
  .appt-day-tile .appt-day-avail { font-size: 0.72rem; color: var(--ink-soft); margin-top: 0.2rem; }
  .appt-day-tile.selected { background: var(--accent); color: #fff; border-color: var(--accent); }
  .appt-day-tile.selected .appt-day-avail { color: rgba(255,255,255,0.85); }
  .appt-day-tile.disabled { opacity: 0.4; cursor: not-allowed; }
  .appt-calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 0.4rem; }
  .appt-calendar-grid .appt-day-tile { min-width: 0; }
  /* Custom date-range inputs in the Appointments filter popover: stack the two
     date fields so the second one can't spill outside the narrow popover
     (Wendy, 2026-08-23). */
  #appointmentsApptDateCustom, #appointmentsCreatedCustom { flex-direction: column; }
  #appointmentsApptDateCustom input, #appointmentsCreatedCustom input { width: 100%; }

  /* Floating New Appointment button on the Appointments list (bottom-right).
     Explicit :hover so it isn't washed out by the generic button:hover rule
     that flips filled buttons to a light bg (recurring gotcha). */
  .desk-fab {
    position: fixed; right: 1.8rem; bottom: 1.8rem; z-index: 150;
    display: inline-flex; align-items: center; gap: 0.4rem;
    background: var(--accent); color: #fff; border: none; border-radius: 999px;
    padding: 0.85rem 1.4rem; font-size: 0.95rem; font-weight: 700; cursor: pointer;
    box-shadow: 0 10px 26px rgba(84,101,255,0.45);
  }
  .desk-fab:hover:not(:disabled) { background: var(--sidebar-bg-soft); color: #fff; border: none; }

  /* New Appointment wizard bits */
  .dna-err { color: var(--danger); font-size: 0.85rem; margin: 0.6rem 0 0; min-height: 1em; }
  .dna-info { background: var(--bg); border: 1px solid var(--line); border-radius: 12px; padding: 0.8rem 0.95rem; margin: 0 0 0.9rem; font-size: 0.95rem; line-height: 1.5; }
  .dna-actions { display: flex; justify-content: flex-end; margin-top: 1rem; }
  .dna-actions-split { justify-content: space-between; align-items: center; }
  .dna-match-list { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.9rem; }
  .dna-match {
    display: flex; flex-direction: column; gap: 0.15rem; text-align: left; width: 100%;
    border: 1px solid var(--line); border-radius: 12px; padding: 0.7rem 0.9rem; cursor: pointer;
    background: var(--surface); color: var(--ink); font-size: 0.9rem;
  }
  .dna-match:hover:not(:disabled) { border-color: var(--accent); background: var(--bg); }
  .dna-match .dna-match-id { color: var(--ink-soft); font-size: 0.8rem; }
  /* Live phone/email dedup check (Wendy, 2026-09-17) -- same behavior as the
     mobile intake form's green check / gold caution icon, ported to desktop. */
  .dna-field-row { position: relative; }
  .dna-field-row input { padding-right: 2.6rem; }
  .dna-check {
    position: absolute; right: 0.7rem; top: 50%; transform: translateY(-50%);
    width: 22px; height: 22px; display: none; align-items: center; justify-content: center;
  }
  .dna-check.show { display: flex; }
  .dna-check.ok { color: #1a9e5c; }
  .dna-check.warn { color: var(--gold); cursor: pointer; }
  .dna-mismatch {
    display: flex; align-items: center; gap: 0.5rem; background: rgba(192,57,43,0.1); color: var(--danger);
    border: 1px solid rgba(192,57,43,0.35); border-radius: 12px; padding: 0.7rem 0.9rem; font-size: 0.88rem; font-weight: 600;
    margin: 0 0 0.9rem;
  }
  .dna-locked { background: var(--bg); color: var(--ink-soft); }
  /* Matched-customer preview card, clicked open from a gold dedup triangle --
     same info + "Take it" behavior as mobile's preview overlay (Wendy,
     2026-09-17). Sits over the intake step only; never advances the wizard. */
  #deskApptWizard .draggable-window-body { position: relative; }
  .dna-preview-overlay {
    display: none; position: absolute; inset: 0; background: var(--surface);
    padding: 1.3rem; overflow-y: auto; z-index: 5;
  }
  .dna-preview-overlay.show { display: block; }
  .dna-preview-close { display: block; margin: 0 0 0.8rem; padding: 0; }
  .dna-preview-name { margin: 0 0 0.2rem; font-size: 1.1rem; font-weight: 800; }
  .dna-preview-sub { margin: 0 0 1rem; color: var(--ink-soft); font-size: 0.85rem; }
  .dna-preview-row { display: flex; justify-content: space-between; gap: 0.6rem; padding: 0.35rem 0; border-bottom: 1px solid var(--line); font-size: 0.88rem; }
  .dna-preview-row:last-of-type { border-bottom: none; }
  .dna-preview-row-label { color: var(--ink-soft); }
  .dna-preview-appts-label { margin: 1.1rem 0 0.5rem; font-size: 0.76rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-soft); }
  .dna-appt-row { padding: 0.6rem 0.75rem; border-radius: 10px; background: var(--bg); font-size: 0.86rem; margin-bottom: 0.5rem; border: 1px solid var(--line); }
  .dna-appt-row:last-child { margin-bottom: 0; }
  .dna-appt-top { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }
  .dna-appt-detail { margin-top: 0.3rem; color: var(--ink-soft); font-size: 0.8rem; }
  .dna-appts-loading, .dna-appts-empty { color: var(--ink-soft); font-size: 0.86rem; margin: 0; }

  .appt-wave-list { display: flex; flex-direction: column; gap: 0.5rem; }
  .appt-wave-row {
    display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0.8rem;
    border: 1px solid var(--line); border-radius: 10px; cursor: pointer; font-size: 0.85rem;
  }
  .appt-wave-row.selected { background: var(--accent); color: #fff; border-color: var(--accent); }
  .appt-wave-row.disabled { opacity: 0.4; cursor: not-allowed; }
  /* Availability status per time slot — same palette as .status-pill.active/
     .inactive/.disabled-access elsewhere, so "available/full/overbooked"
     reads consistently with the rest of the app (Wendy, 2026-08-19). Only
     .available stays clickable; a slot at 0 or below is never selectable,
     so no one can continue with a time that isn't actually open. */
  /* Wave-time lists in the booking wizard + Edit Tour can run long -- cap the
     height and scroll so the dialog stays usable (Wendy, 2026-08-22). */
  #apptBookingWaveList, #apptRescheduleWaveList { max-height: 300px; overflow-y: auto; }
  .appt-wave-status { font-weight: 600; padding: 0.15rem 0.55rem; border-radius: 999px; font-size: 0.78rem; }
  .appt-wave-status-available { background: #e4f5ec; color: #1a7a4c; }
  .appt-wave-status-soldout { background: var(--line); color: var(--ink-soft); }
  .appt-wave-status-overbooked { background: #fbe4e1; color: #b0392b; }

  /* Wave Allotment report tab (Wendy, 2026-08-20): one Showroom + one Week
     at a time, a heatmap grid -- one row per wave time, one column per day
     -- instead of overlapping lines, since with several wave times sharing
     one line chart there was no reliable way to tell which line was which.
     Each cell is colored by how full that time/day is; bright green is
     reserved for exactly 100% ("perfection"), anything over is red, and
     everything under 100% stays a muted green scale so it doesn't compete
     visually with a genuinely full slot. */
  .wave-report-card {
    border-radius: 16px; padding: 1.6rem 1.8rem; margin-top: 1rem;
    background: var(--surface); border: 1px solid var(--line);
    box-shadow: 0 4px 20px rgba(27,36,48,0.06);
  }
  html[data-theme="dark"] .wave-report-card {
    background: radial-gradient(120% 140% at 15% 0%, #1c2333 0%, #0c0f16 65%, #08090d 100%);
    border: 1px solid rgba(255,255,255,0.06);
    box-shadow: 0 20px 60px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.04);
  }
  .wr-controls-row { display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
  .wr-week-nav { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: 0.9rem; color: var(--ink); }
  .wr-week-nav button { border-radius: 999px; }
  .wr-legend-label { font-size: 0.9rem; color: var(--ink); }
  .wr-chart-empty { color: var(--ink-soft); font-size: 0.9rem; padding: 2rem 0; text-align: center; }

  table.wr-grid { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
  table.wr-grid th, table.wr-grid td { padding: 0.55rem 0.5rem; text-align: center; }
  table.wr-grid thead th {
    color: var(--ink-soft); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em;
    border-bottom: 1px solid var(--line);
  }
  table.wr-grid thead th.wr-time-col-head { text-align: left; }
  table.wr-grid tbody th {
    text-align: left; font-weight: 600; color: var(--ink); white-space: nowrap;
    border-bottom: 1px solid var(--line);
  }
  table.wr-grid tbody td, table.wr-grid tfoot td { border-bottom: 1px solid var(--line); }
  .wr-cell {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    border-radius: 8px; padding: 0.45rem 0.3rem; min-width: 58px;
  }
  .wr-cell .wr-ratio { font-weight: 700; font-size: 0.85rem; }
  .wr-cell .wr-pct { font-size: 0.68rem; opacity: 0.85; margin-top: 1px; }
  .wr-lvl-none { background: rgba(127,140,155,0.18); color: var(--ink-soft); }
  .wr-lvl-0   { background: transparent; color: var(--ink-soft); }
  .wr-lvl-25  { background: rgba(46,230,166,0.14); color: var(--ink); }
  .wr-lvl-50  { background: rgba(46,230,166,0.28); color: var(--ink); }
  .wr-lvl-75  { background: rgba(46,230,166,0.45); color: #0a3b2c; }
  .wr-lvl-90  { background: rgba(46,230,166,0.62); color: #0a3b2c; }
  .wr-lvl-100 { background: rgba(46,230,166,0.85); color: #05261b; }
  html[data-theme="dark"] .wr-lvl-75  { color: #eafff6; }
  html[data-theme="dark"] .wr-lvl-90  { color: #eafff6; }
  html[data-theme="dark"] .wr-lvl-100 { color: #04140f; }
  .wr-lvl-over-low  { background: rgba(255,91,91,0.35); color: #4a0b0b; }
  .wr-lvl-over-high { background: rgba(255,91,91,0.65); color: #fff; }
  html[data-theme="dark"] .wr-lvl-over-low { color: #ffe6e6; }

  .wr-totals-col th, .wr-totals-col td { border-left: 2px solid var(--line); }
  .wr-totals-block { display: flex; flex-direction: column; gap: 2px; align-items: center; }
  .wr-totals-block .wr-allotment { color: var(--ink-soft); font-size: 0.7rem; }
  .wr-totals-block .wr-booked { font-weight: 700; font-size: 0.85rem; }

  table.wr-grid tfoot tr { background: rgba(100,110,125,0.08); }
  html[data-theme="dark"] table.wr-grid tfoot tr { background: rgba(255,255,255,0.06); }
  table.wr-grid tfoot th { text-align: left; font-weight: 700; padding: 0.65rem 0.5rem; border-top: 2px solid var(--line); border-bottom: none; }
  table.wr-grid tfoot td { padding: 0.65rem 0.5rem; border-top: 2px solid var(--line); }

  /* Appointments list: wide stacked cards instead of a long horizontal
     table row (Wendy, 2026-08-19) -- each field gets its own labeled block
     so nothing needs a horizontal scroll to read. */
  /* "You were just here" marker on the row you returned to from a detail view
     (Wendy, 2026-08-22). Stays light blue the whole time you're on the list.
     Text color is pinned dark on purpose (Wendy, 2026-08-24) -- the highlight
     background stays the same fixed light blue in both themes, so it can't
     just inherit --ink, which flips to near-white in dark mode and made the
     whole row unreadable. Scoped to the cell itself, not a blanket * rule --
     status pills/badges inside the row keep their own explicit colors via
     normal CSS inheritance rules. */
  tr.row-flash td { background-color: #dbeafe; color: #16233a; }
  tr.row-flash td:first-child { box-shadow: inset 3px 0 0 0 var(--accent); }
  /* Card-list variant (Appointments) */
  .appt-card.row-flash { background-color: #dbeafe; color: #16233a; box-shadow: inset 3px 0 0 0 var(--accent); }
  .appt-card-list { display: flex; flex-direction: column; gap: 0.7rem; }
  .appt-card {
    border: 1px solid var(--line); border-radius: 14px; background: var(--surface);
    padding: 0.9rem 1.1rem; box-shadow: 0 4px 20px rgba(27,36,48,0.06);
  }
  .appt-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.7rem; }
  .appt-card-header b { font-size: 1rem; }
  .appt-card-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 0.8rem 1.2rem; }
  .appt-card-field label { display: block; font-size: 0.72rem; font-weight: 600; color: var(--ink-soft); opacity: 0.65; letter-spacing: 0.04em; margin-bottom: 0.15rem; }
  .appt-card-field div { font-size: 0.92rem; }
  /* Grouped/stacked columns on the appointment card (Wendy, 2026-08-22): each
     group is a vertical stack; groups flow across in responsive columns. */
  .appt-card-groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.9rem 1.3rem; align-items: start; }
  .appt-group { display: flex; flex-direction: column; gap: 0.55rem; }
  .appt-group-nolabel { gap: 0.35rem; font-size: 0.92rem; }
  .appt-group-nolabel > div { font-size: 0.92rem; }
  .appt-id-line { display: flex; align-items: center; gap: 0.4rem; }
  .appt-id-line b { font-size: 1rem; }
  .appt-primary-name { font-weight: 700; }
  .appt-rescheduled-pill { display: inline-block; background: var(--gold); color: #3a2c00; font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 0.1rem 0.45rem; border-radius: 999px; }
  .appt-bounceback-pill { display: inline-block; margin-left: 0.35rem; background: #e3ecfb; color: #1d4a8f; font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 0.1rem 0.45rem; border-radius: 999px; white-space: nowrap; }
  html[data-theme="dark"] .appt-bounceback-pill { background: #23395e; color: #cfe0ff; }
  /* Main View: pill sits under Membership in the column, so no left gap and
     no stretching to the column's full width. */
  .appt-group > .appt-bounceback-pill { margin-left: 0; align-self: flex-start; }
  /* Appointments Admin View (Wendy, 2026-10-02): spreadsheet grid with skinny
     rows -- deliberate exception to rule 1. Scrolls sideways; header row
     stays put while scrolling down inside the grid. */
  .admin-grid-wrap { overflow: auto; max-height: 72vh; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }
  .admin-grid-wrap .admin-grid { width: max-content; min-width: 100%; border-radius: 0; box-shadow: none; overflow: visible; font-size: 0.8rem; }
  .admin-grid-wrap .admin-grid th,
  .admin-grid-wrap .admin-grid td { padding: 0.3rem 0.6rem; white-space: nowrap; vertical-align: middle; border-right: 1px solid var(--line); }
  /* Headers may wrap to 2+ lines so a long label (e.g. "Marketing Center")
     doesn't stretch the whole column wider than its short data (Wendy, 2026-10-04).
     Data cells stay nowrap (set above). */
  .admin-grid-wrap .admin-grid th { position: sticky; top: 0; z-index: 2; font-weight: 600; white-space: normal; line-height: 1.2; vertical-align: bottom; }
  /* Header row a shade darker gray so it reads as a header (Wendy, 2026-10-02). */
  .admin-grid-wrap .admin-grid thead th { background: #e4e7eb; color: #2b3440; border-bottom: 1px solid rgba(27, 36, 48, 0.35); }
  html[data-theme="dark"] .admin-grid-wrap .admin-grid thead th { background: #2c333d; color: #e6e9ee; border-bottom-color: rgba(255, 255, 255, 0.3); }
  .admin-grid-wrap .admin-grid th.admin-grid-sortable { cursor: pointer; user-select: none; }
  .admin-grid-wrap .admin-grid th .admin-sort-icon { margin-left: 0.3rem; opacity: 0.4; font-size: 0.75em; }
  .admin-grid-wrap .admin-grid th.admin-grid-sortable:hover .admin-sort-icon { opacity: 0.75; }
  .admin-grid-wrap .admin-grid th.sorted .admin-sort-icon { opacity: 1; }
  .admin-grid-wrap .admin-grid th.admin-grid-sortable:hover,
  .admin-grid-wrap .admin-grid th.sorted { color: var(--ink); }
  .admin-grid-wrap .admin-grid tbody tr:nth-child(even) td { background: var(--bg); }
  .admin-grid-wrap .admin-grid .icon-btn { padding: 0 0.15rem; vertical-align: middle; }
  /* Admin View Columns button + picker (Wendy, 2026-10-02). */
  .admin-grid-toolbar { display: flex; justify-content: flex-end; gap: 0.5rem; margin-bottom: 0.5rem; }
  .col-picker-list { max-height: 55vh; overflow-y: auto; border: 1px solid var(--line); border-radius: 10px; }
  .col-picker-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; padding: 0.3rem 0.6rem; border-bottom: 1px solid var(--line); font-size: 0.88rem; }
  .col-picker-row:last-child { border-bottom: none; }
  .col-picker-row label { display: flex; align-items: center; gap: 0.45rem; cursor: pointer; flex: 1; }
  .col-picker-row.col-picker-off label { color: var(--ink-soft); }
  .col-picker-moves { display: flex; gap: 0.15rem; }
  .col-picker-moves .icon-btn { padding: 0.1rem 0.35rem; font-size: 0.7rem; }
  .col-picker-moves .icon-btn:disabled { opacity: 0.3; cursor: default; }
  /* Bounce-back Code/Date cells match the blue Bounce-back pill when filled. */
  .admin-grid-wrap .admin-grid tbody tr td.admin-bb-cell { background: #e3ecfb; color: #1d4a8f; font-weight: 700; }
  html[data-theme="dark"] .admin-grid-wrap .admin-grid tbody tr td.admin-bb-cell { background: #23395e; color: #cfe0ff; }
  /* Bounce-back Vacation # link: keeps the cell's blue, underlined (rule 9a). */
  .admin-grid-wrap .admin-grid td.admin-bb-cell a.apptBbVacationLink { color: inherit; text-decoration: underline; }
  /* Vacation Detail > Assignment: link to the bounce-back tour (rule 9a). */
  #vBounceBackTour .v-bb-appt-link { color: var(--accent); font-weight: 600; text-decoration: underline; }
  /* The row you just came back from (Exit Appointment Detail). Needs the
     grid's own specificity to beat the striped rows above. */
  .admin-grid-wrap .admin-grid tbody tr.row-flash td { background: #dbeafe; color: #16233a; }
  .appt-cio-line { margin-top: 0.15rem; }
  .appt-cio { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; font-weight: 600; }
  .appt-cio-out { color: var(--ink-soft); }
  .appt-cio-in { color: #c9820a; }
  .appt-cio-none { color: var(--ink-soft); font-weight: 500; }
  .appt-wave-row.selected .appt-wave-status { background: rgba(255,255,255,0.25); color: #fff; }

  /* Advanced Filters panel: collapsed by default under a table's quick
     search/status row, expanded with the "More Filters" toggle. */
  .filters-panel {
    display: none; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 0.7rem;
    /* A light grey tint (not flat var(--surface), which is the same white as
       the page behind it and made the expanded panel invisible — Wendy,
       2026-08-17) blended from --line so it still adapts in dark mode. */
    background: color-mix(in srgb, var(--line) 12%, var(--surface));
    border: 1px solid var(--line); border-radius: 14px;
    padding: 1rem 1.1rem; margin-bottom: 0.9rem;
  }
  .filters-panel.show { display: grid; }
  /* Popover variant (Wendy, 2026-08-18): floats over the page instead of
     pushing the table down, so the list underneath stays full-height. Needs
     its own anchor with position:relative — see #vacationsActionsRow. */
  .filters-popover {
    position: absolute; top: 100%; left: 0; margin-top: 0.5rem; z-index: 150;
    max-height: 70vh; overflow-y: auto; width: 820px; max-width: calc(100vw - 3rem);
    box-shadow: 0 16px 44px rgba(20, 35, 42, 0.22);
    /* Fully opaque, not the ~91%-opaque color-mix .filters-panel normally
       uses -- fine for the non-floating variant (nothing behind it but page
       background), but this variant floats OVER real content, and that
       ~9% bleed-through is very visible against anything bold sitting close
       behind it (Wendy, 2026-09-08, caught behind the Analytics bar chart). */
    background: var(--surface);
  }
  .filters-panel label { display: block; font-size: 0.78rem; font-weight: 600; color: var(--ink-soft); margin-bottom: 0.25rem; }

  /* Actions popover: a home for "special" list-level buttons (Text Blast
     today, more later) so they don't keep piling up in the header row
     (Wendy, 2026-08-18). Anchored to its own small wrapper around just the
     Actions button, not the whole filters row, so it opens directly under
     the button instead of sliding over to line up with the search box. */
  .actions-popover {
    display: none; position: absolute; top: 100%; right: 0; margin-top: 0.5rem; z-index: 150;
    background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
    box-shadow: 0 16px 44px rgba(20, 35, 42, 0.22); padding: 0.7rem; min-width: 220px;
    flex-direction: column; gap: 0.5rem;
  }
  .actions-popover.show { display: flex; }
  .filters-panel .filter-field { min-width: 0; }
  /* :not([type="checkbox"]) (Wendy, 2026-09-08): this used to also stretch
     the checkbox inputs inside a checkbox-list filter (Marketing Center/
     Package/Template) to full row width, squeezing each label's text into a
     sliver of leftover space at the far edge instead of sitting naturally
     beside a normal-sized checkbox. */
  .filters-panel .filter-field input:not([type="checkbox"]), .filters-panel .filter-field select { width: 100%; box-sizing: border-box; }
  .filters-panel-actions { grid-column: 1 / -1; display: flex; justify-content: flex-end; gap: 0.6rem; margin-top: 0.2rem; }

  /* Single-column filters variant (Wendy, 2026-08-21): stack every filter
     group vertically like the old sidebar, instead of the default multi-column
     auto-fit grid that looked cramped/messy in a dropdown popover. */
  .filters-panel.filters-panel-1col { grid-template-columns: 1fr; }
  .filters-panel.filters-panel-1col .filters-panel-actions { justify-content: flex-end; }
  /* Two-column filters variant: left = dropdowns/categorical, right = date
     ranges (From/To paired on one row instead of four stacked blocks). */
  .filters-panel.filters-panel-2col { grid-template-columns: 1fr 1fr; align-items: start; }
  /* Sidebar filters variant -- Vacations only (Wendy, 2026-09-20): that list
     has far more filter fields than any other list in the app, so its
     popover is a deliberate one-off exception to the standard single-column
     convention. Modeled on macOS System Settings (Wendy, 2026-09-20: "that's
     how I want it, the way Apple would do it") -- a fixed-size window with a
     category list down the left and just ONE category's fields shown at a
     time on the right, instead of a card grid or multi-column layout.
     Two earlier approaches both fought the browser instead of working with
     it: a CSS grid left dead space under short cards next to a tall one, and
     CSS columns (masonry) silently added an extra column off the right edge
     any time content was taller than the box, which read as "it keeps
     moving." Showing one category at a time in a single ordinary column
     sidesteps both failure modes entirely -- there's nothing left that can
     reflow sideways or resize the window.
     Fixed/centered, not anchored under the Filters button -- the button sits
     close to the right edge of the header row, and the old
     position:absolute/left:0 anchor let a wide panel run past the viewport
     edge instead of adjusting. A fixed, centered window with its own scrim
     (#vacationsFiltersScrim) never depends on the button's position. */
  .filters-modal-scrim {
    display: none; position: fixed; inset: 0; background: rgba(15, 20, 25, 0.45); z-index: 399;
  }
  .filters-modal-scrim.show { display: block; }
  .filters-panel.filters-panel-grid.show {
    display: flex; flex-direction: column; position: fixed; top: 50%; left: 50%;
    transform: translate(-50%, -50%); margin-top: 0; z-index: 400;
    width: min(94vw, 720px); height: 78vh; padding: 0; overflow: hidden;
  }
  .filters-panel-grid .filters-body { display: flex; flex: 1 1 auto; min-height: 0; }
  .filters-panel-grid .filters-sidebar {
    flex: 0 0 190px; border-right: 1px solid var(--line); overflow-y: auto;
    padding: 0.6rem; display: flex; flex-direction: column; gap: 0.15rem;
  }
  .filters-sidebar-item {
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
    text-align: left; background: none; border: none; border-radius: 8px;
    padding: 0.55rem 0.7rem; font-size: 0.85rem; font-weight: 600; color: var(--ink);
    cursor: pointer;
  }
  .filters-panel-grid .filters-sidebar-item:hover:not(.active) {
    background: color-mix(in srgb, var(--line) 25%, var(--surface)); color: var(--ink);
  }
  .filters-sidebar-item.active { background: var(--accent); color: #fff; }
  .filters-panel-grid .filters-sidebar-item.active:hover { background: var(--accent); color: #fff; }
  .filters-sidebar-count {
    font-size: 0.72rem; font-weight: 700; min-width: 1.2em; text-align: center;
    padding: 0.05rem 0.35rem; border-radius: 999px;
    background: color-mix(in srgb, var(--purple) 30%, var(--surface)); color: var(--ink);
  }
  .filters-sidebar-item.active .filters-sidebar-count { background: rgba(255,255,255,0.3); color: #fff; }
  .filters-sidebar-count:empty { display: none; }
  .filters-panel-grid .filters-sidebar-content {
    flex: 1 1 auto; overflow-y: auto; padding: 0.9rem 1.1rem;
  }
  .filters-category { display: none; }
  .filters-category.active { display: block; }
  /* Slightly squared off, not the app's default 999px pill (Wendy,
     2026-09-20: "the fields are too round") -- scoped to this one panel
     since every other list's filter fields keep the standard pill shape. */
  .filters-panel-grid .filter-field input, .filters-panel-grid .filter-field select {
    border-radius: 8px;
  }
  /* Light-purple highlight on a selected dropdown value (Wendy, 2026-09-20:
     "I want you to highlight it... so they know what they're committing in
     their search") -- toggled by JS (blast.js) whenever a select's value
     isn't its default "All ..." option. */
  .filters-panel-grid .filter-field select.filter-value-active {
    background: color-mix(in srgb, var(--purple) 22%, var(--surface));
    border-color: var(--purple);
  }
  .filters-panel-grid .filters-panel-actions {
    flex: 0 0 auto; margin: 0; padding: 0.7rem 1.1rem;
    border-top: 1px solid var(--line);
  }
  @media (max-width: 620px) {
    .filters-panel.filters-panel-grid.show { width: min(94vw, 460px); height: 84vh; }
    .filters-panel-grid .filters-body { flex-direction: column; }
    .filters-panel-grid .filters-sidebar {
      flex: 0 0 auto; flex-direction: row; flex-wrap: wrap; border-right: none;
      border-bottom: 1px solid var(--line);
    }
  }
  .filters-panel-2col .filters-panel-col { display: flex; flex-direction: column; gap: 0.55rem; }
  .filters-panel-2col .filter-field select { max-width: 260px; }
  .filters-panel-2col .date-quick-range-select { max-width: 260px; }
  /* Unscoped (not just 2col) so Vacations' date/money/count range pairs still
     lay out correctly now that its panel is filters-panel-1col (Wendy,
     2026-09-07) -- these rows look the same regardless of column count. */
  .date-range-inputs { display: flex; align-items: center; gap: 0.5rem; }
  .date-range-inputs input[type="date"], .date-range-inputs input[type="number"] { flex: 1; min-width: 0; max-width: 150px; }
  .date-range-sep { color: var(--ink-soft); font-size: 0.78rem; flex-shrink: 0; }
  /* "Blank only" sits on the same line as the field's own label (right-
     aligned) instead of its own row underneath the inputs — reads as a
     modifier on that field, not a disconnected extra control, and doesn't
     add height to every one of the 12 date filters (Wendy, 2026-08-17:
     the original placement "isn't intuitive" and made the panel too tall). */
  /* Fixed label width so every "Blank only" checkbox lands in the same
     column regardless of how long the field's own label is (Wendy,
     2026-08-18: labels of different lengths pushed each checkbox to a
     different spot, "align these in 1 straight line"). */
  .date-range-field-header { display: flex; justify-content: flex-start; align-items: baseline; gap: 0.6rem; }
  .date-range-field-header > label:first-child { flex: 0 0 170px; }
  /* The native up/down spinner arrows were eating most of a narrow number
     box's width, leaving no room to actually read a 2-digit value (Wendy,
     2026-08-18) — dropped them entirely, not needed for typing a filter
     value, and widened slightly now that they're gone. */
  .nudge-count-input { text-align: center; -moz-appearance: textfield; }
  .nudge-count-input::-webkit-inner-spin-button, .nudge-count-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
  /* Found the real bug: .filters-panel-2col .date-range-inputs
     input[type="number"] below (higher specificity, 3 selectors) was
     silently overriding .nudge-count-input's own flex/max-width the whole
     time — this is why "narrower" never actually took effect no matter how
     small a number I set. Matching that same selector shape here so this
     one actually wins. */
  .date-range-inputs input.nudge-count-input { flex: 0 0 44px; max-width: 44px; padding: 0.4rem 0.3rem; }
  /* Wider sibling of nudge-count-input for dollar amounts, which won't
     reliably stay 2 digits (Wendy, 2026-08-18: new Amounts & Counts group). */
  .money-range-input { text-align: center; -moz-appearance: textfield; }
  .money-range-input::-webkit-inner-spin-button, .money-range-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
  .date-range-inputs input.money-range-input { flex: 0 0 75px; max-width: 75px; padding: 0.4rem 0.4rem; }
  /* Small caps section divider so the panel visually reads as distinct
     groups (Amounts & Counts / Filters / Dates), not one long list. */
  .filters-group-label {
    font-size: 0.68rem; font-weight: 700; letter-spacing: 0.05em;
    color: var(--ink-soft); margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid var(--line);
  }
  .filters-group-label.filters-group-label-first { margin-top: 0; padding-top: 0; border-top: none; }
  .date-range-blank-only { display: flex; align-items: center; gap: 0.3rem; font-size: 0.72rem; color: var(--ink-soft); font-weight: 400; cursor: pointer; white-space: nowrap; }
  .date-range-blank-only input { margin: 0; }

  .saved-searches-row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-bottom: 0.9rem; }
  /* Gold pill, the same --gold as the search icon (Wendy, 2026-09-30: the
     old magenta read as red/negative). Dark text for contrast on gold, in
     both light and dark themes. */
  .saved-search-chip {
    display: inline-flex; align-items: center; gap: 0.4rem; background: var(--gold); border: 1px solid #e6a000;
    border-radius: 999px; padding: 0.35rem 0.4rem 0.35rem 0.85rem; font-size: 0.82rem; font-weight: 600; color: #20242b;
  }
  .saved-search-chip:hover { background: #ffc23d; border-color: #cc8e00; }
  .saved-search-chip .saved-search-apply { cursor: pointer; }
  .saved-search-chip .saved-search-delete {
    background: transparent; border: none; color: #20242b; font-size: 0.85rem; line-height: 1;
    border-radius: 50%; width: 18px; height: 18px; padding: 0; cursor: pointer;
  }
  .saved-search-chip .saved-search-delete:hover { background: var(--danger); color: #fff; }

  /* Pagination controls: a small, unobtrusive bar under each of the four
     main list tables (Customer Contacts, Vacations, Leads, Appointments).
     Right-aligned, same visual weight as filters-panel-actions above. */
  .pagination-controls {
    display: flex; justify-content: flex-end; align-items: center; gap: 0.6rem;
    margin-top: 0.6rem; font-size: 0.82rem; color: var(--ink-soft);
  }
  .pagination-controls .pagination-btn {
    background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
    padding: 0.35rem 0.7rem; font-size: 0.82rem; font-weight: 600; color: var(--ink-soft); cursor: pointer;
  }
  .pagination-controls .pagination-btn:hover:not(:disabled) { color: var(--ink); border-color: var(--ink-soft); }
  .pagination-controls .pagination-btn:disabled { opacity: 0.45; cursor: default; }
  .pagination-controls .pagination-status { font-weight: 600; }
  .pagination-controls .pagination-size-select {
    border: 1px solid var(--line); border-radius: 8px; padding: 0.3rem 0.5rem;
    font-size: 0.82rem; color: var(--ink-soft); background: var(--surface);
  }

  @media (prefers-reduced-motion: reduce) {
    #winToast, #winToast .twinkle-star { animation: none; transition: none; }
  }

  #configWarning { max-width: 480px; margin: 4rem auto; text-align: center; }

  .icon-btn {
    background: transparent; border: none; border-radius: 8px; padding: 0.3rem 0.4rem;
    color: var(--ink-soft); font-size: 0.95rem; line-height: 1; flex-shrink: 0;
  }
  .icon-btn:hover { background: var(--bg); color: var(--ink); }
  .search-clear-btn {
    position: absolute; right: 0.35rem; top: 50%; transform: translateY(-50%);
    width: 1.3rem; height: 1.3rem; border-radius: 50%; border: none; background: var(--line);
    color: var(--ink-soft); font-size: 0.85rem; line-height: 1; display: none;
    align-items: center; justify-content: center; cursor: pointer; padding: 0;
  }
  /* Scoped to .search-clear-wrap so this outweighs the generic
     button:hover:not(:disabled) rule — that one was winning on specificity
     and painting the button's background back to var(--bg), washing the
     "×" out to invisible against its own text color (same bug class as the
     sidebar-toggle hover fix above, different button). */
  .search-clear-wrap .search-clear-btn:hover { background: var(--ink); color: var(--bg); }
  /* Search icon inside a search-first box (Customers, 2026-09-29). Gold =
     text typed but not run yet. The clear "x" moves over to make room. */
  .search-clear-wrap.has-search-go .search-clear-btn { right: 1.9rem; }
  .search-go-btn {
    position: absolute; right: 0.3rem; top: 50%; transform: translateY(-50%);
    width: 1.5rem; height: 1.5rem; border: none; border-radius: 6px; background: transparent;
    color: var(--ink-soft); display: flex; align-items: center; justify-content: center;
    cursor: pointer; padding: 0;
  }
  .search-clear-wrap .search-go-btn:hover:not(:disabled) { background: var(--line); color: var(--ink); }
  .search-go-btn.pending, .search-clear-wrap .search-go-btn.pending:hover:not(:disabled) { color: var(--gold); }
  .dial-link {
    display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
    color: var(--ink-soft); text-decoration: none; margin-right: 4px; border-radius: 8px; padding: 2px;
  }
  .dial-link:hover { background: var(--bg); color: var(--accent); }

  .detail-header { display: flex; justify-content: space-between; align-items: start; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.2rem; }
  /* Customer Detail's Notification, styled as an actual sticky note (Wendy,
     2026-08-24 -- comparing this look against Vacation Detail's plain
     bell+text line before deciding whether to roll it out everywhere). A
     small ::before strip simulates a piece of tape holding it up; kept as a
     normal in-flow element (not floating over the page) so it can't drift
     over other content. */
  .customer-notification-sticky {
    position: relative; display: inline-block; min-width: 200px; max-width: 300px;
    background: #ffd9e3; color: #7a1f38; border-radius: 2px 14px 2px 14px;
    padding: 0.85rem 1.6rem 0.75rem 0.9rem; margin-top: 0.4rem;
    box-shadow: 0 8px 18px rgba(20,20,10,0.18), 0 1px 0 rgba(0,0,0,0.05);
    transform: rotate(-2deg); font-size: 0.85rem;
  }
  .customer-notification-sticky::before {
    content: ""; position: absolute; top: -9px; left: 50%; transform: translateX(-50%) rotate(3deg);
    width: 38px; height: 15px; background: rgba(255,255,255,0.6); border-radius: 2px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
  }
  .customer-notification-edit-btn {
    position: absolute; top: 0.5rem; right: 0.5rem; background: none; border: none; cursor: pointer;
    color: #a04a63; opacity: 0.65; padding: 0.2rem; line-height: 0;
  }
  .customer-notification-edit-btn:hover { opacity: 1; }
  /* Same red the bell icon uses everywhere else (var(--danger)) so the
     sticky visually reads as "the thing that red bell was flagging"
     (Wendy, 2026-08-24). */
  .customer-notification-date-row { display: flex; align-items: center; gap: 0.4rem; color: var(--danger); margin-bottom: 0.3rem; }
  .customer-notification-date { font-weight: 800; }
  .customer-notification-text { line-height: 1.4; word-break: break-word; }
  .id-row { display: flex; align-items: center; gap: 0.3rem; margin-top: 0.2rem; }
  .test-badge {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 9px; font-weight: 700; letter-spacing: 0.04em;
    color: var(--danger); border: 1px solid var(--danger); padding: 1px 6px; border-radius: 4px; margin-left: 8px; vertical-align: middle;
  }
  .owner-ribbon { display: inline-flex; vertical-align: middle; margin-left: 6px; }
  .owner-ribbon-lg svg { width: 26px; height: 26px; }
  /* Membership-card-style panel for Owner ID/Type/Level, shown on Appointment
     Detail only when the customer is an owner (Wendy, 2026-09-23). Gold stays
     constant across light/dark per the theme table, so no dark-mode override
     needed. */
  .owner-info-card {
    margin-top: 0.7rem; padding: 0.7rem 0.9rem; border-radius: 10px; max-width: 260px;
    background: linear-gradient(135deg, #ffe28a, var(--gold) 55%, #e29900);
    box-shadow: 0 2px 6px rgba(0,0,0,0.18);
    display: flex; flex-direction: column; gap: 0.35rem;
  }
  .owner-info-card-row { display: flex; justify-content: space-between; align-items: baseline; gap: 0.6rem; }
  .owner-info-card-label {
    font-size: 0.66rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: #4a3300; opacity: 0.75;
  }
  .owner-info-card-value {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.85rem; font-weight: 700; color: #3a2800; text-align: right;
  }
  .nudge-badge {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 9px; font-weight: 700; letter-spacing: 0.04em;
    color: #fff; background: var(--purple); padding: 1px 6px; border-radius: 4px; margin-left: 8px; vertical-align: middle;
  }
  .blast-badge {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 9px; font-weight: 700; letter-spacing: 0.04em;
    color: #fff; background: var(--sage); padding: 1px 6px; border-radius: 4px; margin-left: 8px; vertical-align: middle;
  }
  .marketing-badge {
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 9px; font-weight: 700; letter-spacing: 0.04em;
    color: #fff; background: var(--rose); padding: 1px 6px; border-radius: 4px; margin-left: 8px; vertical-align: middle;
  }
  /* Plain record count (Vacations/Leads/Appointments) vs. unread-message
     count (SMS/Email) get different colors so "how many" and "needs
     attention" read as visually distinct at a glance. */
  .count-badge, .unread-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 20px; height: 20px; padding: 0 5px; border-radius: 999px;
    font-size: 0.72rem; font-weight: 800; line-height: 1;
  }
  .count-badge { background: var(--sidebar-bg-soft); color: #fff; }
  /* Muted gray variant: a "nothing new, here's the running total" count, e.g.
     SMS History once every message is read (Wendy, 2026-08-21). */
  .count-badge-muted { background: var(--ink-soft); color: #fff; }
  .unread-badge { background: var(--sidebar-bg); color: #fff; }
  /* Apple Messages-style unread indicator for list rows — just a plain dot,
     no number, since a list row only needs to flag "something's unread" */
  .unread-dot {
    display: inline-block; width: 9px; height: 9px; border-radius: 50%;
    background: var(--sidebar-bg); margin-left: 6px; vertical-align: middle;
  }
  /* Notification flag: a customer-level flag shown wherever the customer
     shows up (Vacation Detail's Customer Card today, list views too), same
     red-bell language in both places so it reads as one feature. */
  .notification-bell { color: var(--ink-soft); opacity: 0.45; vertical-align: middle; }
  .notification-bell.active { color: var(--danger); opacity: 1; }
  .notification-toggle-btn {
    display: inline-flex; align-items: center; gap: 0.4rem; background: none;
    border: 1px solid var(--line); border-radius: 999px; padding: 0.3rem 0.8rem;
    font-size: 0.85rem; color: var(--ink-soft); cursor: pointer;
  }
  .notification-toggle-btn:hover { border-color: var(--danger); color: var(--danger); }
  .notification-toggle-btn.active { border-color: var(--danger); color: var(--danger); }

  /* Text Blast spinner: the same gold star used in the win toast
     (.twinkle-star), just bigger and spinning continuously while a blast
     runs, so it reads as the same app instead of a bolted-on component. */
  .blast-spinner-star {
    font-size: 3.6rem; color: var(--gold); display: inline-block; line-height: 1;
    animation: blast-spin 1.1s linear infinite, blast-glow 1.6s ease-in-out infinite;
  }
  @keyframes blast-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
  @keyframes blast-glow {
    0%, 100% { text-shadow: 0 0 6px rgba(255,179,15,0.45); }
    50% { text-shadow: 0 0 26px rgba(255,179,15,0.95); }
  }
  @media (prefers-reduced-motion: reduce) { .blast-spinner-star { animation: none; } }
  /* Same star, small, next to a search-first list's count while the
     database works (Wendy, 2026-09-29). */
  .list-busy-star {
    font-size: 1.5rem; color: var(--gold); display: inline-block; line-height: 1;
    animation: blast-spin 1.1s linear infinite, blast-glow 1.6s ease-in-out infinite;
  }
  .list-busy-star[hidden] { display: none; }
  @media (prefers-reduced-motion: reduce) { .list-busy-star { animation: none; } }

  /* Text Blast message preview: its own small phone frame, dedicated classes
     rather than reusing .phone-mockup — that one only looks right inside
     the Settings editor's two-column flex-stretch layout; borrowing it here
     without that surrounding structure is what rendered broken earlier. */
  .text-blast-phone {
    width: 100%; max-width: 300px; aspect-ratio: 9 / 18.5; margin: 0 auto; background: #1a1a1a;
    border-radius: 30px; padding: 16px 8px 10px; position: relative; box-sizing: border-box;
    display: flex; flex-direction: column;
  }
  .text-blast-phone-notch {
    position: absolute; top: 7px; left: 50%; transform: translateX(-50%);
    width: 54px; height: 12px; background: #1a1a1a; border-radius: 8px; z-index: 1;
  }
  .text-blast-phone-screen {
    background: #fff; border-radius: 18px; padding: 20px 8px 10px;
    flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden;
    display: flex; flex-direction: column; gap: 8px;
  }
  .text-blast-preview-bubble {
    background: #e9e9eb; color: #000; border-radius: 16px; padding: 10px 12px;
    font-size: 0.85rem; line-height: 1.45; white-space: pre-wrap;
    min-width: 0; word-wrap: break-word;
  }
  /* Keeps Cancel/Continue and Back/Send Blast anchored to the bottom of the
     dialog instead of floating right under whatever content happens to be
     short that moment, with dead space left below. */
  #textBlastTemplateForm .draggable-window-body { display: flex; flex-direction: column; min-height: 420px; }
  #textBlastPickPanel, #textBlastPreviewPanel { display: flex; flex-direction: column; flex: 1; }
  #textBlastPickPanel .row, #textBlastPreviewPanel .row { margin-top: auto; padding-top: 0.9rem; }
  /* Same shape as Text Blast above (Wendy, 2026-08-18: the picker step
     shouldn't force a tall window and it shouldn't fight a manual resize
     either) — no explicit height is ever set from JS on this window, so a
     resize the user does by hand (native drag handle) sticks across
     Continue/Back instead of getting silently overwritten. */
  #emailBlastTemplateForm .draggable-window-body { display: flex; flex-direction: column; min-height: 420px; }

  /* Bounce-Back wizard: same anchored-footer shape as Text/Email Blast above
     (Wendy, 2026-09-22). #bounceBackOriginSearch isn't inside .content, so
     it needs its own copy of the app's pill-input look rather than falling
     back to a plain browser input. */
  #bounceBackWizardForm .draggable-window-body { display: flex; flex-direction: column; min-height: 420px; }
  #bounceBackPickPanel, #bounceBackConfirmPanel { display: flex; flex-direction: column; flex: 1; }
  #bounceBackPickPanel .row, #bounceBackConfirmPanel .row { margin-top: auto; padding-top: 0.9rem; }

  /* Family Link wizard: same anchored-footer shape as the wizards above
     (Wendy, 2026-09-22) -- buttons stay pinned to the bottom right of the
     dialog even when it's resized taller, instead of floating up under the
     form fields. */
  #familyLinkForm .draggable-window-body { display: flex; flex-direction: column; min-height: 360px; }
  #bounceBackOriginSearch {
    font-family: inherit; font-size: 0.9rem; padding: 0.5rem 0.9rem;
    border-radius: 999px; border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  }
  /* A selected origin row goes solid accent-blue with white text, but the
     "Converts to..." subtitle underneath uses .muted (a soft gray) that
     reads as almost-invisible on that blue background -- brighten it to a
     translucent white instead so it stays legible once picked. */
  .sms-template-pick-item.selected .muted { color: rgba(255, 255, 255, 0.85); }

  .nav-link-btn {
    background: none; border: none; padding: 0.2rem 0; border-radius: 0; color: var(--accent); font-weight: 600; font-size: 0.9rem; vertical-align: middle;
  }
  .nav-link-btn:hover { background: none; border-color: transparent; text-decoration: underline; color: var(--sidebar-bg-soft); }
  #smsMarkReadBtn { text-decoration: underline; }
  .record-position { font-size: 0.82rem; font-weight: 600; color: var(--ink-soft); align-self: center; }
  /* Same nav header shape on every Settings table's edit form as Customer
     Contacts — ID + Previous/Next/Exit — so switching tables never feels
     like a different app. */
  .record-nav-row {
    display: flex; justify-content: space-between; align-items: center;
    flex-wrap: wrap; gap: 0.6rem; margin-bottom: 0.9rem; padding-bottom: 0.8rem;
    border-bottom: 1px solid var(--line);
  }
  .record-nav-controls { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; }

  .opt-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .opt-group { position: relative; }
  .opt-btn {
    font-size: 0.82rem; padding: 0.5rem 0.9rem 0.5rem 0.7rem; background: var(--bg); border: 1px solid var(--line);
    display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 600; color: var(--ink);
  }
  .opt-btn:hover { background: var(--line); border-color: var(--accent); }
  .opt-btn svg { flex-shrink: 0; color: var(--ink-soft); }
  .opt-dropdown {
    display: none; position: absolute; top: calc(100% + 0.5rem); left: 0; z-index: 20;
    background: var(--surface); border: 1px solid var(--line); border-radius: 14px; box-shadow: 0 12px 30px rgba(20,25,30,0.18);
    padding: 0.5rem; min-width: 200px; flex-direction: column; gap: 0.3rem;
  }
  .opt-dropdown.show { display: flex; }
  .dropdown-item {
    display: block; width: 100%; text-align: left; border: none; border-radius: 8px;
    background: transparent; padding: 0.55rem 0.7rem; font-size: 0.85rem;
  }
  .dropdown-item:hover { background: var(--bg); }
  .dropdown-item.danger-item { color: var(--danger); }
  .nav-link-btn.danger-item { color: var(--danger); }
  .nav-link-btn.danger-item:hover { color: var(--danger); opacity: 0.75; }

  .field-with-actions { display: flex; align-items: center; gap: 0.3rem; }
  /* width:auto is needed alongside flex-grow, or the input keeps the
     browser's native ~20-character size instead of claiming the row's full
     available width like every other field's input (Wendy, 2026-09-20) --
     same fix the read-mode-only rule below already had for its case. */
  .field-with-actions input { flex: 1 1 auto; width: auto; min-width: 0; }
  /* Click-to-dial is a read-mode convenience; while editing the phone it's
     just clutter (Wendy, 2026-10-06), so hide it whenever the panel is not
     in read mode. Read mode keeps it so staff can still tap to call. */
  #customerTabPanel:not(.read-mode) #dialPhoneBtn { display: none; }
  .dnc-popover {
    display: none; position: absolute; z-index: 20; margin-top: 0.4rem;
    background: var(--surface); border: 1px solid var(--line); border-radius: 14px; box-shadow: 0 12px 30px rgba(20,25,30,0.18);
    padding: 0.7rem; min-width: 220px;
  }
  .dnc-popover.show { display: block; }
  .dnc-popover select { width: 100%; margin-bottom: 0.5rem; }
  .field { position: relative; }

  /* Self-service combo box — always shows every option regardless of what's
     typed (native <datalist> filters to matching text, which is confusing
     when you're just trying to see the full list). Free typing still works
     for a brand-new value not in the list yet. */
  .combo-box { position: relative; display: flex; align-items: center; }
  .combo-box input { flex: 1; padding-right: 1.8rem !important; }
  /* .short-combo-toggle shares this look but is wired separately below (via
     wireShortValueComboToggle/populateShortValueComboMenu) -- these fields
     (Vacation Pkg Code, TM Code, FTD Sent By) need to store/display a short
     value (a code or username) while showing a fuller "value — name" label
     in the menu, which the generic valueListValuesByField combo-box system
     doesn't support (it assumes the label and stored value are the same). */
  .combo-box-toggle, .short-combo-toggle {
    position: absolute; right: 0.4rem; background: none; border: none;
    color: var(--ink-soft); cursor: pointer; padding: 0.2rem; line-height: 0; font-size: 0.7rem;
  }
  .combo-box-menu {
    display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 25;
    background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
    margin-top: 0.3rem; max-height: 220px; overflow-y: auto; box-shadow: 0 12px 30px rgba(20,25,30,0.18);
  }
  .combo-box-menu.show { display: block; }
  .combo-box-menu-item { padding: 0.5rem 0.8rem; cursor: pointer; font-size: 0.85rem; }
  .combo-box-menu-item:hover { background: var(--bg); }
  .combo-box-menu-item.muted-item { color: var(--ink-soft); cursor: default; }
  .combo-box-menu-item.blank-item { color: var(--ink-soft); font-style: italic; border-bottom: 1px solid var(--line); }

  .emoji-picker-wrap { position: relative; display: inline-block; }
  .emoji-picker-menu {
    display: none; position: absolute; top: 100%; left: 0; z-index: 25;
    grid-template-columns: repeat(6, 1fr); gap: 0.15rem; width: 200px;
    background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
    margin-top: 0.3rem; padding: 0.4rem; box-shadow: 0 12px 30px rgba(20,25,30,0.18);
  }
  .emoji-picker-menu.show { display: grid; }
  .emoji-picker-menu button { padding: 0.3rem; font-size: 1.15rem; line-height: 1; border: none; background: none; border-radius: 6px; }
  .emoji-picker-menu button:hover { background: var(--bg); }

  .related-group { padding-top: 0.9rem; margin-top: 0.9rem; border-top: 1px solid var(--line); }
  .related-group:first-of-type { padding-top: 0; margin-top: 0.4rem; border-top: none; }
  .related-label { font-size: 0.72rem; letter-spacing: 0.04em; color: var(--ink-soft); font-weight: 700; margin-bottom: 0.4rem; }
  .new-record-btn { font-size: 0.82rem; padding: 0.45rem 0.9rem; margin-bottom: 0.7rem; }
  /* SMS History's "All numbers" filter -- kept as a plain native select
     (Wendy, 2026-09-20: moved the Mark Read/Unread buttons to the opposite
     side of the row instead of chasing an exact height match against them). */
  .sms-number-filter {
    font-family: inherit; font-size: 0.82rem; padding: 0.45rem 0.9rem; border-radius: 999px;
    border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  }
  /* Uniform width for the "+ New ___" buttons sitting top-right in a details
     summary row (Vacations/Appointments/Email History/SMS History) -- widths
     varied with label length until Wendy asked for them to match (2026-08-27). */
  .section-header-new-btn { margin-left: auto; margin-bottom: 0; min-width: 160px; text-align: center; }
  /* Hover "i" next to a Settings panel title -- replaces the description
     paragraph that used to sit under the title (Wendy, 2026-08-22). */
  .settings-info-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 17px; height: 17px; border-radius: 50%; flex: 0 0 auto;
    border: 1.5px solid var(--ink-soft); color: var(--ink-soft);
    font-size: 0.72rem; font-weight: 700; font-style: normal; font-family: inherit;
    line-height: 1; cursor: help;
  }
  .settings-info-icon:hover { border-color: var(--accent); color: var(--accent); }
  .quick-add-form {
    display: none; background: var(--bg); border: 1px solid var(--line); border-radius: 14px;
    padding: 0.9rem; margin-bottom: 0.9rem;
  }
  .quick-add-form.show { display: block; }

  /* Keeps a form's Save/Cancel row in view while scrolling a long form
     instead of burying it at the true bottom (Wendy 2026-09-16). The dirty
     "lights up" enable/disable styling on the Save button itself is
     untouched, this just keeps it visible. */
  .form-save-row {
    position: sticky; bottom: 0; background: var(--bg);
    padding-top: 0.6rem; padding-bottom: 0.6rem;
    border-top: 1px solid var(--line); z-index: 3;
  }

  /* Offers Pricelist edit form grouped into titled cards (Wendy, 2026-10-06):
     "separate the fields by cards that are related, the way Apple would do it."
     Soft-bordered surface panels, always open (not collapsible), a quiet
     uppercase title per group — the calm grouped look of Apple's Settings. Only
     the markup is re-housed; every field id and the save/dirty logic are
     unchanged, and the sticky .form-save-row stays a direct child of the form. */
  /* Cards flow two-across so fields don't stretch too wide (Wendy, 2026-10-06);
     collapses to one column on narrow screens. align-items:stretch makes each
     card match the height of the one beside it, so a row of two cards reads as
     one even block (Wendy, 2026-10-06). */
  .op-card-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    align-items: stretch;
    margin-top: 0.4rem;
  }
  @media (max-width: 760px) { .op-card-grid { grid-template-columns: 1fr; } }
  .op-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 1rem 1.15rem 1.15rem;
    margin-top: 1rem;
  }
  .op-card-grid .op-card { margin-top: 0; }
  .op-card:first-of-type { margin-top: 0.4rem; }
  /* Text boxes here stay a fixed size -- no drag-to-resize handle (Wendy,
     2026-10-06), so the cards keep their shape. */
  .op-card textarea { resize: none; }
  /* The three Description boxes read as one tidy set, so lock them to the same
     height regardless of how much text each holds (Wendy, 2026-10-06). The AI
     Knowledge box keeps its own taller size -- this targets the three by id. */
  /* Internal + Guest's descriptions are the tall boxes; Quote's "Includes" is a
     short one-liner at half their height (Wendy, 2026-10-06). */
  #opDescriptionInternal, #opDescriptionPublic {
    box-sizing: border-box;
    height: 8.5em;
    min-height: 8.5em;
    max-height: 8.5em;
  }
  #opDescriptionQuoteIncludes {
    box-sizing: border-box;
    height: 3em;
    min-height: 3em;
    max-height: 3em;
  }
  /* Card titles match the app's panel headings (.overview-panel-title) --
     normal case, bold, --ink -- instead of the tiny grey all-caps label they
     were, so the cards read like the rest of the app (Wendy, 2026-10-06). */
  .op-card-title {
    margin: 0 0 0.9rem;
    font-size: 0.95rem;
    font-weight: 800;
    color: var(--ink);
  }
  /* Offer Basics card: title on the left, Active slider pinned top-right on the
     same line (Wendy, 2026-10-06). */
  .op-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;
  }
  .op-card-head .op-card-title { margin-bottom: 0.9rem; }
  .op-card-head-active { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.9rem; }
  .op-card-head-active label { margin: 0; font-size: 0.8rem; color: var(--ink-soft); }

  /* Grey "not committed yet" treatment — nothing here is a real record until
     Save is clicked, so the zone itself should look provisional/draft. */
  .quick-add-form.draft-zone {
    background: rgba(120, 130, 140, 0.14);
    border: 1px dashed var(--ink-soft);
  }

  /* Fields on the left, phone preview on the right — the mockup no longer
     stacks below the whole form, so it doesn't eat vertical page space. */
  .template-editor-layout { display: flex; gap: 1.6rem; align-items: flex-start; }
  .template-editor-fields { flex: 1; min-width: 0; }
  .template-editor-preview { flex-shrink: 0; }

  /* Message body shaped like an actual phone, per Wendy's request — real
     iPhone proportions (roughly 9:19.5, not a squarish box), plus a notch,
     around a "screen" holding the image preview (if any) and an iMessage-gray
     text bubble, instead of a plain wide textarea. */
  .phone-mockup {
    width: 260px; margin: 0; background: #1a1a1a; border-radius: 44px;
    padding: 16px 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.25);
  }
  .phone-mockup-screen {
    background: #fff; border-radius: 30px; padding: 30px 10px 14px; min-height: 520px;
    display: flex; flex-direction: column; gap: 10px; position: relative;
  }
  .phone-mockup-notch {
    position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
    width: 76px; height: 18px; background: #1a1a1a; border-radius: 10px;
  }
  .phone-mockup-screen img#stImagePreview, .phone-mockup-screen img.phone-mockup-preview-image {
    width: 100%; border-radius: 14px; object-fit: cover; max-height: 200px; flex-shrink: 0;
  }
  .phone-mockup-screen textarea {
    flex: 1; width: 100%; border: none; outline: none; resize: none;
    font-family: inherit; font-size: 0.95rem; padding: 10px 12px; line-height: 1.4;
    background: #e9e9eb; border-radius: 18px; color: #000;
  }
  /* Vacation Detail's compose panel starts smaller than the Settings editor's
     full 260px/520px phone (this window opens compact), but since the whole
     window is resizable (Wendy's request — wordy templates need more room),
     the phone grows with it instead of staying a fixed size. */
  #vSmsComposeForm .phone-mockup { width: 225px; padding: 12px 8px; border-radius: 34px; flex: 1; display: flex; flex-direction: column; min-height: 0; }
  #vSmsComposeForm .phone-mockup-screen { border-radius: 22px; padding: 22px 8px 10px; flex: 1; min-height: 0; }
  #vSmsComposeForm .phone-mockup-screen textarea { font-size: 0.85rem; }

  /* SMS History on Vacation Detail reads as a real conversation thread —
     inbound (customer) bubbles on the left, outbound (staff) bubbles on the
     right, same iMessage-style visual language as the compose phone-mockup. */
  .sms-thread { display: flex; flex-direction: column; gap: 0.7rem; background: #f0f0f2; border-radius: 14px; padding: 1rem; max-height: 420px; overflow-y: auto; margin-top: 0.8rem; }
  /* Inside the pop-out chat window (Wendy, 2026-09-09), the outer
     #vSmsPopoutThread already scrolls at the window's own height -- the
     inline panel's fixed 420px would either waste space in a short window
     or double-scroll in a tall one. */
  #vSmsPopoutThread .sms-thread { max-height: none; height: 100%; margin-top: 0; box-sizing: border-box; }
  .sms-thread-row { display: flex; flex-direction: column; max-width: 75%; }
  .sms-thread-row.sms-thread-outbound { align-self: flex-end; align-items: flex-end; }
  .sms-thread-row.sms-thread-inbound { align-self: flex-start; align-items: flex-start; }
  .sms-thread-bubble { padding: 0.55rem 0.85rem; border-radius: 16px; font-size: 0.88rem; line-height: 1.4; white-space: pre-wrap; }
  .sms-thread-outbound .sms-thread-bubble { background: var(--accent); color: #fff; }
  .sms-thread-inbound .sms-thread-bubble { background: #e5e5ea; color: #000; }
  .sms-thread-meta { font-size: 0.68rem; color: var(--ink-soft); margin-top: 0.25rem; }
  #vSmsComposeForm .template-editor-layout { flex: 1; align-items: stretch; min-height: 0; }
  #vSmsComposeForm .template-editor-preview { display: flex; flex-direction: column; }
  #vSmsComposeForm .phone-mockup-notch { width: 58px; height: 14px; }
  /* Vacation Detail's SMS compose: a clickable list standing in for the plain
     <select> template picker elsewhere, so choosing a template reads as
     "pick from a list," not "pick from a dropdown." */
  .sms-template-pick-list { border: 1px solid var(--line); border-radius: 10px; max-height: 340px; overflow-y: auto; }
  .sms-template-pick-item { padding: 0.6rem 0.9rem; cursor: pointer; font-size: 0.88rem; border-bottom: 1px solid var(--line); }
  .sms-template-pick-item:last-child { border-bottom: none; }
  .sms-template-pick-item:hover { background: var(--bg); }
  .sms-template-pick-item.muted-item { color: var(--ink-soft); font-style: italic; }
  .sms-template-pick-item.selected { background: var(--accent); color: #fff; font-style: normal; }
  /* Note Templates picker (shared across Customer/Vacation/Appointment notes) */
  .note-template-list { border: 1px solid var(--line); border-radius: 10px; max-height: 260px; overflow-y: auto; }
  .note-template-list:empty { display: none; }
  .note-template-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.55rem 0.8rem; cursor: pointer; border-bottom: 1px solid var(--line); }
  .note-template-row:last-child { border-bottom: none; }
  .note-template-row:hover { background: var(--bg); }
  .note-template-main { flex: 1 1 auto; min-width: 0; }
  .note-template-main b { display: block; font-size: 0.86rem; }
  .note-template-preview { font-size: 0.78rem; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .note-template-delete { flex: 0 0 auto; color: var(--ink-soft); font-size: 0.9rem; line-height: 1; }
  .note-template-delete:hover { color: var(--danger); }
  .note-template-empty { padding: 0.9rem; text-align: center; color: var(--ink-soft); font-style: italic; font-size: 0.84rem; }
  /* Underline tabs, not pills (Wendy 2026-08-21). button.-scoped hover so it beats
     the generic button:hover:not(:disabled) rule instead of getting a filled bg. */
  .note-template-mode-toggle { display: flex; gap: 1.4rem; border-bottom: 1px solid var(--line); }
  button.ntm-seg { border: none; background: none; border-radius: 0; padding: 0.45rem 0.15rem; margin-bottom: -1px; font-size: 0.85rem; cursor: pointer; color: var(--ink-soft); border-bottom: 2px solid transparent; }
  button.ntm-seg:hover:not(:disabled) { background: none; border-color: transparent; color: var(--ink); }
  button.ntm-seg.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
  button.ntm-seg.active:hover:not(:disabled) { color: var(--accent); border-bottom-color: var(--accent); }
  /* Create panel grows to fill the window body so Save Template (margin-top:auto)
     pins to the true bottom-left, not the bottom of a fixed-height box. */
  #noteTemplateCreatePanel { display: flex; flex-direction: column; flex: 1 1 auto; }
  /* The "jump left to right" cue Wendy asked for — a brief pop/slide as the
     merged text lands in the phone bubble, not a literal flying DOM element
     (fragile) — just enough motion to read as "that just arrived here." */
  @keyframes smsPreviewLand { from { opacity: 0.25; transform: translateX(16px); } to { opacity: 1; transform: translateX(0); } }
  .sms-preview-land { animation: smsPreviewLand 0.35s ease; }

  .section-divider {
    display: flex; align-items: center; gap: 0.6rem; margin: 1.4rem 0 0.9rem;
    font-size: 0.72rem; letter-spacing: 0.05em; color: var(--ink-soft); font-weight: 700;
  }
  .section-divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }

  .tab-row { display: flex; gap: 0.4rem; border-bottom: 1px solid var(--line); margin-bottom: 1.1rem; }
  /* Appointment Detail tabs sit directly on top of their card -- no separator
     line, minimal gap, so the tab labels read as belonging to the card below
     rather than a floating strip (Wendy, 2026-08-22). */
  #apptDetailTabsContainer { border-bottom: none; margin-bottom: 0.4rem; }
  .tab-btn {
    border: none; border-radius: 0; background: transparent; padding: 0.6rem 0.2rem; margin-right: 1.2rem;
    font-size: 0.9rem; font-weight: 600; color: var(--ink-soft); border-bottom: 2px solid transparent;
  }
  .tab-btn:hover { background: transparent; border-color: transparent; border-bottom-color: var(--line); color: var(--ink); }
  .tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
  .tab-btn.active:hover { border-bottom-color: var(--accent); }
  /* Vacation Detail's RRF/Quotes/Payment/Hotel/Entertainment strip specifically
     wants more breathing room than the app-wide tab-btn default. */
  #vacationSubTabsContainer .tab-btn { margin-right: 2.1rem; }
  .tab-count-badge {
    display: inline-block; min-width: 1.3rem; padding: 0.05rem 0.4rem; margin-left: 0.4rem;
    border-radius: 999px; background: var(--line); color: var(--ink-soft);
    font-size: 0.72rem; font-weight: 700; text-align: center; vertical-align: 1px;
  }
  .tab-btn.active .tab-count-badge { background: var(--accent); color: #fff; }
  /* Unread-RRF alert state: purple badge wins over active/inactive styling
     (Wendy, 2026-09-06 -- an unread RRF must be visible from any tab; color
     switched from green to purple 2026-09-09 to match the Overview "New Date
     Requests" card and the Vacations list dot -- same signal, one color). */
  .tab-count-badge.attn, .tab-btn.active .tab-count-badge.attn { background: var(--purple); color: #fff; }
  /* Overview cards that navigate somewhere read as links (Wendy, 2026-09-06:
     "it doesn't look clickable"). */
  /* Clickable = the NUMBER is the link (Wendy, 2026-09-06), underlined in
     the accent, label stays quiet. Hover: a breath of lift, nothing loud. */
  /* grab, not pointer -- every card (clickable or not) is also a drag
     source now, and this rule's specificity otherwise wins over the plain
     .stat-card cursor:grab rule since it's defined later (Wendy, 2026-09-07:
     "notifications... only shows a finger"). A plain click still works
     fine regardless of cursor icon -- dragging only kicks in past a 5px
     move threshold. */
  .stat-card-clickable { cursor: grab; transition: border-color 0.15s ease, box-shadow 0.15s ease; }
  /* Regular weight, not the base .stat-num's 600 (Wendy, 2026-09-07: "don't
     make the number to click bold, keep it Regular") -- the color + underline
     alone are enough to read as clickable. */
  .stat-card-clickable .stat-num { font-weight: 400; text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 2px; }
  /* Each clickable number picks up its own card's rail color (Wendy,
     2026-09-07: "the number they click should match the card's left side
     bar") instead of one blue for every card. Notifications' rail is the
     pale pastel pink used elsewhere for the sticky-note look -- too washed
     out to read as text, so its number uses a deeper magenta-pink instead
     of the literal rail hex (Wendy: "too pinky and might get drowned out"). */
  .stat-card-clickable .stat-num.rail-accent { color: var(--accent); text-decoration-color: rgba(84, 101, 255, 0.45); }
  .stat-card-clickable .stat-num.rail-gold { color: var(--gold); text-decoration-color: rgba(255, 179, 15, 0.45); }
  .stat-card-clickable .stat-num.rail-sage { color: var(--sage); text-decoration-color: rgba(155, 193, 188, 0.6); }
  .stat-card-clickable .stat-num.rail-rose { color: var(--rose); text-decoration-color: rgba(217, 85, 110, 0.45); }
  .stat-card-clickable .stat-num.rail-orange { color: var(--orange); text-decoration-color: rgba(242, 121, 60, 0.45); }
  .stat-card-clickable .stat-num.rail-pink { color: #db4d75; text-decoration-color: rgba(219, 77, 117, 0.45); }
  .stat-card-clickable:hover { border-color: var(--accent); box-shadow: 0 4px 16px rgba(84, 101, 255, 0.12); }
  .stat-card-clickable:hover .stat-num.rail-accent { text-decoration-color: var(--accent); }
  .stat-card-clickable:hover .stat-num.rail-gold { text-decoration-color: var(--gold); }
  .stat-card-clickable:hover .stat-num.rail-sage { text-decoration-color: var(--sage); }
  .stat-card-clickable:hover .stat-num.rail-rose { text-decoration-color: var(--rose); }
  .stat-card-clickable:hover .stat-num.rail-orange { text-decoration-color: var(--orange); }
  .stat-card-clickable:hover .stat-num.rail-pink { text-decoration-color: #db4d75; }
  .rrf-unread-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #1a9e5c; margin-right: 5px; }
  .tab-panel { display: none; }
  .tab-panel.active { display: block; }
  #apptCustContactPanel.active { display: flex; flex-direction: column; flex: 1; }
  /* Appointment Detail's tabs vary a lot in content (5 role-field rows on
     Tour vs. one field on Gifting) -- without a shared floor, the whole
     card visibly resized every time you switched tabs (Wendy, 2026-08-20:
     "stop... make it all the same size as the Tour tab"). Only one panel
     is ever display:block at a time, so giving all of them the same
     min-height keeps the card's size constant regardless of which is active. */
  #apptTourPanel, #apptSalePanel, #apptGiftingPanel, #apptNotesPanel, #apptLogPanel { min-height: 480px; }
  /* Same fix, same reason, on Vendor Detail (Wendy, 2026-09-18: "the activity
     log keeps moving every time I click on a tab. It needs to stay put") --
     the Activity Log sits right below these tabs, so giving them all the
     same floor keeps its position constant across tabs. */
  #vendorNotesPanel, #vendorAttachmentsPanel, #vendorEmailHistoryPanel, #vendorLocationsPanel, #vendorDealsPanel, #vendorLinksPanel { min-height: 480px; }
  #ownerTabLabel.is-owner {
    background: var(--gold); color: #4a2f00; padding: 0.15rem 0.65rem; border-radius: 999px; font-weight: 700;
  }
  /* Gray Card (Wendy, 2026-10-07): tab label goes gray for members, and the
     member fields only show once "Gray Card Member?" is Yes. */
  #grayCardTabLabel.is-gc-member {
    background: #8a9099; color: #ffffff; padding: 0.15rem 0.65rem; border-radius: 999px; font-weight: 700;
  }
  #grayCardTabPanel:not(.is-gc-member) .gc-member-only { display: none; }
  .gray-card-mark { display: inline-flex; vertical-align: middle; margin-left: 6px; }

  #customerTabPanel.read-mode .row,
  #demographicsTabPanel.read-mode .row,
  #ownerTabPanel.read-mode .row,
  #grayCardTabPanel.read-mode .row { margin-top: 0.6rem !important; }
  #customerTabPanel.read-mode .field,
  #demographicsTabPanel.read-mode .field,
  #ownerTabPanel.read-mode .field,
  #grayCardTabPanel.read-mode .field { gap: 0.1rem; }
  #customerTabPanel.read-mode input, #customerTabPanel.read-mode select,
  #demographicsTabPanel.read-mode input, #demographicsTabPanel.read-mode select,
  #ownerTabPanel.read-mode input, #ownerTabPanel.read-mode select,
  #grayCardTabPanel.read-mode input, #grayCardTabPanel.read-mode select {
    border-color: transparent; background: transparent; padding: 0.1rem 0; color: var(--ink-strong); height: auto; appearance: none;
  }
  /* The dropdown-arrow toggle is inert in read-mode (opening a picker to edit
     a field you can't edit yet makes no sense) but was still rendering as a
     faint, confusing dot right after the value. Edit mode keeps it. */
  #customerTabPanel.read-mode .combo-box-toggle,
  #demographicsTabPanel.read-mode .combo-box-toggle,
  #ownerTabPanel.read-mode .combo-box-toggle,
  #grayCardTabPanel.read-mode .combo-box-toggle { display: none; }
  #cFirstName, #cLastName { font-weight: 700; font-size: 1.05rem; }
  /* A readOnly/disabled field is still focusable by default, so clicking one
     was showing the browser's native blue focus ring — making a locked field
     look like it just became a live edit box. Suppressed only in read-mode;
     edit mode keeps the focus ring since it's genuinely editable there. */
  #customerTabPanel.read-mode input:focus, #customerTabPanel.read-mode select:focus,
  #demographicsTabPanel.read-mode input:focus, #demographicsTabPanel.read-mode select:focus,
  #ownerTabPanel.read-mode input:focus, #ownerTabPanel.read-mode select:focus,
  #grayCardTabPanel.read-mode input:focus, #grayCardTabPanel.read-mode select:focus {
    outline: none; box-shadow: none;
  }
  #customerTabPanel.read-mode .field-with-actions { flex-wrap: nowrap; }
  /* flex-grow:1 lets the value actually claim the row's available width
     instead of sitting at the browser's native ~20-character input size —
     that fixed width was the real cause of long emails looking cut off.
     The ellipsis is just a graceful fallback if it's still too long. */
  #customerTabPanel.read-mode .field-with-actions input {
    flex: 1 1 auto; width: auto; min-width: 40px;
    text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
  }
  #customerTabPanel.read-mode #emailDncGear, #customerTabPanel.read-mode #phoneDncGear { display: none; }
  .icon-btn.dnc-disabled { opacity: 0.35; pointer-events: none; }
  #customerTabPanel.read-mode input::placeholder,
  #demographicsTabPanel.read-mode input::placeholder,
  #ownerTabPanel.read-mode input::placeholder,
  #grayCardTabPanel.read-mode input::placeholder { color: transparent; }
  #customerEditToggle.active { color: var(--accent); }

  /* Admin > Value Lists: controls on the left, current values on the right
     (Wendy 2026-10-02). Stacks on narrow screens. */
  .vl-columns { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1.4rem 2.5rem; }
  .vl-columns .vl-col-controls { flex: 0 1 360px; min-width: 0; }
  .vl-columns .vl-col-values { flex: 1 1 360px; max-width: 520px; min-width: 0; }
  .vl-values-heading { display: block; font-size: 0.75rem; color: var(--ink-soft); letter-spacing: 0.04em; font-weight: 600; margin-bottom: 0.2rem; }
  .vl-value-row.vl-value-new { animation: vl-new-flash 1.8s ease-out; }
  @keyframes vl-new-flash {
    0%, 35% { background: rgba(255, 179, 15, 0.35); }
    100% { background: transparent; }
  }
