/* ============================================================================
   Smart Capture HITL — consolidated app.css
   Single stylesheet referenced by every page (inspector_page.html,
   tasks_page.html, validation_page.html, help_page.html). Replaces the
   per-page CSS files that previously duplicated all the chrome (env-strip,
   nav, user-chip, modal, toast). Sections below:
     1. Tokens + chrome (shared across every page; lifted from inspector-page.css)
     2. Tasks page — canonical body/nav/buttons/modal/toast/animations
     3. Inspector page — form*, combobox, email-list, captured fields tables
     4. Validation page — validation form, crowd-input, line-item editor
     5. Help page — help-header, help-content, back button
   Page-specific class selectors (.filterField, .leftContainer, .help-content,
   .tasks-list etc.) only fire on the page whose markup uses them. Only
   .main-container is body-class scoped (inspector wants full width; others
   keep the tasks-canonical 1600px max-width).
   ============================================================================ */


/* ============================================================
   THEME TOKENS — single source of truth for all colors.
   Light defaults live in :root; dark overrides ONLY the values.
   See docs/superpowers/specs/2026-06-06-theme-switch-design.md
   ============================================================ */
:root {
  /* Tell the browser which palette to paint native UI in (calendar
     picker icon, datetime spinners, scrollbars, <select> popups). Without
     this, native controls stay light-mode and their black glyphs vanish
     on the dark theme. Overridden to `dark` in the dark block below. */
  color-scheme: light;

  /* brand / accent */
  --accent:#007fff; --accent-d:#0064cc; --accent-strong:#1e40af; --accent-deep:#1e3a8a;
  --accent-bg:#eff6ff; --accent-bg-2:#dbeafe; --accent-bg-3:#e6f2ff; --accent-border:#93c5fd;
  --on-accent:#ffffff;            /* text/icons ON a filled accent — stays light in both themes */

  /* neutrals — text */
  --ink-strong:#0d0d0d; --ink:#1f2937; --ink-2:#374151; --ink-3:#4b5563;
  --muted:#6b7280; --muted-2:#9ca3af; --faint:#d1d5db;

  /* neutrals — surfaces & lines */
  --bg:#e6f2ff; --surface:#ffffff; --surface-2:#f9fafb; --surface-3:#f3f4f6; --surface-4:#f0f0f0;
  --line:#e5e7eb; --line-strong:#d1d5db;

  /* slate (used by .page-home rules) */
  --slate:#64748b;

  /* semantics */
  --green:#5cb85c; --green-d:#065f46; --green-bg:#d1fae5; --green-bg-soft:#ecfdf5;
  --amber:#e0a02e; --amber-d:#92400e; --amber-bg:#fef3c7; --amber-bg-soft:#fffbeb;
  --red:#d64545; --red-strong:#dc2626; --red-d:#991b1b; --red-bg:#fee2e2; --red-bg-soft:#fef2f2;
  --violet:#7c5cff; --violet-d:#7c3aed; --indigo:#6366f1;

  /* environment banner — deep shades so the white banner text stays legible
     in BOTH themes (same value in light/dark). Gray base = var(--muted) for
     every other environment. */
  --env-prod:#f800ff;

  /* misc */
  --chart-grid:#9fb0c0;
  --elev:0px 2px 4px -2px #1018280f, 0px 4px 8px -2px #1018281a;
  --elevation-1:var(--elev);      /* fixes the stale undefined reference */
  --radius:12px;
  /* tail tokens */
  --orange:#f84200; --violet-bg:#efeaff; --violet-border:#d7ccff;
  --indigo-d:#4338ca; --indigo-bg:#e0e7ff; --indigo-border:#c7d2fe;
  --code-bg:#1e293b; --code-fg:#f1f5f9; --viewer-frame:#525659;
  /* chart hover tooltip — a dark card with light text in BOTH themes (do not
     use --ink: it inverts to near-white in dark mode → white-on-white). */
  --tooltip-bg:#1f2937; --tooltip-fg:#ffffff; --tooltip-border:transparent;
  --overlay-light:#ffffff4d; --shadow-strong:#10182833;
  /* surface-dependent translucent tokens */
  --skeleton-1:rgba(229,231,235,0.5); --skeleton-2:rgba(229,231,235,0.9); --label-bg:rgba(255,255,255,0.72);
}

:root[data-theme="dark"] {
  color-scheme: dark;     /* native UI (calendar icon, spinners, scrollbars) → dark */
  --accent:#3b9dff; --accent-d:#8cc6ff; --accent-strong:#bcd9ff; --accent-deep:#cfe3ff;
  --accent-bg:#16243b; --accent-bg-2:#1d3354; --accent-bg-3:#13243d; --accent-border:#2f598f;
  --on-accent:#ffffff;

  --ink-strong:#f5f8fc; --ink:#e6edf6; --ink-2:#cbd6e2; --ink-3:#aab6c4;
  --muted:#94a3b8; --muted-2:#74808f; --faint:#5b6675;

  --bg:#0f172a; --surface:#1e293b; --surface-2:#233044; --surface-3:#2a384e; --surface-4:#30405a;
  --line:#334155; --line-strong:#3f5069;

  --slate:#94a3b8;

  --green:#34d399; --green-d:#6ee7b7; --green-bg:#0c2f24; --green-bg-soft:#0e2a22;
  --amber:#fbbf24; --amber-d:#fcd34d; --amber-bg:#3a2c08; --amber-bg-soft:#332810;
  --red:#f87171; --red-strong:#f87171; --red-d:#fca5a5; --red-bg:#3a1414; --red-bg-soft:#341515;
  --violet:#a78bfa; --violet-d:#c4b5fd; --indigo:#818cf8;
  --env-prod:#f800ff;   /* same deep shades in dark — white text stays legible */

  --chart-grid:#3f5069;
  --elev:0 2px 4px -2px #00000066, 0 6px 14px -4px #00000080;
  /* tail tokens (dark) */
  --orange:#ff7a45; --violet-bg:#2a2447; --violet-border:#4a3f7a;
  --indigo-d:#a5b4fc; --indigo-bg:#1e2547; --indigo-border:#38406b;
  --code-bg:#0b1220; --code-fg:#e6edf6; --viewer-frame:#1f2937;
  --tooltip-bg:#0b1220; --tooltip-fg:#f1f5f9; --tooltip-border:#3f5069;  /* near-black card + light text + visible border on the dark dashboard */
  --overlay-light:#ffffff4d; --shadow-strong:#00000080;
  /* surface-dependent translucent tokens (dark) */
  --skeleton-1:rgba(148,163,184,0.10); --skeleton-2:rgba(148,163,184,0.20); --label-bg:rgba(15,23,42,0.72);
}

/* Environment banner colors. Prod = green, Sandbox = blue; every other
   environment keeps the gray base (.env-strip background: var(--muted)). */
.env-strip.env-prod { background: var(--env-prod); }

.env-strip .env-strip-label {
  opacity: 0.85; font-weight: 500; margin-right: 6px; letter-spacing: 0.3px;
}

.env-strip:empty { display: none; }


/* ---------- Nav links rendered by nav.js into #nav-container ----------
   Right-cluster placement (A2 design): page-nav sits next to the help
   icon, with a tab-style underline on the active page. The legacy
   .nav-center selector is retained for any page that still renders
   centered, but Inspector's nav container now lives in .nav-right. */
.nav-right #nav-container,
.nav-center { display: flex; gap: 0; }

.nav-link {
  color: var(--ink-2); text-decoration: none; padding: 8px 12px;
  font-size: 13.5px; font-weight: 500;
  border-bottom: 2px solid transparent;
  /* Overlap the navContainer's bottom border so the underline reads as
     a tab indicator rather than floating in space. */
  margin-bottom: -1px;
}

.nav-link:hover { background-color: var(--surface-2); }

.nav-link.active {
  color: var(--accent); font-weight: 600;
  border-bottom-color: var(--accent);
}

.nav-link.nav-disabled{opacity:.4;pointer-events:none;cursor:not-allowed;text-decoration:none}


/* ---------- User chip (avatar + name + ▾ dropdown) ----------
   Replaces the user-info span + Logout button cluster. Inline-flex pill
   with the user's initials, first name and a chevron; clicking opens a
   dropdown menu with the full email + a Logout button. Same definition
   duplicated across inspector-/tasks-/validation-/help-page.css so every
   page renders it identically. */
.user-chip {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-3); padding: 3px 10px 3px 4px;
  border-radius: 999px;
  cursor: pointer; user-select: none;
  font-family: inherit;
}

.user-chip:hover { background: var(--line); }

.user-chip:focus { outline: 2px solid var(--accent-border); outline-offset: 2px; }

.user-chip-avatar {
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}

.user-chip-name {
  font-size: 12.5px; color: var(--ink); font-weight: 500;
  max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.user-chip-chev { color: var(--muted); font-size: 9px; }

.user-chip-menu {
  position: absolute; right: 0; top: calc(100% + 6px);
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.08);
  min-width: 220px; padding: 6px;
  z-index: 100;
}

.user-chip-menu[hidden] { display: none; }

.user-chip-menu-header {
  padding: 8px 10px 10px;
  border-bottom: 1px solid var(--line); margin-bottom: 4px;
}

.user-chip-menu-header .who {
  color: var(--muted); font-size: 11px; letter-spacing: 0.3px;
  text-transform: uppercase; font-weight: 600;
}

.user-chip-menu-header .email {
  font-weight: 500; color: var(--ink); margin-top: 2px;
  font-size: 13px; word-break: break-all;
}

.user-chip-menu-item {
  width: 100%; text-align: left;
  background: transparent; border: 0; cursor: pointer;
  padding: 8px 10px; border-radius: 4px; color: var(--ink);
  font-size: 13px; font-family: inherit;
}

.user-chip-menu-item:hover { background: var(--surface-3); }

.user-chip-logout { color: var(--red-d); }


/* ---------- Page-action sub-bar ----------
   Horizontal strip below the navContainer that holds the page title +
   page-specific action buttons. Used by Inspector (just the title) and
   Validation (title + Reject/Release/Submit). */
.sub-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.sub-bar .page-title {
  margin: 0; font-size: 18px; font-weight: 600; color: var(--ink);
}

.sub-bar .sub-bar-actions { display: flex; gap: 8px; margin-left: auto; }

/* ============================================================================
   Tasks page (canonical body/navContainer/buttons/modal/toast/animations)
   ============================================================================ */



html,
body {
    background: var(--accent-bg-3);
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}


/* Hide content by default until authentication is confirmed */
body:not(.authenticated) {
    opacity: 0;
    overflow: hidden;
}


/* Show content once authenticated */
body.authenticated {
    opacity: 1;
    transition: opacity 0.2s ease-in;
}


/* Loading screen while checking authentication */
#auth-loading-screen {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: var(--accent-bg-3);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
    flex-direction: column;
    gap: 20px;
}


#auth-loading-screen.hidden {
    display: none;
}


.loading-spinner {
    width: 40px;
    height: 40px;
    border: 4px solid var(--accent);
    border-top: 4px solid transparent;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}


@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}


.navContainer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 30px;
    background: var(--surface);
    box-shadow: var(--elevation-1);
    margin-bottom: 0;
}

/* Dark-theme logo swap. The shipped img/onphase-logo.svg has dark-grey
   (#333) wordmark text that disappears on the dark nav surface. A CSS
   filter can't fix it without wrecking the multi-colour gradient mark,
   so we swap to the white-wordmark variant. `content: url()` replaces the
   rendered <img> in all modern browsers while keeping the element's alt
   text — no per-page HTML change needed. Scoped to the nav logo only. */
:root[data-theme="dark"] .navContainer > img[src*="onphase-logo"] {
    content: url("../img/onphase-logo-dark.svg");
}


.nav-right {
    display: flex;
    align-items: center;
    gap: 20px;
}


.user-info {
    color: var(--ink-2);
    font-size: 14px;
}


.logout-btn {
    background: var(--accent);
    color: var(--on-accent);
    border: none;
    padding: 8px 16px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 14px;
    transition: background 0.2s ease;
}


.logout-btn:hover {
    background: var(--accent-d);
}


.main-container {
    flex: 1;
    padding: 30px;
    max-width: 1600px;
    margin: 0 auto;
    width: 100%;
    box-sizing: border-box;
}


.page-title {
    color: var(--ink-strong);
    margin: 0 0 16px 0;
    font-size: 28px;
    font-weight: normal;
}


/* Refresh action sits at the end of the filter row, aligned to the bottom
   of the selects (filterField is a column, so push the button down). */
.filter-actions { justify-content: flex-end; }


.form-hint {
    color: var(--muted);
    font-size: 13px;
    margin: 10px 0 0 0;
}


/* Auth loading screen message — tokenized so it reads in both themes. */
#auth-loading-screen p { color: var(--muted); font-size: 16px; margin: 0; }


.header-filter-select {
    width: 100%;
    padding: 6px 8px;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: var(--surface);
    color: var(--ink);
    font-family: inherit;
    font-size: 13.5px;
    min-width: 120px;
}


.header-filter-select:focus {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
    border-color: var(--accent);
}


.tasks-container {
    background: var(--surface);
    border-radius: 9px;
    box-shadow: var(--elevation-1);
    overflow: hidden;
}


.tasks-header {
    background: var(--accent);
    color: var(--on-accent);
    padding: 12px 20px;
    border-radius: 9px 9px 0 0;
    font-weight: 600;
    font-size: 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}


.tasks-header-left {
    display: flex;
    align-items: center;
    gap: 15px;
}


.tasks-count {
    background: rgba(255, 255, 255, 0.2);
    padding: 4px 12px;
    border-radius: 15px;
    font-size: 14px;
}


.header-actions {
    display: flex;
    gap: 10px;
    flex-shrink: 0;
}


/* Modal */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.4);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.modal {
    background: var(--surface);
    color: var(--ink-strong);
    padding: 20px;
    border-radius: 9px;
    width: 90%;
    max-width: 420px;
    box-shadow: 0px 10px 20px rgba(0,0,0,0.15);
}

.modal-header { font-size: 18px; font-weight: 600; margin-bottom: 10px; }

.modal-body p { margin: 0 0 8px 0; color: var(--ink-3); }

.modal-body textarea {
    width: 100%;
    min-height: 90px;
    padding: 10px;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    font-family: inherit;
    font-size: 13.5px;
    color: var(--ink);
    background: var(--surface);
    resize: vertical;
    box-sizing: border-box;
}

.modal-body textarea:focus {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
    border-color: var(--accent);
}

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


/* Toast notifications */
.toast-container {
    position: fixed;
    top: 80px;
    right: 20px;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 10px;
    pointer-events: none;
}


.toast {
    background: var(--surface);
    color: var(--ink-2);
    padding: 15px 20px;
    border-radius: 9px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    min-width: 250px;
    max-width: 400px;
    display: flex;
    align-items: center;
    gap: 12px;
    pointer-events: all;
    animation: slideIn 0.3s ease-out;
    border-left: 4px solid var(--accent);
}


.toast.info { border-left-color: var(--accent); }

.toast.success { border-left-color: var(--green); background: var(--green-bg-soft); }

.toast.error { border-left-color: var(--red-strong); background: var(--red-bg-soft); }

.toast.warning { border-left-color: var(--amber); background: var(--amber-bg-soft); }


.toast-icon {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
}


.toast-message {
    flex: 1;
    font-size: 14px;
    line-height: 1.4;
}


.toast-close {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    cursor: pointer;
    opacity: 0.5;
    transition: opacity 0.2s;
}


.toast-close:hover {
    opacity: 1;
}


@keyframes slideIn {
    from {
        transform: translateX(400px);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}


@keyframes slideOut {
    from {
        transform: translateX(0);
        opacity: 1;
    }
    to {
        transform: translateX(400px);
        opacity: 0;
    }
}


.tasks-list {
    padding: 0;
}


.tasks-list-header {
    display: flex;
    align-items: center;
    padding: 15px 30px;
    background: var(--surface-3);
    border-bottom: 2px solid var(--surface-3);
    font-weight: 600;
    color: var(--muted);
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}


.tasks-list-header .task-radio {
    width: 18px;
    margin-right: 20px;
    visibility: hidden;
}


.tasks-list-header .task-details > div {
    cursor: pointer;
    user-select: none;
    transition: color 0.2s ease;
    display: flex;
    align-items: center;
    gap: 5px;
}


.tasks-list-header .task-details > div:hover {
    color: var(--accent);
}


.tasks-list-header .task-details > div.active {
    color: var(--accent);
}


.sort-icon {
    width: 14px;
    height: 14px;
    opacity: 0.5;
    transition: opacity 0.2s ease;
}


.tasks-list-header .task-details > div:hover .sort-icon,
.tasks-list-header .task-details > div.active .sort-icon {
    opacity: 1;
}


.filter-icon {
    width: 14px;
    height: 14px;
    opacity: 0.4;
    transition: opacity 0.2s ease;
    cursor: pointer;
}


.filter-icon:hover,
.filter-icon.has-filter {
    opacity: 1;
}


.filter-icon.has-filter {
    color: var(--accent);
}


.column-header-content {
    display: flex;
    align-items: center;
    gap: 5px;
}


.column-sort {
    display: flex;
    align-items: center;
    gap: 3px;
    flex: 1;
}


/* Filter dropdown */
.filter-dropdown {
    position: absolute;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    padding: 15px;
    z-index: 1000;
    min-width: 220px;
    display: none;
    margin-top: 8px;
}


.filter-dropdown.show {
    display: block;
}


.filter-dropdown-header {
    font-weight: 600;
    margin-bottom: 10px;
    color: var(--ink-2);
    font-size: 14px;
}


.filter-dropdown-content {
    display: flex;
    flex-direction: column;
    gap: 8px;
}


.filter-dropdown input[type="text"],
.filter-dropdown input[type="date"],
.filter-dropdown select {
    width: 100%;
    padding: 6px 8px;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    font-family: inherit;
    font-size: 13.5px;
    color: var(--ink);
    background: var(--surface);
    box-sizing: border-box;
}


.filter-dropdown label {
    display: block;
    font-size: 12px;
    color: var(--muted);
    margin-bottom: 4px;
    margin-top: 8px;
}


.filter-dropdown label:first-of-type {
    margin-top: 0;
}


.filter-dropdown input[type="text"]:focus,
.filter-dropdown input[type="date"]:focus,
.filter-dropdown select:focus {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
    border-color: var(--accent);
}


.filter-dropdown-actions {
    display: flex;
    gap: 8px;
    margin-top: 10px;
}


.filter-dropdown-btn {
    flex: 1;
    padding: 6px 12px;
    border: none;
    border-radius: 6px;
    font-size: 13px;
    cursor: pointer;
    transition: background 0.2s ease;
}


.filter-dropdown-btn.apply {
    background: var(--accent);
    color: var(--on-accent);
}


.filter-dropdown-btn.apply:hover {
    background: var(--accent-d);
}


.filter-dropdown-btn.clear {
    background: var(--surface-4);
    color: var(--muted);
}


.filter-dropdown-btn.clear:hover {
    background: var(--line);
}


.task-item {
    display: flex;
    align-items: center;
    padding: 20px 30px;
    border-bottom: 1px solid var(--surface-4);
    cursor: pointer;
    transition: all 0.2s ease;
}


.task-item:hover {
    background: var(--accent-bg-3);
}


.task-item:last-child {
    border-bottom: none;
}


.task-item.selected {
    background: var(--accent-bg-3);
    border-left: 4px solid var(--accent);
}


.task-radio {
    margin-right: 20px;
    width: 18px;
    height: 18px;
    accent-color: var(--accent);
}


.task-details {
    flex: 1;
    display: grid;
    grid-template-columns: 2fr 1fr 1fr 1.5fr 1fr;
    gap: 20px;
    align-items: center;
}


.task-name {
    font-weight: 600;
    color: var(--ink-strong);
    font-size: 16px;
}


.task-status {
    padding: 6px 12px;
    border-radius: 15px;
    font-size: 12px;
    font-weight: 500;
    text-align: center;
    text-transform: uppercase;
}


.status-pending {
    background: var(--amber-bg);
    color: var(--amber-d);
}


.status-in-progress {
    background: var(--accent-bg-2);
    color: var(--accent-deep);
}


.status-completed {
    background: var(--green-bg);
    color: var(--green-d);
}


.status-terminated {
    background: var(--red-bg);
    color: var(--red-d);
}


.task-user {
    color: var(--muted);
    font-size: 14px;
}


.task-client {
    color: var(--muted);
    font-size: 14px;
}


.task-time {
    color: var(--muted-2);
    font-size: 13px;
}


.action-buttons {
    margin-top: 30px;
    padding: 20px 30px;
    background: var(--surface-3);
    display: flex;
    gap: 15px;
    justify-content: flex-end;
}


.btn {
    padding: 12px 24px;
    border: none;
    border-radius: 9px;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
}


.btn-primary {
    background: var(--accent);
    color: var(--on-accent);
}


.btn-primary:hover {
    background: var(--accent-d);
    transform: translateY(-1px);
}


.btn-secondary {
    background: var(--slate);
    color: var(--on-accent);
}


.btn-secondary:hover {
    background: var(--ink-3);
    transform: translateY(-1px);
}


.btn:disabled {
    background: var(--faint);
    cursor: not-allowed;
    transform: none;
}


.btn-header {
    padding: 8px 16px;
    font-size: 13px;
    background: rgba(255, 255, 255, 0.2);
    color: var(--on-accent);
    border: 1px solid rgba(255, 255, 255, 0.3);
    white-space: nowrap;
    flex-shrink: 0;
}


.btn-header:hover {
    background: var(--overlay-light);
    transform: translateY(-1px);
}


.btn-header:disabled {
    background: rgba(255, 255, 255, 0.1);
    color: rgba(255, 255, 255, 0.5);
    cursor: not-allowed;
    transform: none;
}

/* "Start Working" is the primary CTA, but it sits ON the accent-colored
   .tasks-header bar. A solid surface button with accent text keeps it the
   visible primary action (vs the translucent .btn-header siblings) and
   themes correctly in both modes. */
.start {
    background: var(--surface);
    color: var(--accent);
    border-color: var(--surface);
}


.start:hover:not(:disabled) {
    background: var(--surface-2);
    transform: translateY(-1px);
}

.btn-danger {
    background: var(--red-strong);
    color: var(--on-accent);
}


.btn-danger:hover:not(:disabled) {
    background: var(--red-d);
}


.btn-danger:disabled {
    background: rgba(220, 53, 69, 0.3);
    color: rgba(255, 255, 255, 0.5);
}



.empty-state {
    text-align: center;
    padding: 60px 30px;
    color: var(--muted);
}


.empty-state svg {
    width: 64px;
    height: 64px;
    margin-bottom: 20px;
    opacity: 0.5;
}


@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}


/* Responsive design */
@media (max-width: 768px) {
    .navContainer {
        padding: 10px 20px;
    }
    
    .main-container {
        padding: 20px 15px;
    }
    
    .tasks-list-header {
        display: none;
    }
    
    .task-details {
        grid-template-columns: 1fr;
        gap: 10px;
    }
}

/* ============================================================================
   Inspector page (form*, combobox, email-list, details.section, cf-*, wf-*)
   ============================================================================ */

/* Inspector page styles — extends tasks-page.css.
   Base layout (body bg, .navContainer, .user-info, .logout-btn, .main-container)
   is inherited from tasks-page.css so the inspector page matches the existing
   HITL pages. The styles below add the inspector-specific elements. */

/* ---------- Full-width environment strip ---------- */
/* Always rendered at the very top of the page; the JS populates the
   text from CONFIG.environment. Slim (18px) so it adds minimal vertical
   chrome while making the environment unmistakable from anywhere on
   screen. Same definition is duplicated in tasks-page.css /
   validation-page.css / help-page.css so every page renders it
   identically — no shared chrome stylesheet exists yet. */
.env-strip {
  background: var(--muted); color: var(--on-accent);
  text-align: center;
  font-size: 12px; font-weight: 700; letter-spacing: 0.6px;
  height: 22px; line-height: 22px;
  text-transform: uppercase;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* Pin the env strip to the top of the viewport so operators always
     see which environment they're in (especially PROD with its green
     var(--env-prod) banner) even after scrolling through long email lists or
     detail panels. position:sticky keeps the strip in document flow when
     unscrolled (no body-padding compensation needed) and pins it once
     the page scrolls past it. z-index:200 sits above the user-chip
     dropdown (z-index:100) — so the strip stays visible on top of a
     dropdown that's open while the page is scrolled — but below
     modal-tier overlays (z-index:1000+). */
  position: sticky;
  top: 0;
  z-index: 200;
}


/* Env badge on the right of the nav */
.env-badge {
  margin-left: 4px; padding: 2px 6px;
  background: var(--orange); color: var(--on-accent); border-radius: 3px;
  font-size: 12px; font-weight: bold;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 24px;
}

.env-badge:empty { display: none; }


/* ---------- Main container layout ----------
   The diagnostic page wants full viewport width so the email list and
   detail pane can breathe on large monitors. Overrides the 1600px cap
   inherited from tasks-page.css. */
body.page-inspector .main-container { padding: 20px 30px; max-width: none; width: 100%; box-sizing: border-box; }


/* ---------- Filters (formContainer style, matches validation page) ---------- */
.formContainer { display: flex; flex-direction: column; border-radius: 9px; width: 100%; margin-bottom: 20px; }

.formHeader { background: var(--accent); color: var(--on-accent); padding: 12px 20px; border-radius: 9px 9px 0 0; font-weight: 600; }

.formBody { display: flex; flex-direction: column; padding: 16px 20px; background: var(--surface); border-radius: 0 0 9px 9px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }

.formSection { display: flex; flex-direction: column; }


/* Filter rows: explicit groupings rather than auto-grid so the layout is
   stable across viewport widths. Each .filterRow wraps on its own when
   the contents don't fit. */
.filterRow {
  display: flex; flex-wrap: wrap; gap: 14px 18px; margin-bottom: 14px;
}

.filterRow:last-child { margin-bottom: 0; }

.filterField { display: flex; flex-direction: column; gap: 4px; flex: 1 1 200px; min-width: 200px; }

/* Mailbox: longest-text field. Round 70: dropped min-width: 380 so the
   mailbox combobox always sizes to its container (was overflowing
   the ~360-px left pane on landscape iPhone side-by-side). flex-basis
   stays at 380 so it gets the room it wants when there's enough; on
   narrower containers it shrinks to fit, never overflowing. */
.filterField.mailbox { flex: 2 1 380px; min-width: 0; }

/* .filterField.has-attachments removed in Round 21 alongside the
   filter itself — selector deleted to avoid dead-code drift. */

/* Date + time range: two datetime-local inputs (from, to) on one row.
   Each input combines a calendar date picker with a time picker into
   one control so the user picks both at once. Round 47 rolled the
   split-picker experiment (Rounds 26-46) back to a single combined
   <input type="datetime-local"> after the cross-browser sizing fight
   (Chrome's picker icon vs. Safari's compact rendering) never
   settled cleanly. The single combined picker had worked fine in
   practice; the popup-AM/PM concern that drove the split was a
   smaller real-world annoyance than the layout churn.
   Defaults: yesterday 00:00 UTC → today 23:59:59 UTC. */
/* Round 70: dropped min-width: 540 so the date range filterField can
   shrink to its container on landscape iPhone (left pane ~360 px).
   At ≤1000-px viewports the date-range-inputs flex-wrap rule below
   takes the From / To pickers onto their own lines. */
.filterField.date-range { flex: 1 1 540px; min-width: 0; }

/* Sender + Subject: share row equally. */
.filterField.text-search { flex: 1 1 0; min-width: 220px; }

/* flex-wrap:nowrap keeps both pickers on a single line in every
   desktop browser. The parent min-width above is sized to Safari's
   worst-case so the inputs never need to shrink below their natural
   rendered width. The iPhone @media block at the bottom of this
   stylesheet flips this back to wrap so mobile still stacks. */
.date-range-inputs { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }

.date-range-inputs input[type="datetime-local"] {
  padding: 6px 8px; border: 1px solid var(--line-strong); border-radius: 4px;
  /* Inherit the body font instead of the OS default monospace that
     browsers ship with native date/time pickers. */
  font-family: inherit;
  font-size: 13.5px; color: var(--ink); background: var(--surface);
  line-height: 1.3;
  /* flex:1 1 0 + min-width:0 = split available row width evenly
     between the two inputs; can shrink if a parent tightens, which
     prevents an overflow forcing-wrap. */
  flex: 1 1 0; min-width: 0;
}

.date-range-inputs input[type="datetime-local"]:focus {
  outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent);
}

/* Force 24-hour display in the inline edit field by hiding the
   AM/PM segment. Chromium/Webkit then renders the hour as 00-23.
   Firefox already renders 24h on most locales without intervention.
   The popup scroller overlay is OS-locale controlled — accepted
   trade-off for using the native picker. */
.date-range-inputs input[type="datetime-local"]::-webkit-datetime-edit-ampm-field {
  display: none;
}

.date-range-inputs .date-sep {
  color: var(--muted); font-size: 13px; font-weight: 500; user-select: none;
  padding: 0 2px;
}

/* "UTC" hint next to the Date range label. */
.filterHint {
  margin-left: 6px; font-size: 11px; font-weight: 600;
  color: var(--muted); letter-spacing: 0.4px;
  background: var(--surface-3); padding: 1px 6px; border-radius: 3px;
  text-transform: uppercase;
}

.filterLabel { font-size: 13px; color: var(--ink-3); font-weight: 500; }

.filterField input,
.filterField select {
  padding: 6px 8px; border: 1px solid var(--line-strong); border-radius: 4px;
  font-family: inherit;
  font-size: 13.5px; color: var(--ink); background: var(--surface);
}

.filterField input:focus,
.filterField select:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }


/* ---------- Mailbox combobox (WAI-ARIA combobox-with-listbox) ----------
   The combobox + its listbox occupy the FULL width of the parent
   .filterField.mailbox. The listbox is positioned absolutely with
   left:0/right:0 so it tracks the input's width exactly; both
   carry box-sizing:border-box so padding/border are inside the
   100% width. The listbox option text wraps / breaks aggressively
   so a long email like "ap-very-long-name@inbox.predeploy.com"
   can't shove the listbox wider than its container and overflow
   onto the right pane. (Round 32 hardening.) */
.combobox {
  position: relative; display: flex; align-items: stretch;
  width: 100%; box-sizing: border-box;
}

.combobox-input {
  flex: 1 1 0; min-width: 0;
  padding: 6px 28px 6px 8px;
  border: 1px solid var(--line-strong); border-radius: 4px;
  font-size: 14px; color: var(--ink); background: var(--surface);
  box-sizing: border-box;
}

.combobox-input:focus {
  outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent);
}

.combobox-toggle {
  position: absolute; top: 0; right: 0; height: 100%;
  width: 28px; padding: 0; border: 0; background: transparent;
  color: var(--muted); font-size: 12px; cursor: pointer;
}

.combobox-toggle:hover { color: var(--accent); }

.combobox-listbox {
  position: absolute; top: calc(100% + 2px); left: 0; right: 0;
  z-index: 50;
  margin: 0; padding: 4px 0; list-style: none;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 4px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
  max-height: 280px; overflow-y: auto; overflow-x: hidden;
  box-sizing: border-box;
}

.combobox-listbox[hidden] { display: none; }

.combobox-option {
  padding: 6px 10px; font-size: 13.5px; color: var(--ink);
  cursor: pointer; line-height: 1.3;
  /* Long mailbox labels still need to be readable, but they must
     NOT widen the listbox past the field. */
  overflow-wrap: anywhere; word-break: break-word;
}

.combobox-option:hover,
.combobox-option.active,
.combobox-option[aria-selected="true"] {
  background: var(--accent-bg); color: var(--accent-strong);
}

/* Search-term highlight inside an option. The shared MailboxCombobox
   module emits plain <mark> elements, so scope the highlight styling
   here (rather than a global mark rule) to keep it off other pages. */
.combobox-option mark {
  background: var(--amber-bg); color: var(--amber-d); padding: 0 1px; border-radius: 2px;
}

.combobox-option .label-main { display: block; }

.combobox-option .label-sub {
  display: block; color: var(--muted); font-size: 11.5px; margin-top: 1px;
  font-family: 'Menlo','Monaco',monospace;
  overflow-wrap: anywhere; word-break: break-all;
}

.combobox-empty {
  padding: 10px 12px; color: var(--muted); font-style: italic; font-size: 13px;
}

.combobox-status {
  position: absolute; left: -10000px; width: 1px; height: 1px;
  overflow: hidden;  /* visually hidden but read by screen readers */
}

.required { color: var(--red-d); }

/* `.presets` button cluster was removed when the Last 24h / Last 7d
   preset shortcuts came out — sensible defaults (yesterday → today) now
   cover the common case. */
.status-helpers { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }

.status-helpers .actionBtn { padding: 4px 10px; font-size: 12px; }


.formActions {
  display: flex; align-items: center; gap: 10px;
  padding-top: 14px; margin-top: 14px; border-top: 1px solid var(--surface-4);
}

.actionBtn { padding: 6px 14px; border-radius: 6px; font-weight: 500; cursor: pointer; font-size: 14px; }

.btn-release { background: var(--surface); color: var(--ink-3); border: 1px solid var(--muted-2); }

.btn-release:hover { background: var(--surface-3); }

.btn-submit { background: var(--accent); color: var(--on-accent); border: 1px solid var(--accent); }

.btn-submit:hover { background: var(--accent-d); border-color: var(--accent-d); }

#result-summary { color: var(--muted); font-size: 13px; margin-left: auto; }


/* ---------- Split layout (list left, detail right) ---------- */
.diagnosticSplit {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  /* Allow each pane to scroll independently without the outer page scrolling */
}

/* 40/60 split — left pane (email search + list) intentionally narrower
   so the detail pane on the right has more room for invoice cards,
   captured-fields tables, and workflow timelines. flex-grow ratios let
   the 20px gap shrink the panes proportionally; max-width caps prevent
   one pane from absorbing the entire row if the other collapses. */
.diagnosticSplitLeft {
  flex: 40 1 0;
  min-width: 0;          /* keeps tables from forcing horizontal overflow */
  max-width: 40%;
}

.diagnosticSplitRight {
  flex: 60 1 0;
  min-width: 0;
  max-width: 60%;
  position: sticky;
  top: 16px;
  max-height: calc(100vh - 130px);
  overflow-y: auto;
}

/* Round 70: stack-the-split breakpoint dropped from 1100 px → 768 px
   so iPhone landscape viewports (844 px on 14/15/16, 926 px on Plus,
   932 px on Pro Max) keep the two-column side-by-side layout — the
   user previously had to scroll past the full email list to find the
   invoice details on landscape because everything was stacked. The
   old 1100 cutoff was too aggressive: at 932 width the split renders
   ~360 / ~540 (40/60) which has plenty of room for both panes. */
@media (max-width: 768px) {
  .diagnosticSplit { flex-direction: column; }
  .diagnosticSplitLeft,
  .diagnosticSplitRight { max-width: 100%; position: static; max-height: none; }
}

/* Round 70 part 2: when the viewport is SHORT (e.g. landscape iPhone:
   height ~390-430 px), unset the right pane's `position: sticky` +
   `max-height: calc(100vh - 130px)` rules. On a tall viewport that
   pair keeps the detail visible while the user scrolls the list on
   the left; on a 390-px-tall viewport the same pair squeezes the
   detail pane to ~210 px high, which is the visual "invoice details
   not showing" symptom the user reported. With position: static the
   right pane flows naturally — both panes scroll together with the
   page so the detail content is reachable. */
@media (max-height: 600px) {
  .diagnosticSplitRight {
    position: static;
    max-height: none;
  }
}

/* Round 70 part 3: at viewports ≤ 1000 px the side-by-side split
   means the left pane (with filters) is ≤ ~400 px wide. The date
   range pickers (From → To) would overflow that container if kept on
   a single line, so flip flex-wrap and let each picker take its own
   line. Covers landscape iPhones (844-932 wide), iPad portrait (768),
   and any narrow desktop window — and obviously stacked portrait
   phones below 768. The ≤768 mobile block has the same rules
   redundantly, which is harmless and keeps each block self-contained. */
@media (max-width: 1000px) {
  .date-range-inputs { flex-wrap: wrap; }
  .date-range-inputs input[type="datetime-local"] { flex: 1 1 100%; }
  /* Hide the From → To separator when the pickers stack onto
     their own lines — the visual arrow only makes sense inline. */
  .date-range-inputs .date-sep { display: none; }
}

/* ---------- iPhone portrait tightening (≤768px) ----------
   At this width the split has already stacked (max-width: 768 above).
   These rules trim the chrome so the single-column doesn't waste
   horizontal space, and make sure the right pane (detail) is never
   cropped or hidden by leftover sticky/height rules. */
@media (max-width: 768px) {
  /* Reclaim the side gutters — every px counts on a 390px iPhone. */
  body.page-inspector .main-container { padding: 12px 12px; }
  .diagnosticSplit { gap: 12px; }
  /* Belt-and-suspenders: even if some descendant CSS tries to clamp
     the detail pane's height on a narrow viewport, force it to show
     in full so a tapped email's detail can't end up cropped. */
  .diagnosticSplitRight,
  .diagnosticSplitRight .detail {
    max-height: none;
    overflow: visible;
  }
  /* Filter rows stack 1-up rather than wrapping mid-row. */
  .filterField,
  .filterField.mailbox,
  .filterField.date-range,
  .filterField.text-search { flex: 1 1 100%; min-width: 0; }
  /* Desktop pins the two datetime-local pickers on one line via
     flex-wrap: nowrap up top — flip it back to wrap here so iPhone-
     class viewports stack the from/to halves vertically instead of
     cramming them side-by-side. */
  .date-range-inputs { flex-wrap: wrap; }
  .date-range-inputs input[type="datetime-local"] { flex: 1 1 100%; }
  /* Pagination wraps to two rows: summary up top, controls under. */
  .pagination { flex-direction: column; align-items: stretch; }
  .pagination .summary { text-align: center; }
  .pagination .controls { justify-content: center; }
  /* Email-card UTC/EST timestamps lose the right-aligned column on
     iPhone and float under the From/Subject instead — saves ~140px
     of horizontal real estate. */
  .ce-top { flex-direction: column; }
  .ce-time { text-align: left; margin-top: 4px; }

  /* ---------- Round 67: post-Round-57+ mobile fixes ----------
     The original Round 27 mobile block predates the invoice-doc-row-
     bottom 4-col grid (Round 57), captured-fields Primary/Additional
     split (Round 59), workflow timeline column refresh (Round 51),
     and Email Details consolidation (Round 65). Without these rules,
     iPhone 14/15/16 portrait (390-393px) overflowed horizontally on
     several panels.
     Landscape (844-932px on iPhone 14+) is above this 768px cap, so
     it keeps the full desktop-style view (already stacked into one
     column via the 1100px breakpoint above). Hidden-on-portrait
     content stays visible there. */

  /* Header chrome (ALL pages, identical): logo top-left, user-chip top-right
     aligned with the logo, and the nav links drop to a full-width second row.
     Dissolve .nav-right (display:contents) so the chip can share the logo's row;
     margin-right:auto on the logo pushes the chip/help cluster right (robust to
     the help page, which has no help icon). */
  .navContainer { flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 10px 14px; }
  .navContainer .nav-right { display: contents; }
  .navContainer > img { margin-right: auto; }
  .navContainer #help-link { order: 1; }
  .navContainer #user-chip-container { order: 2; }
  .navContainer #nav-container { order: 3; flex: 0 0 100%; }
  .detail-header h2 { font-size: 18px; }

  /* Invoice doc card top-row: hide separator dots since they read as
     orphans when the row wraps. (The row-bottom rules from Round 69
     above already cover both desktop and mobile in a single flex-
     wrap layout — no mobile-specific override needed any more.) */
  .invoice-doc-row-top { gap: 4px 6px; }
  .invoice-doc-row-top .sep { display: none; }

  /* Email Details header dl: stack each field as dt above dd in one
     column. The 110px label column wasn't leaving enough room for
     long From/Subject values at iPhone width. */
  .email-headers {
    grid-template-columns: 1fr;
    gap: 2px 0;
  }
  .email-headers dt {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px;
    color: var(--muted); margin-top: 6px;
  }
  .email-headers dt:first-child { margin-top: 0; }
  .email-headers dd { padding-bottom: 2px; }

  /* Captured fields table: hide the Original column on mobile. The
     "Edited" indicator still shows via the .cf-edited row marker
     (purple left rail) so HITL edits aren't invisible — the strike-
     through original value just isn't visible until landscape /
     desktop. */
  .cf-table th.cf-th-orig,
  .cf-table td.cf-orig { display: none; }

  /* Workflow table: stack UTC + EST timestamps vertically inside a
     single timestamp cell, so the row reads as
       [rail] [step name]
       [rail] [UTC stacked over EST] [Duration]
     instead of squeezing two timestamps + duration into 3 columns
     side-by-side. The wf-ts cells already share the same selector;
     we just merge their visual presentation via the EST display
     change. */
  .wf-steps thead th:nth-child(3) { display: none; } /* EST column header */
  .wf-steps tbody tr.detail-row td:nth-child(2) {
    display: block; padding-bottom: 2px;
  }
  .wf-steps tbody tr.detail-row td:nth-child(3) {
    display: none; /* EST cell — its content moves into UTC's cell visually */
  }

  /* Long monospace SF execution name in the actions strip wraps
     instead of overflowing. */
  .invoice-actions { flex-wrap: wrap; gap: 6px 10px; }
  .invoice-actions .exec-link,
  .exec-link { word-break: break-all; }

  /* Iframe email body — keep within the column without forcing
     horizontal scroll on the parent. */
  iframe.email-html { width: 100%; max-width: 100%; }
}

/* ---------- iPhone SE / Mini / small Android (≤380px) ----------
   Tightest viewport block — applies ONLY to truly small screens
   (iPhone SE 375 px, plus older Mini and small Android). Round 67
   triggered this at ≤480 px which incorrectly captured iPhone 14/15/16
   (390-393 px) and Pro Max (430 px) too — Pro Max has +55 px more
   room than SE and shouldn't get the same tightening (16 px h2,
   8 px padding, 40 px rail). Round 68: bumped down to 380 px so
   only true small viewports get squeezed; standard iPhones (390+)
   and Pro Max (430) keep the roomier ≤768 px rules above. */
@media (max-width: 380px) {
  body.page-inspector .main-container { padding: 8px 8px; }
  .detail-header h2 { font-size: 16px; }
  .formContainer { width: 100%; margin-bottom: 12px; }
  /* Workflow rail column gets a tighter footprint on the smallest
     viewports so the step-name column gets more room. */
  .wf-steps th.wf-rail-col,
  .wf-steps .wf-rail { width: 40px; }
}


/* ---------- Email-list card layout ----------
   Replaces the old 4-column table. Each email is a card:
     [status icon] [sender bold / subject normal] [UTC + EST date/time right]
     [counts as inline text below — no stacked pills]
   Status icon follows the priority cascade:
     1. ✗  failed     — any invoice failed (highest priority)
     2. ⟳  running    — any invoice still in flight
     3. ✓  processed  — every invoice succeeded
     4. ⊘  empty      — no valid attachments at all (incl. 0-attachment
                        emails and emails whose attachments were all rejected)
   Mock: docs/mocks/inspector-screen.html (Round 7).
*/
.tableContainer {
  background: transparent; margin-bottom: 20px;
}
#email-list { display: flex; flex-direction: column; gap: 8px; }

.ce-card {
  display: flex; gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--surface); cursor: pointer;
  transition: background 0.15s;
  /* min-width: 0 closes a flex-constraint gap: as a row-flex container
     .ce-card's default min-width is auto, which respects its content's
     min-content size. A subject containing a long unbreakable run
     (URL, hash, attachment filename) would otherwise push the card
     wider than its #email-list column and break the ellipsis /
     line-clamp on .ce-subject. With min-width:0 the card honors the
     cross-axis stretch from #email-list instead. */
  min-width: 0;
}
.ce-card:hover { background: var(--surface-2); }
.ce-card.selected {
  background: var(--accent-bg);
  border-left: 3px solid var(--accent);
  padding-left: 11px;
}

/* ============================================================
   Round 73-b: skeleton loaders. Two contexts:
     · #email-list during loadList() → 5 .sc-skel-card slots
     · #detail-region during open() → .sc-skel-detail stack
   Cards mimic the shape of real .ce-card output / detail content
   so the user sees where content WILL appear, not just a generic
   spinner. 1.4 s shimmer sweep on each placeholder. Replaces the
   prior "Loaded N emails for…" success toast — operators get
   feedback DURING the fetch instead of AFTER.
   `prefers-reduced-motion: reduce` freezes the gradient so the
   placeholder stays visible without animating.
   ============================================================ */
.sc-skel-card {
  display: flex; gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--surface);
  cursor: default;
}
.sc-skel-icon {
  width: 25px; height: 25px; border-radius: 50%;
  flex-shrink: 0;
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--line) 50%, var(--surface-3) 75%);
  background-size: 200% 100%;
  animation: sc-shimmer 1.4s ease-in-out infinite;
}
.sc-skel-body {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.sc-skel-line {
  height: 12px; border-radius: 3px;
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--line) 50%, var(--surface-3) 75%);
  background-size: 200% 100%;
  animation: sc-shimmer 1.4s ease-in-out infinite;
}
.sc-skel-line.w-30 { width: 30%; }
.sc-skel-line.w-45 { width: 45%; }
.sc-skel-line.w-60 { width: 60%; }
.sc-skel-line.w-75 { width: 75%; }
.sc-skel-line.w-90 { width: 90%; }
.sc-skel-line.h-10 { height: 10px; }
.sc-skel-line.h-16 { height: 16px; }
.sc-skel-row {
  display: flex; gap: 10px; align-items: center;
}
.sc-skel-row .sc-skel-line { flex: 1; }
.sc-skel-detail {
  padding: 16px 20px;
  display: flex; flex-direction: column; gap: 14px;
  background: var(--surface); border-radius: 9px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
}
.sc-skel-detail .sc-skel-section {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 0; border-bottom: 1px solid var(--surface-4);
}
.sc-skel-detail .sc-skel-section:last-child { border-bottom: 0; }
.sc-skel-label {
  color: var(--muted-2); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.5px;
}
@keyframes sc-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sc-skel-icon, .sc-skel-line { animation: none; }
}


/* Round status icon — color cascade derived per row from the priority
   logic above. The icon glyph carries the meaning; the ring +
   light fill are decorative reinforcement. Unicode glyphs so we
   don't need an icon font or SVG sprite. Final shape after Rounds
   34-50 of iteration: 25x25 hollow-feeling ring (1px border +
   tinted -50 background) with a matching darker glyph in the
   middle. box-sizing:border-box keeps the outer footprint at 25x25
   regardless of border width. */
.ce-icon {
  /* Lock the badge dimensions hard — defense-in-depth. The root cause
     of the original 50×50 misrendering was a class-name collision
     between this badge's `empty` variant token and the bare `.empty`
     utility class for the #empty-state placeholder div (formerly at
     same specificity 0,0,1,0; the utility lost the cascade only by
     source order). That utility's padding:24px leaked onto the badge
     and pushed it past 25×25. The collision is now resolved by
     scoping the utility to #empty-state (below in this file), but
     the min/max pinning stays — it costs nothing and prevents any
     future class-collision or parent-flex stretch from resurfacing
     this bug. */
  flex: 0 0 25px;
  width: 25px; height: 25px;
  min-width: 25px; max-width: 25px;
  min-height: 25px; max-height: 25px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700;
  align-self: flex-start;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: transparent;
  border: 1px solid transparent;
  box-sizing: border-box;
}
/* Per variant: background = Tailwind -50 (lightest tint); border +
   glyph = the darker semantic shade. warning uses var(--amber) (Flat-UI
   Sun Flower) instead of an amber-tier color so it doesn't read as
   close to the red `failed` bullet on a quick scan. */
.ce-icon.failed    { background: var(--red-bg-soft); border-color: var(--red-d); color: var(--red-d); }
.ce-icon.warning   { background: var(--amber-bg-soft); border-color: var(--amber); color: var(--amber); }
.ce-icon.running   { background: var(--accent-bg); border-color: var(--accent-strong); color: var(--accent-strong); }
.ce-icon.processed { background: var(--green-bg-soft); border-color: var(--green-d); color: var(--green-d); }
.ce-icon.empty     { background: var(--surface-2); border-color: var(--muted-2); color: var(--muted-2); }

.ce-body { flex: 1; min-width: 0; }
.ce-top {
  display: flex; gap: 12px; align-items: flex-start;
}
.ce-meta { flex: 1; min-width: 0; }
.ce-from {
  color: var(--ink-strong); font-size: 14px; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ce-subject {
  color: var(--ink); font-size: 13.5px; font-weight: 400;
  margin-top: 2px;
  /* Wrap to at most 2 lines, then ellipsis on the last line. Lets the
     card show a bit more context for long subjects without growing
     unboundedly. overflow-wrap:anywhere is what makes this robust
     against unbreakable runs (URLs, hashes, filenames with no spaces)
     — the browser breaks INSIDE the run if there's no word boundary,
     so .ce-card never gets pushed past its column width. The previous
     "white-space:nowrap + text-overflow:ellipsis" single-line clip
     was bypassed by such unbreakable runs because they pushed the
     parent's min-content past the container width.
     -webkit-line-clamp is widely supported (Chromium, Firefox 68+,
     Safari) — the line-clamp standard property is the modern form
     but -webkit-line-clamp still wins on coverage. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
/* Right column: full date + HH:MM:SS on both UTC and EST lines so a
   cross-midnight email (UTC 02:00 ≈ EST 22:00 previous day) reads with
   the correct day in either timezone. */
.ce-time {
  flex-shrink: 0; text-align: right;
  font-size: 11px; line-height: 1.35;
}
.ce-time .ce-utc { color: var(--ink); }
.ce-time .ce-est { color: var(--muted); margin-top: 2px; }
.ce-counts {
  margin-top: 8px;
  color: var(--ink-3); font-size: 12px;
  display: flex; flex-wrap: wrap; gap: 4px 10px;
}
.ce-counts .sep { color: var(--faint); }
/* Invoice total — bolded so the eye lands on the headline number
   first; the per-state breakdown reads as secondary context. */
.ce-counts .ce-invoice-total { font-weight: 700; color: var(--ink); }
.ce-counts .ce-state { font-weight: 600; }
.ce-counts .ce-state.failed    { color: var(--red-d); }
.ce-counts .ce-state.running   { color: var(--accent-strong); }
.ce-counts .ce-state.processed { color: var(--green-d); }
.ce-counts .ce-state.empty     { color: var(--muted); font-style: italic; }

/* Timestamp pair (UTC + EST) */
/* Stacked UTC + EST timestamps. Inherits the page sans-serif so it
   reads as part of the surrounding row, not as code. */
.ts-pair { display: flex; flex-direction: column; gap: 0; font-size: 12px; line-height: 1.4; }

.ts-pair .ts-utc { color: var(--ink); }

.ts-pair .ts-est { color: var(--muted); }


/* Attachments cell — plain count, no status pills (email has no single status) */
.attachCounts { color: var(--ink-3); font-size: 13px; }

.attachCounts .dim { color: var(--muted-2); font-style: italic; }


/* Stacked valid/invalid pills on email rows. Same shape/colors as the
   detail-panel header so the user can glance between list and detail and
   instantly match the counts. */
.attachStack {
  display: flex; flex-direction: column; gap: 3px; white-space: nowrap;
  align-items: flex-start;
}


/* Unified pill style used in BOTH the list and the detail header */
.count-pill {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; line-height: 1.5; white-space: nowrap;
}

.count-pill.valid     { background: var(--muted); color: var(--on-accent); }
   /* slate-dark — accepted attachments. Flipped with the invoices total
      pill in Round 25: the attachments line gets the calmer neutral
      slate now that invoices wears the louder royal blue (below). */
.count-pill.invalid   { background: var(--surface-3); color: var(--ink-3); }
   /* gray — ignored attachments (skipped, not errors) */
.count-pill.zero      { background: var(--surface-3); color: var(--muted-2); }
   /* muted */
.count-pill.none      {
  background: transparent; color: var(--muted-2); font-style: italic;
  font-weight: normal; padding: 0;
}

/* Status pills for the Invoice status column — same shape, semantic colors
   that mirror the per-doc status pills in the detail panel. The `total`
   variant is the neutral "N invoices" pill that sits above the per-status
   breakdown; intentionally desaturated so the categorical pills below it
   carry the visual weight. */
.count-pill.total     { background: var(--accent); color: var(--on-accent); }
   /* royal blue — total-invoices anchor pill in the detail Header.
      Round 25 flipped it from the calmer slate-dark to royal blue so
      the invoices row reads as the operator's primary headline number,
      with the per-state pills (processed/running/failed) sitting as
      secondary context to its right. Distinct from OnPhase brand
      var(--accent) so the two don't compete in the chrome. */
.count-pill.processed { background: var(--green-bg); color: var(--green-d); }
   /* green */
.count-pill.running   { background: var(--accent-bg-2); color: var(--accent-strong); }
   /* blue  */
.count-pill.failed    { background: var(--red-bg); color: var(--red-d); }
   /* red   */
/* Round 78e: cumulative-duration pill in the email header (next to
   status badges). Same chip shape as the other count-pills so the
   row stays visually consistent. Muted gray for the terminal case
   (final number) and blue-tinted for the in-flight case (still
   growing — mirrors the .running variant's color signal). */
.count-pill.duration         { background: var(--surface-3); color: var(--ink-2);
                               font-family: 'Menlo','Monaco',monospace; }
.count-pill.duration.running { background: var(--accent-bg-2); color: var(--accent-strong); }
   /* gray / blue */

/* =====================================================================
 * Round 80 — Date-range validation feedback
 * =====================================================================
 * Applied to the two datetime-local inputs (#from-datetime and
 * #to-datetime) when the user picks a range where end ≤ start.
 * Red border + soft red glow signal the invalid pair; the
 * .date-range-error message below the inputs (auto-created by JS)
 * carries the human-readable explanation. Both indications clear
 * the moment the range becomes valid (or Reset fires).
 */
.field-error,
input.field-error,
input[type="datetime-local"].field-error {
  border-color: var(--red-strong) !important;
  box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.20);
}
.date-range-error {
  margin-top: 6px;
  padding: 6px 10px;
  background: var(--red-bg-soft);
  border-left: 3px solid var(--red-strong);
  border-radius: 0 4px 4px 0;
  color: var(--red-d);
  font-size: 12px;
  line-height: 1.4;
}

/* Invoice status cell — stacked pills, same layout as .attachStack so list
   columns line up visually. Total invoice pill stacks above the per-status
   breakdown pills. */
.statusStack {
  display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
  white-space: nowrap;
}


/* ---------- Detail placeholder (right pane before any row is selected) ---------- */
.detail-placeholder {
  padding: 60px 24px; text-align: center; color: var(--muted-2);
  font-size: 14px;
}

.detail-placeholder .placeholder-icon {
  /* Email illustration (inline SVG). Faded so it reads as a soft focal
     point of the empty pane rather than a hard graphic. */
  line-height: 1; opacity: 0.45;
  margin-bottom: 16px;
}
.detail-placeholder .placeholder-icon svg {
  width: 120px; height: auto; display: inline-block;
}

.detail-placeholder p { margin: 0; color: var(--muted); }


/* Empty state — scoped to the #empty-state div on inspector_page.html
   (the placeholder shown above the email list when the result set is
   empty or a mailbox hasn't been picked). Previously written as a
   bare `.empty` class, but the JS reuses the word `empty` as a status-
   icon variant token (`<span class="ce-icon empty">`) and as a counts-
   row state token (`<span class="ce-state empty">`). Same-specificity
   collision: the bare `.empty` rule was leaking padding:24px + italic
   onto BOTH the status badge (rendering it ~50×50 instead of 25×25
   because of the padding) AND the "no invoices to process" text span
   (pushing it 24px away from its neighbors in .ce-counts). Scoping
   via the ID confines the utility to its intended target and
   eliminates the collision. */
#empty-state { padding: 24px; color: var(--muted); font-style: italic; text-align: center; }

#empty-state:empty { display: none; }

/* Mailbox-required call-out — surfaces in the email-list area when no
   mailbox is selected yet. Red box treatment because the page can't
   show anything until the user picks one. */
#empty-state.required-mailbox {
  margin: 12px 16px;
  padding: 12px 16px;
  background: var(--red-bg-soft);
  border: 1px solid var(--red);
  color: var(--red-d);
  border-radius: 6px;
  font-style: normal;
  font-weight: 500;
  text-align: left;
}


/* ---------- Pagination ----------
   On wide viewports: summary left, controls right (space-between).
   On narrow viewports the controls cluster wraps (flex-wrap: wrap)
   so the page-size selector + Prev/Next stay on one row when they
   fit, then break to a second row on iPhone-class widths. */
.pagination {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; border-top: 1px solid var(--surface-4); background: var(--surface-2);
  flex-wrap: wrap; gap: 8px 12px;
}

.pagination .summary { color: var(--muted); font-size: 13px; }

.pagination .controls {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;
}

.pagination .controls .actionBtn { padding: 4px 12px; font-size: 13px; }

.pagination .controls .actionBtn:disabled { opacity: 0.5; cursor: not-allowed; }

.pagination .page-indicator { font-size: 13px; color: var(--ink-3); padding: 0 8px; }

/* Page-size selector — sits to the left of the Prev/Next cluster.
   "Per page" label + a slim <select>. The label is inline so the
   widget reads as one chip on wide viewports; on iPhone the whole
   pagination wraps and the label sits above the select. */
.pagination .page-size-field {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--ink-3);
}

.pagination .page-size-label {
  white-space: nowrap;
}

.pagination .page-size-select {
  padding: 3px 6px; border: 1px solid var(--line-strong); border-radius: 4px;
  background: var(--surface); color: var(--ink);
  font-size: 13px; font-family: inherit;
  cursor: pointer;
}

.pagination .page-size-select:focus {
  outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent);
}


/* ---------- Combined detail panel ---------- */
.detail {
  background: var(--surface); border-radius: 9px; overflow: hidden;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04); margin-bottom: 20px;
}

.detail-header {
  background: linear-gradient(180deg, var(--accent) 0%, var(--accent-d) 100%);
  color: var(--on-accent); padding: 12px 20px;
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px;
}

.detail-header h2 { margin: 0; font-size: 16px; color: var(--on-accent); }

.detail-summary { font-size: 13px; color: rgba(255,255,255,0.9); }

.detail-body { padding: 16px 20px; }


/* Collapsible sections inside the detail.
   Per-section variant classes color the summary header so the panel reads
   at a glance: blue=Header, slate=Body, green=Invoice documents, amber=Invalid. */
details.section {
  margin-top: 12px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface);
  overflow: hidden;
}

details.section:first-of-type { margin-top: 0; }

details.section[open] { box-shadow: 0 1px 2px rgba(0,0,0,0.03); }

details.section > summary {
  cursor: pointer; padding: 10px 14px; font-weight: 600;
  font-size: 14px; user-select: none; list-style: none;
  display: flex; align-items: center; gap: 10px;
  border-left: 4px solid transparent;
}

details.section > summary::-webkit-details-marker { display: none; }

details.section > summary::before { content: "▸"; transition: transform 0.15s; font-size: 12px; }

details.section[open] > summary::before { transform: rotate(90deg); }

details.section > summary .count { color: var(--muted); font-weight: 500; font-size: 13px; }

details.section > .section-body { padding: 12px 16px 16px; border-top: 1px solid var(--surface-4); background: var(--surface); }


/* Per-section header variants */
details.section.sec-header  > summary { background: var(--accent-bg); color: var(--accent-strong); border-left-color: var(--accent); }

details.section.sec-header  > summary::before { color: var(--accent-strong); }

details.section.sec-message > summary { background: var(--surface-3); color: var(--ink-2); border-left-color: var(--muted-2); }

details.section.sec-message > summary::before { color: var(--muted); }

/* Round 65: Body nests inside the "Email Details" section. Small top
   margin separates it from the header dl above. */
details.section .email-body-nested { margin-top: 12px; }

details.section.sec-docs    > summary { background: var(--green-bg-soft); color: var(--green-d); border-left-color: var(--green); }

details.section.sec-docs    > summary::before { color: var(--green-d); }

/* Invalid/rejected — neutral gray. These attachments are dropped from
   processing, not flagged as errors; using a warning/amber tone over-
   stated their severity. Gray reads as "skipped / ignored". */
details.section.sec-invalid > summary { background: var(--surface-3); color: var(--ink-2); border-left-color: var(--muted-2); }

details.section.sec-invalid > summary::before { color: var(--muted); }

/* Plain sections (no variant) keep the original blue tone */
details.section:not(.sec-header):not(.sec-message):not(.sec-docs):not(.sec-invalid):not(.advanced) > summary { color: var(--accent); }

details.section:not(.sec-header):not(.sec-message):not(.sec-docs):not(.sec-invalid):not(.advanced) > summary::before { color: var(--muted); }


/* Email headers list */
.email-headers { margin: 0; display: grid; grid-template-columns: 110px 1fr; gap: 6px 14px; }

.email-headers dt { font-weight: 600; color: var(--ink-3); font-size: 13px; }

.email-headers dd { margin: 0; color: var(--ink); font-size: 14px; }

.email-headers .ts-pair {
  display: flex; flex-direction: column; gap: 0; line-height: 1.3;
}

/* Both UTC and EST lines share the same font-size so their character
   columns line up (the EST line used to be 12px while UTC was 13px,
   which made the two timestamps drift by ~half a character per
   digit). Color contrast still distinguishes the primary UTC value
   from the EST companion. */
.email-headers .ts-pair .ts-utc { color: var(--ink); font-size: 13px; }

.email-headers .ts-pair .ts-est { color: var(--muted); font-size: 13px; }


/* Body */
.email-body {
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 4px;
  padding: 12px; font-size: 13px; white-space: pre-wrap;
  max-height: 320px; overflow: auto; color: var(--ink);
}

iframe.email-html {
  width: 100%; height: 280px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface);
}


/* Invoice document cards (collapsible <details>) */
.invoice-doc-wrap {
  border: 1px solid var(--line-strong); border-radius: 6px; margin-bottom: 12px;
  background: var(--surface-2); overflow: hidden;
}

.invoice-doc-wrap:last-child { margin-bottom: 0; }

/* Round 57: two-row summary. Row 1 carries identity (caret + invoice
   number + filename + page count). Row 2 is a fixed-grid status strip —
   flex slot on the left for context (e.g. Review Time Expired pill),
   then right-anchored fixed slots for page-range / status / duration.
   Each fixed slot's min-width holds its widest expected content so the
   layout doesn't shift when a slot is empty.
   The legacy `.doc-status { margin-left: auto; }` flex anchor that the
   single-row layout used is gone — the grid handles positioning now. */
.invoice-doc-wrap > summary.invoice-doc-header {
  padding: 10px 14px; cursor: pointer; user-select: none;
  display: flex; flex-direction: column; gap: 6px;
  background: var(--surface); list-style: none;
}

.invoice-doc-row-top {
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
  min-width: 0;
}
.invoice-doc-row-top .invoice-number {
  font-weight: 700; color: var(--ink); font-size: 13px;
  letter-spacing: 0.2px; flex-shrink: 0;
}
/* Round 72-d: badge always renders (JS emits "INVOICE #: —" when the
   value is blank) so the header layout stays visually consistent
   across cards. The previous `:empty { display: none; }` rule is
   gone — kept here as a comment marker so the next reader knows
   the badge-always behavior was intentional, not a regression. */
.invoice-doc-row-top .invoice-pages {
  color: var(--muted); font-size: 12px; flex-shrink: 0;
}
.invoice-doc-row-top .invoice-pages:empty { display: none; }
.invoice-doc-row-top .sep {
  color: var(--faint); font-size: 13px; flex-shrink: 0;
}
/* Hide adjacent separator when its preceding element collapsed. JS
   already suppresses the markup for absent values, but the rule keeps
   the layout safe from orphan dots in degenerate cases. */
.invoice-doc-row-top .invoice-number:empty + .sep,
.invoice-doc-row-top .invoice-pages:empty + .sep { display: none; }

/* Round 69: drop the fixed-width placeholder slots. Slots shrink to
   content size; empty slots disappear entirely (no reserved gap);
   filled slots right-float. Context slot still left-anchored via
   margin-right: auto when present. Replaces the old 4-col grid
   (1fr auto auto auto with min-widths 100/110/80) — that approach
   reserved space even when a slot was empty, which created cut-off
   on narrow viewports and dead space everywhere else. */
.invoice-doc-row-bottom {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px 10px;
  justify-content: flex-end;
}
.invoice-doc-row-bottom > .slot {
  display: flex; align-items: center; min-width: 0;
}
.invoice-doc-row-bottom > .slot:empty { display: none; }
.invoice-doc-row-bottom > .slot-context {
  margin-right: auto;
  justify-content: flex-start;
}

.invoice-doc-wrap > summary::-webkit-details-marker { display: none; }

.invoice-doc-wrap[open] > summary.invoice-doc-header { border-bottom: 1px solid var(--line); }

.invoice-doc-wrap summary .caret { color: var(--muted); font-size: 12px; transition: transform 0.15s; }

.invoice-doc-wrap[open] summary .caret { transform: rotate(90deg); color: var(--accent); }

.invoice-doc-filename {
  font-family: 'Menlo','Monaco',monospace; font-size: 13px;
  color: var(--ink-strong); flex: 1; word-break: break-all;
}

.invoice-doc-meta { color: var(--muted); font-size: 12px; }

.invoice-doc-body { padding: 12px 14px; }

.invoice-doc-body .label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--muted); margin: 4px 0 8px;
}


/* Split-invoice rows nested inside an invoice-doc */
.split-invoices { display: flex; flex-direction: column; gap: 8px; }

.split-invoice {
  background: var(--surface); border-left: 3px solid var(--accent);
  padding: 8px 12px; border-radius: 0 4px 4px 0;
  display: grid; grid-template-columns: 1fr auto auto;
  gap: 12px; align-items: center;
}

.split-invoice .name {
  font-family: 'Menlo','Monaco',monospace; font-size: 12px; color: var(--ink-2);
}

.split-invoice .meta { color: var(--muted); font-size: 12px; }

.split-invoice .actions { display: flex; gap: 8px; }

.split-invoice a { color: var(--accent); text-decoration: none; font-size: 12px; }

.split-invoice a:hover { text-decoration: underline; }


/* Per-invoice workflow shown under each split child (the child has its OWN
   SF execution, distinct from the parent). Kept visually nested so the row
   relationship reads at a glance. */
.split-invoice-workflow {
  margin: 4px 0 12px 16px;
  padding: 6px 10px;
  background: var(--accent-bg-3);
  border-left: 2px solid var(--muted-2);
  border-radius: 0 4px 4px 0;
}

.split-invoice-workflow .label { margin-top: 0 !important; }

.split-invoice-workflow .flowchart .stage { min-width: 95px; padding: 6px 8px; }


/* PDF modal overlay — full-screen scrim with a centered iframe so the
   user can preview the converted PDF without leaving the detail panel. */
.pdf-modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(15, 23, 42, 0.6);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}

.pdf-modal-overlay[hidden] { display: none; }

.pdf-modal {
  background: var(--surface); border-radius: 8px; box-shadow: 0 12px 40px rgba(0,0,0,0.3);
  width: 100%; max-width: 1100px; height: 100%; max-height: 90vh;
  display: flex; flex-direction: column; overflow: hidden;
}

.pdf-modal-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 16px; border-bottom: 1px solid var(--line); background: var(--surface-2);
  flex-shrink: 0;
}

.pdf-modal-title {
  font-family: 'Menlo','Monaco',monospace; font-size: 13px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pdf-modal-actions { display: flex; align-items: center; gap: 14px; }

.pdf-modal-newtab {
  font-size: 12.5px; color: var(--accent); text-decoration: none;
}

.pdf-modal-newtab:hover { text-decoration: underline; }

.pdf-modal-close {
  background: transparent; border: 0; cursor: pointer;
  font-size: 22px; line-height: 1; color: var(--muted); padding: 0 4px;
}

.pdf-modal-close:hover { color: var(--ink); }

.pdf-modal-frame { flex: 1; width: 100%; border: 0; background: var(--viewer-frame); }


/* Split-child marker — shown on doc cards whose filename matches the
   `<stem>_split_<a>-<b>.<ext>` pattern document_splitting_process writes.
   The card itself gets a left rail in indigo so siblings vs. split
   children are distinguishable in the doc list at a glance. */
.invoice-doc-wrap.is-split { border-left: 3px solid var(--indigo); }

.split-badge {
  display: inline-block; padding: 2px 8px; border-radius: 4px;
  background: var(--accent-bg-3); color: var(--indigo-d); border: 1px solid var(--indigo-border);
  font-size: 11px; font-weight: 600; font-family: 'Menlo','Monaco',monospace;
}
/* Clickable "Pages a-b" badge — opens the PDF (same action as the PDF icon). */
a.split-badge-link { text-decoration: none; cursor: pointer; }
a.split-badge-link:hover { background: var(--indigo-bg); border-color: var(--indigo-border); }

.split-source {
  margin: 0 0 10px 0; padding: 8px 12px;
  background: var(--accent-bg-3); border-left: 3px solid var(--indigo); border-radius: 0 4px 4px 0;
  font-size: 12.5px; color: var(--indigo-d);
}

.split-source code {
  font-family: 'Menlo','Monaco',monospace; font-size: 12px; color: var(--indigo-d);
  background: transparent; padding: 0 2px;
}


/* Document status pills inside cards */
.doc-status {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600;
}

.doc-status.PROCESSED { background: var(--green-bg); color: var(--green-d); }

.doc-status.RUNNING   { background: var(--accent-bg-2); color: var(--accent-strong); }

.doc-status.FAILED    { background: var(--red-bg); color: var(--red-d); }

.doc-status.REJECTED  { background: var(--surface-3); color: var(--ink-3); }

/* Backend-pagination footer for the Invoice Documents section. Rendered
   below the doc-card stack when the email has more invoices than the
   currently-selected page size — see renderDocsPaginationFooter in
   inspector-detail.js. Three children:
     · .docs-pagination-status — "Showing X of Y invoices" (left-aligned)
     · .docs-pagination-size   — "Page size: [25 ▼]" (chooses next batch)
     · .docs-load-more         — "Load more (N remaining)" button (right)
   Flex layout with `justify-content: space-between` so the load-more
   button anchors to the right edge while the status text stays at the
   left. Wraps to two rows on narrow viewports so the controls don't
   overflow. */
.docs-pagination-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  margin: 12px 4px 4px;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: 12.5px;
  color: var(--ink-3);
}
.docs-pagination-status { font-weight: 500; color: var(--ink); }
.docs-pagination-size {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted);
}
.docs-pagination-size select {
  padding: 2px 4px;
  border: 1px solid var(--line-strong); border-radius: 4px;
  background: var(--surface);
  font-size: 12px;
  cursor: pointer;
}
.docs-load-more {
  margin-left: auto;
  padding: 6px 14px;
  background: var(--accent-strong); color: var(--on-accent);
  border: 0; border-radius: 4px;
  font-size: 12.5px; font-weight: 600;
  cursor: pointer;
}
.docs-load-more:hover { background: var(--accent-deep); }
.docs-load-more:disabled {
  background: var(--muted-2); cursor: wait;
}
/* Inline spinner shown on the Load-more button while the next page fetches. */
.docs-load-more .btn-spin {
  display: inline-block; width: 12px; height: 12px; margin-right: 7px;
  vertical-align: -2px; border: 2px solid rgba(255, 255, 255, 0.5);
  border-top-color: var(--on-accent); border-radius: 50%; animation: spin 0.8s linear infinite;
}


/* STATUS_UNAVAILABLE — backend couldn't fetch SF execution history for
   this doc (IAM, throttle, timeout, stale ARN, or 20-page pagination
   cap). Distinct gray-with-amber-text treatment so operators can tell
   it apart from the four happy statuses above and the warning-amber
   bypass-outcome pill below; the cursor-help + tooltip on the element
   itself directs to CloudWatch for diagnosis. Dashed border reinforces
   the "uncertain data" signal — different visual vocabulary from the
   solid pills, which carry confirmed states. */
.doc-status.STATUS_UNAVAILABLE {
  background: var(--surface-3); color: var(--amber-d);
  border: 1px dashed var(--amber);
  padding: 1px 7px;  /* compensate for the 1px border so size matches the others */
  cursor: help;
}

/* Per-invoice processing-duration pill — sits next to the status pill in
   the doc header. Neutral gray (NOT a status color) so it reads as a
   metric rather than another state. Monospace so durations align
   vertically when stacking invoice cards (4m 14s next to 56.6s next to
   1h 4m). The ⏱ glyph mirrors the email-card duration badge so the
   same visual language carries from list to detail. Same auto-format
   scale used everywhere else (s / m+s / h+m). */
.doc-duration {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  background: var(--surface-3); color: var(--ink-3);
  font-size: 11px; font-weight: 600;
  font-family: 'Menlo','Monaco',monospace;
  margin-left: 6px; white-space: nowrap;
}

/* Bypassed-validation warning. Surfaces when the final JSON shows
   requires_validation=true + validation_method="bypassed" while the
   mailbox config has bypass_a2i=false — i.e. the invoice was supposed
   to be human-reviewed but the a2i loop timed out and the pipeline
   moved on without a reviewer. Amber, NOT red: the invoice still
   reached PROCESSED, just without the safety check the config asked
   for. Pill sits NEXT TO the existing doc-status; both render. */
.review-warning {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px;
  background: var(--amber-bg); color: var(--amber-d);
  border: 1px solid var(--amber);
  font-size: 11px; font-weight: 600;
  margin-left: 6px;
  white-space: nowrap;
}
/* Expanded form of the warning — shown inside the invoice card body so
   the user reading the captured fields sees WHY they should give this
   one a second look. Sits above the action links. */
.review-skipped-banner {
  margin: 0 0 12px;
  padding: 10px 14px;
  background: var(--amber-bg-soft);
  border-left: 3px solid var(--amber);
  border-radius: 4px;
  color: var(--amber-d);
  font-size: 12.5px;
  line-height: 1.5;
}
.review-skipped-banner strong { color: var(--amber-d); }
.review-skipped-banner code {
  background: var(--amber-bg); padding: 1px 4px; border-radius: 3px;
  font-family: 'Menlo', 'Monaco', monospace; font-size: 11.5px;
  color: var(--amber-d);
}


/* Invalid attachments — 3-column row: [filename] [extension] [reason].
   Neutral gray palette: these items are skipped, not failures, so the
   visual tone should read as "ignored" rather than "needs attention". */
.invalid-attachment {
  display: grid; grid-template-columns: 1fr auto 2fr; align-items: center;
  gap: 14px; padding: 10px 14px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 4px;
  margin-bottom: 6px;
}

.invalid-attachment:last-child { margin-bottom: 0; }

.invalid-attachment .name {
  font-family: 'Menlo','Monaco',monospace; font-size: 12px; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.invalid-attachment .ext {
  font-family: 'Menlo','Monaco',monospace; font-size: 11.5px;
  background: var(--surface-3); color: var(--ink-3); padding: 2px 8px; border-radius: 4px;
  font-weight: 600; white-space: nowrap;
}

.invalid-attachment .reason { color: var(--muted); font-size: 12px; }


/* "Accepted attachment types" hint at the top of the Invalid attachments
   section — surfaces what WOULD have been accepted under the current
   mailbox configuration, with a note that config may have drifted. */
.accepted-extensions-note {
  margin: 0 0 10px 0; padding: 10px 14px;
  background: var(--surface-2); border-left: 3px solid var(--muted-2); border-radius: 0 4px 4px 0;
  font-size: 12.5px; color: var(--ink-2); line-height: 1.5;
}

.accepted-extensions-note strong { color: var(--ink); font-weight: 600; display: block; margin-bottom: 4px; }

.accepted-extensions-note code {
  display: block; font-family: 'Menlo','Monaco',monospace; font-size: 12px;
  color: var(--ink); padding: 4px 0;
}

.accepted-extensions-note em { display: block; margin-top: 6px; color: var(--muted); font-size: 11.5px; }


/* Advanced (low-key) section */
details.advanced > summary { color: var(--muted); font-weight: 500; }

details.advanced > summary:hover { color: var(--accent); }


/* ============================================================
   Captured fields table (Option A)
   INVOICE_RECEIPT_ID pinned top + bold; header_keys highlighted
   above a divider; all other Textract fields below in muted style.
   Outer chrome (border, summary, caret) inherited from .invoice-sub.
============================================================ */
details.captured-fields .cf-title { flex: 1; }

details.captured-fields .cf-meta {
  display: flex; gap: 6px; font-weight: 400;
}

.cf-meta-pill {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.2px;
  background: var(--line); color: var(--ink-3);
}

.cf-meta-pill.cf-meta-edit { background: var(--violet-bg); color: var(--violet-d); }

/* "Below threshold" pill — amber, deliberately not red. */
.cf-meta-pill.cf-meta-low  { background: var(--amber-bg); color: var(--amber-d); }

/* header_only flag pill — always shown so the config mode is explicit. */
.cf-meta-pill.cf-meta-ho-no  { background: var(--accent-bg-3); color: var(--accent-strong); }
.cf-meta-pill.cf-meta-ho-yes { background: var(--amber-bg); color: var(--amber-d); }

/* Line-item cell (Option A): value, then optional original, then a small
   confidence pill, stacked. */
.cf-lines-table td.cf-li-cell { vertical-align: top; }
.cf-li-cell .cf-li-v { font-weight: 600; }
.cf-li-cell .cf-li-conf { margin-top: 3px; }
.cf-lines-note { color: var(--muted); font-size: 12px; font-style: italic; padding: 8px 2px; }


.cf-wrap { overflow-x: auto; }

.cf-table {
  width: 100%; border-collapse: collapse;
  font-size: 12.5px; color: var(--ink);
}

.cf-table th {
  background: var(--surface-2); color: var(--muted); font-weight: 600;
  text-align: left;
  padding: 6px 12px;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.cf-table th.cf-th-conf { width: 90px; text-align: right; }

.cf-table td {
  padding: 7px 12px; vertical-align: middle;
  border-bottom: 1px solid var(--surface-3);
}

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

.cf-table td.cf-field {
  font-family: 'Menlo','Monaco',monospace; font-size: 11.5px;
  color: var(--ink-3); font-weight: 500;
  white-space: nowrap;
}

/* Field name column shrinks to its actual content (the widest realistic
   key is INVOICE_RECEIPT_ID ≈ 16 monospace characters) instead of
   getting an equal share alongside Value + Original. Frees space for
   the Value column where the operationally-meaningful data lives.
   Uses the standard CSS table "1% + nowrap" trick — width:1% asks for
   the smallest possible fraction, the white-space:nowrap on .cf-field
   above keeps the natural content width as the floor. */
.cf-table td.cf-field,
.cf-table th.cf-th-field { width: 1%; }

.cf-table td.cf-val { color: var(--ink); }

.cf-table td.cf-val.cf-empty { color: var(--faint); font-style: italic; }

.cf-table td.cf-orig {
  color: var(--muted-2); font-style: italic;
  font-family: 'Menlo','Monaco',monospace; font-size: 11.5px;
  white-space: nowrap;
}

.cf-table td.cf-orig s { color: var(--muted-2); text-decoration: line-through; }

.cf-table td.cf-orig:empty::before { content: "—"; color: var(--faint); font-style: normal; }

.cf-edit-mark {
  margin-left: 6px;
  color: var(--violet-d); font-style: normal; font-size: 11px;
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}

.cf-table td.cf-conf { width: 90px; text-align: right; }


/* Confidence pills */
.cf-pill {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 600; line-height: 1.4;
  font-family: 'Menlo','Monaco',monospace;
}

/* Two-tier confidence pill: cf-pill-high = at-or-above client_config.min_confidence_score,
   cf-pill-med = below. No red tier by design. */
.cf-pill-high { background: var(--green-bg); color: var(--green-d); }

.cf-pill-med  { background: var(--amber-bg); color: var(--amber-d); }

.cf-pill-na   { background: var(--surface-3); color: var(--muted-2); font-style: italic; font-family: inherit; }
/* Round 72-d: red em-dash pill for synthesized fields — Textract
   didn't capture the value, decide_human_validation injected a
   placeholder with confidence_score=0 to force HITL. The visible
   pill content is just an em-dash so the cell stays visually
   quiet; the red color does the talking. The outer captured-
   fields summary's "<n> not captured" meta pill is where the
   operator reads the word-form label. Distinct from .cf-pill-na
   (gray) which marks fields naturally absent where no review was
   expected. Red palette echoes .cf-pill-low so HITL-trigger
   cells stay in the same hue family. */
.cf-pill-not-captured {
  background: var(--red-bg); color: var(--red-d);
  font-style: italic; font-family: inherit;
  letter-spacing: 0;
}


/* Highlighted rows for header_keys (the "key" fields validator checks) */
.cf-table tr.cf-key { background: var(--accent-bg-3); }

.cf-table tr.cf-key:hover { background: var(--accent-bg-3); }

.cf-table tr.cf-key td.cf-field { color: var(--accent-strong); }


/* HITL-edited rows get a soft left-rail accent */
.cf-table tr.cf-edited td:first-child { box-shadow: inset 3px 0 0 var(--violet-d); }


/* Low-confidence rows: subtle warning tint overlay. Round 59 scoped
   this to the PRIMARY table only via .cf-table-primary — low confidence
   on Additional fields is informational and intentionally shouldn't
   shout (those fields don't trigger HITL). */
.cf-table-primary tr.cf-low-row { background: var(--amber-bg-soft); }

.cf-table-primary tr.cf-low-row.cf-key { background: var(--amber-bg-soft); }

.cf-table-primary tr.cf-low-row:hover { background: var(--amber-bg); }

/* Round 72-d: synthesized rows (fields the pipeline injected because
   they were absent from captured headers and configured in
   client_config.priority_fields). Soft red row background + red
   left-rail accent so the row reads as "this field was NOT CAPTURED
   and needs human attention" — distinct from low-confidence (amber)
   and HITL-edited (purple). Three states read at a glance:
       purple rail = HITL-edited
       amber tint  = low confidence (real)
       red rail    = not captured (synthesized)
   Scoped to .cf-table-primary because synthesized fields only land
   in Primary (Additional fields aren't in priority_fields by
   design). The .cf-pk override below wins when INVOICE_RECEIPT_ID
   is the synthesized field — its primary-key blue background loses
   to synthetic-red. */
.cf-table-primary tr.cf-synthetic {
  background: var(--red-bg-soft);
}
.cf-table-primary tr.cf-synthetic:hover {
  background: var(--red-bg);
}
.cf-table-primary tr.cf-synthetic td:first-child {
  box-shadow: inset 3px 0 0 var(--red-d);
}
.cf-table-primary tr.cf-synthetic.cf-pk {
  background: var(--red-bg-soft);
}
.cf-table-primary tr.cf-synthetic.cf-pk td.cf-field,
.cf-table-primary tr.cf-synthetic.cf-pk td.cf-val {
  color: var(--red-d);
}


/* INVOICE_RECEIPT_ID — primary key, pinned top, bold, deepest tint */
.cf-table tr.cf-pk {
  background: var(--accent-bg-2);
  box-shadow: inset 0 -1px 0 var(--accent-border);
}

.cf-table tr.cf-pk:hover { background: var(--accent-bg-2); }

.cf-table tr.cf-pk td.cf-field {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--accent-deep);
  letter-spacing: 0.3px;
}

.cf-table tr.cf-pk td.cf-val {
  font-weight: 700; color: var(--accent-deep); font-size: 13.5px;
}


/* Round 59: Primary / Additional sub-sections inside Captured fields.
   Primary is always visible (cf-subsection.cf-primary); Additional is
   a nested <details class="cf-additional"> defaulting to closed.
   .cf-divider is no longer emitted by JS (the divider's job is done by
   the sub-section headers) but the rule above is left in place in case
   any legacy markup lingers — it's a no-op when absent. */
.cf-subsection { margin-top: 6px; }
.cf-subsection:first-child { margin-top: 0; }

.cf-subhead {
  display: flex; align-items: baseline; gap: 10px;
  padding: 10px 12px 6px;
}

.cf-subtitle {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;
  font-weight: 700; color: var(--ink);
}

.cf-subhint {
  font-size: 11px; color: var(--muted); font-style: italic;
  font-weight: 400;
}

/* Additional fields — collapsible inner <details>. Cosmetically lighter
   than the .invoice-sub parent so it reads as a sub-sub-section. */
details.cf-additional {
  margin-top: 4px;
  border-top: 1px solid var(--surface-3);
  background: var(--surface);
}
details.cf-additional > summary {
  /* Only the chevron+title hit-area (.sec-toggle) toggles; the hint
     and count pill on the rest of the bar keep the default cursor. */
  cursor: default; user-select: none;
  padding: 10px 12px;
  display: flex; align-items: baseline; gap: 10px;
  list-style: none;
}
details.cf-additional > summary::-webkit-details-marker { display: none; }
details.cf-additional > summary .sec-toggle::before {
  content: "▸"; color: var(--muted); transition: transform 0.15s;
  font-size: 11px; flex-shrink: 0;
}
details.cf-additional[open] > summary .sec-toggle::before { transform: rotate(90deg); }


/* Divider row between key-fields group and the rest (legacy — Round 59
   sub-sections replace this; rule retained as a no-op safety net). */
.cf-table tr.cf-divider td {
  background: var(--accent-bg-3);
  padding: 6px 12px;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--muted); font-weight: 600;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}


/* Line items — collapsed sub-block appended below the header table inside
   the Captured fields section. Lighter chrome than .invoice-sub because
   it's NESTED inside one; a single top separator and an inline summary
   row are enough to set it apart from the header table above. */
details.cf-lines { border-top: 1px solid var(--line); }

details.cf-lines > summary {
  /* Only the chevron+title hit-area (.sec-toggle) toggles; the count
     pills on the rest of the bar keep the default cursor. */
  cursor: default; user-select: none;
  padding: 8px 14px;
  background: var(--surface-2);
  font-size: 12.5px; color: var(--ink); font-weight: 600;
  display: flex; align-items: center; gap: 8px;
  list-style: none;
}

details.cf-lines > summary::-webkit-details-marker { display: none; }

details.cf-lines > summary .sec-toggle::before {
  content: "▸"; color: var(--muted); transition: transform 0.15s;
  font-size: 11px; flex-shrink: 0;
}

details.cf-lines[open] > summary .sec-toggle::before { transform: rotate(90deg); }

/* Push the count pills to the right edge. Previously .cf-lines-title
   carried flex:1; now the title lives inside .sec-toggle (natural
   width, tight hover), so the pills get the auto-margin instead. */
details.cf-lines > summary .cf-meta { margin-left: auto; }

.cf-lines-table td .cf-orig {
  display: block; margin-top: 2px;
  /* The inline `.cf-orig` per-table-cell style applied to <td> doesn't
     fit a stacked Value/Original layout — re-state the strikethrough
     treatment for the inner <div> wrapper used by line cells. */
  color: var(--muted-2); font-style: italic; font-size: 11px;
}

.cf-lines-table td .cf-val.cf-empty { color: var(--faint); font-style: italic; }


/* Unified collapsible sub-section chrome — used by Final JSON, Captured
   Fields, Workflow and Raw execution data inside each invoice doc card.
   Same indentation, same caret, same summary treatment so all three read
   as siblings. */
details.invoice-sub {
  margin-top: 12px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
}

details.invoice-sub:first-child { margin-top: 0; }

details.invoice-sub > summary {
  cursor: pointer; user-select: none;
  padding: 10px 14px;
  background: var(--surface-2);
  font-size: 13px; color: var(--ink); font-weight: 600;
  display: flex; align-items: center; gap: 8px;
  list-style: none;
}

details.invoice-sub > summary::-webkit-details-marker { display: none; }

details.invoice-sub > summary::before {
  content: "▸"; color: var(--muted); transition: transform 0.15s;
  font-size: 11px; flex-shrink: 0;
}

details.invoice-sub[open] > summary::before { transform: rotate(90deg); }

/* Round 55: invoice sub-section titles (Workflow / Captured fields /
   Final JSON) read as ALL CAPS for visual hierarchy. CSS transform
   only — underlying text stays normal case so screen readers don't
   shout. Small letter-spacing bump improves cap legibility at 13px. */
details.invoice-sub > summary .sub-title {
  flex: 1;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

details.invoice-sub > summary .sub-meta {
  color: var(--muted); font-weight: 400; font-size: 11.5px;
}

details.invoice-sub > pre.json {
  background: var(--code-bg); color: var(--code-fg); padding: 14px 16px; border-radius: 0;
  font-size: 12px; font-family: 'Menlo','Monaco',monospace; white-space: pre-wrap;
  max-height: 360px; overflow: auto; margin: 0;
}


/* Actions strip across the top of the doc body. SF-execution name on the
   LEFT (full identifier, monospace, ellipsis when it overflows), View
   PDF anchored to the RIGHT via margin-left:auto. Retry pill (when the
   execution failed) sits between them. */
.invoice-actions {
  display: flex; align-items: baseline; gap: 16px;
  padding: 0 2px 10px;
  font-size: 12px;
}
.invoice-actions a { color: var(--accent); text-decoration: none; }
.invoice-actions a:hover { text-decoration: underline; }
/* SF execution name — left side. flex:1 so it absorbs available width
   and ellipses when the name is wider than the row (the live execution
   name is up to 80 chars). Monospace because it's an identifier the
   operator might copy-paste into AWS console or CloudWatch search. */
.invoice-actions .exec-link {
  flex: 0 1 auto; min-width: 0;
  font-family: 'Menlo','Monaco',monospace;
  font-size: 11.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Round 62: "(IT ONLY)" label sits OUTSIDE the <a> so it isn't part
   of the clickable link. Muted gray so the link stays the primary
   visual anchor; em-dash separator lives in the markup itself. */
.invoice-actions .exec-it-only {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.2px;
  text-transform: uppercase;
}
/* View PDF — far right, never shrinks. */
.invoice-actions .pdf-link {
  flex: 0 0 auto;
  margin-left: auto;
}


/* "Final JSON missing" empty state — rendered inside the invoice-sub
   body when the artifact is missing in S3. */
.invoice-final-json-empty {
  padding: 10px 14px; font-size: 12px; color: var(--muted);
  background: var(--surface-2);
}

.invoice-final-json-empty code {
  font-family: 'Menlo','Monaco',monospace; font-size: 11.5px; color: var(--ink-3);
}


/* ============================================================
   Workflow steps table — replaces the old `.flowchart` strip
   inside each invoice doc card. Each state spans TWO rows:
     step-row    = status rail (rowspan=2) + step name spanning
                   the Entered-UTC / Entered-EST / Duration columns
     detail-row  = the three data cells aligned to the header
   The rail and dot center vertically across both rows.
   ============================================================ */
.wf-steps {
  width: 100%; border-collapse: collapse;
  background: var(--surface);
  font-size: 13px;
}

/* When the workflow table sits directly inside the .invoice-sub chrome
   the surrounding border + radius come from the parent details — drop
   any standalone outer border/radius from the table itself. */
details.invoice-sub > .wf-steps { border: 0; border-radius: 0; margin-top: 0; }

.wf-steps th {
  background: var(--surface-2); color: var(--muted); font-weight: 600;
  text-align: left; padding: 8px 12px;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px;
  border-bottom: 1px solid var(--line);
}

.wf-steps th.wf-rail-col { width: 56px; }


.wf-steps td { padding: 6px 12px; vertical-align: middle; }

/* Step name occupies the full data width on its own row */
.wf-steps tr.step-row td.wf-step {
  font-weight: 600; color: var(--ink); padding-top: 10px;
}

.wf-steps tr.failed  td.wf-step { color: var(--red-d); }

.wf-steps tr.running td.wf-step { color: var(--accent-strong); }

/* Detail row sits flush under the step name so the pair reads as one state */
.wf-steps tr.detail-row td { padding-top: 0; padding-bottom: 10px; }


/* Visual separator between state groups (no separator within a pair) */
.wf-steps tbody tr.step-row { border-top: 1px solid var(--surface-3); }

.wf-steps tbody tr.step-row:first-child { border-top: 0; }


/* Continuous rail line through the status column. The rail cell spans
   both rows of the state via rowspan=2 in the HTML, so the dot centers
   vertically across the pair. */
.wf-steps .wf-rail {
  position: relative; text-align: center; padding: 8px 0;
  width: 56px;
}

.wf-steps .wf-rail::before {
  content: ""; position: absolute;
  left: 50%; top: 0; bottom: 0; width: 2px;
  background: var(--muted-2); transform: translateX(-1px);
}

/* Trim the rail at the very first and very last dots. The `last-step`
   class is added explicitly by inspector-detail.js to the final
   step-row — the previous `tr.step-row:last-of-type` selector matched
   nothing in practice because each step renders two <tr>s
   (step-row + detail-row), so the last <tr> in the tbody is always
   a detail-row. The `:last-of-type` rule is left in place as a
   harmless backup in case any caller hand-rolls the table without
   the explicit class. */
.wf-steps tbody tr.step-row:first-child .wf-rail::before { top: 50%; }

.wf-steps tbody tr.step-row.last-step .wf-rail::before  { bottom: 50%; }
.wf-steps tbody tr.step-row:last-of-type .wf-rail::before { bottom: 50%; }


/* Status dot — Round 51 swap. Reuses the .ce-icon hollow-ring
   treatment from the email-card bullets so the workflow row and
   the email row read with the same visual vocabulary. 25×25 ring
   with a 1px border + Tailwind -50 tinted fill; the darker
   semantic shade colors both the border and the Unicode glyph
   inside. z-index:1 keeps the opaque background in front of the
   rail line so the rail visually breaks at each dot but still
   connects steps above and below. */
.wf-steps .wf-dot {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  width: 25px; height: 25px; border-radius: 50%;
  box-sizing: border-box;
  border: 1px solid transparent;
  font-size: 12px; font-weight: 700; line-height: 1;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* Default = done. Glyph color matches border so the ring + symbol
   read as one stroke (same pattern as .ce-icon). */
.wf-steps .wf-dot {
  background: var(--green-bg-soft); border-color: var(--green-d); color: var(--green-d);
}

.wf-steps tr.failed  .wf-dot {
  background: var(--red-bg-soft); border-color: var(--red-d); color: var(--red-d);
}

.wf-steps tr.running .wf-dot {
  background: var(--accent-bg); border-color: var(--accent-strong); color: var(--accent-strong);
}

/* Amber "warning" variant for the Wait for Human Review Response
   step when hitl_timing classifies its outcome as bypass
   (timed_out / declined / stopped) — see isBypassOutcomeState in
   inspector-detail.js. The wait state Pass'd through technically
   but represents an operationally-unhappy outcome. Same Sun-
   Flower-ish amber palette used elsewhere for soft warnings. */
.wf-steps tr.warning .wf-dot {
  background: var(--amber-bg); border-color: var(--amber-d); color: var(--amber-d);
}


/* Workflow timestamps + durations — inherit the page sans-serif so they
   match the rest of the email-list / detail-panel timestamps. */
.wf-steps .wf-ts,
.wf-steps .wf-dur {
  color: var(--muted); font-size: 12px;
  white-space: nowrap;
}

/* Note: a previous attempt set `.wf-steps .wf-dur { width: 1%; }` to
   shrink the Duration column to its content width via the standard
   "1% + nowrap" trick. It broke the workflow view's column layout —
   the step-row's `<td class="wf-step" colspan="3">` covers cols 2-4
   inclusive, and the browser's column-width distribution under that
   colspan + a hard 1% constraint on one of the spanned columns
   produced inconsistent results. Reverted; the workflow now goes
   back to its previous equal three-column distribution. If shrink-
   to-fit is needed in the future, candidate fixes are: (a) drop
   the step-row's colspan and put the step name in a single column
   so the column-width math is unambiguous, or (b) set an explicit
   pixel min/max on each of the three columns instead of using the
   1% trick. */

.wf-steps tr.running .wf-dur.running {
  color: var(--accent-strong);
}

/* Round 77-b: HITL queue-wait vs. active-work split. The
   "Wait for Human Review Callback" SF state lumps two operator-
   meaningful durations into one wallclock (queue wait + reviewer
   active work). The sub-text renders underneath the main ⏱ value
   so operators can see how much of the wait was spent IN QUEUE
   versus the reviewer ACTIVELY WORKING. Smaller, muted, normal-
   weight so it visually defers to the main duration value. Only
   ever rendered for the HITL wait stage — backend attaches the
   `hitl_timing` block conditionally; the JS skips the span when
   absent. Source-agnostic: same shape for AWS A2I and custom
   onPhase HITL paths because both write identical
   metadata.a2i_metadata blocks into final.json. */
.wf-steps .wf-dur-sub {
  display: block;
  color: var(--muted);
  font-size: 11px;
  font-weight: 400;
  font-style: normal;
  white-space: nowrap;
  margin-top: 1px;
}
/* Suffix override: italic blue for the "(not started)" / "(in
   progress)" tail-text only. Exercised for in_progress wait
   stages where the reviewer hasn't accepted yet — the italic-blue
   mirrors the main "in progress…" treatment so the two read as a
   single live state. The "(in progress)" branch would only fire
   if we ever paired this with a live hitl_table lookup; the class
   is defined for symmetry + future use. */
.wf-steps .wf-dur-sub .wf-dur-sub-progress {
  color: var(--accent-strong); font-style: italic;
}
/* Suffix override: warning amber for the "Review expired" tail-text
   on the timed_out wait stage. Reuses the warning palette (var(--amber-d))
   from .wf-steps tr.warning .wf-dot so the wait stage's sub-text
   visually correlates with the warning treatment on the wait state's
   own step-row dot (driven by isBypassOutcomeState). Stays normal
   weight (no italic) — the wait stage itself technically Pass'd
   through so the row isn't in any in-progress visual; the suffix
   just reads "terminal + unhappy". */
.wf-steps .wf-dur-sub .wf-dur-sub-warning {
  color: var(--amber-d);
}

/* Workflow totals row — wraps the per-step rows in a tfoot. Wallclock
   = last step exit minus first step entry. Auto-formatted with the
   same scale used everywhere else (s / m+s / h+m). Bold, monospace,
   slightly tinted background so it reads as the summary row of the
   table. For RUNNING workflows the total reflects elapsed-so-far and
   stays in the in-progress blue style. For FAILED workflows the total
   stops at the failure step and inherits the failed red style. */
.wf-steps tfoot tr.wf-total td {
  background: var(--surface-2);
  border-top: 1px solid var(--line);
  padding: 12px 18px;
  font-weight: 600;
  color: var(--ink);
}
.wf-steps tfoot tr.wf-total td.wf-total-label {
  font-size: 11.5px; letter-spacing: 0.3px;
  text-transform: uppercase; color: var(--muted);
}
.wf-steps tfoot tr.wf-total td.wf-total-value {
  /* Round 78f: more right-padding + min-width so the duration value
     ("⏱ 4m 12s" / "⏱ 1h 14m") has breathing room and never crowds
     the cell edge or wraps awkwardly. */
  text-align: right;
  padding-right: 20px;
  min-width: 120px;
  font-family: 'Menlo','Monaco',monospace;
  font-size: 12.5px; color: var(--ink);
  white-space: nowrap;
}
/* In-progress total ("Elapsed so far ⏱ 2h 17m") stays in the
   running blue but drops italic — italicizing the ⏱ stopwatch
   glyph visibly distorted it. The per-step "in progress…" wf-dur
   cell stays italic (plain text, nothing to distort there). */
.wf-steps tfoot tr.wf-total.running td.wf-total-value { color: var(--accent-strong); }
.wf-steps tfoot tr.wf-total.failed  td.wf-total-value { color: var(--red-d); }


/* Bottom-of-workflow failure banner. Rendered by renderWorkflowFailureBanner
   in inspector-detail.js when inspector_app attaches a `failure_info` dict
   to an invoice (pipeline terminated via FailState with a real exception —
   NOT Declined/Stopped operational outcomes, which use the bypass banner).
   Sits OUTSIDE the .wf-steps <table>, so the column rhythm above does not
   extend into the banner; instead the banner uses its own full-width band
   with a red left-rail accent that visually echoes the dot column above.
   Color palette: soft red row tint (var(--red-bg-soft)) + red rail (var(--red-d)) + dark
   red title (var(--red-d)) — matches the .cf-synthetic "not captured" treatment
   so failures and missing fields read as the same severity family. */
.wf-failure-banner {
  border-left: 3px solid var(--red-d);
  background: var(--red-bg-soft);
  border-radius: 0 6px 6px 0;
  padding: 10px 14px;
  margin-top: 8px;
  color: var(--ink);
  font-size: 13px;
  line-height: 1.45;
}
.wf-failure-banner .wf-failure-title {
  color: var(--red-d);
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 2px;
}
.wf-failure-banner .wf-failure-code {
  color: var(--red-d);
  font-family: 'Menlo','Monaco',monospace;
  font-size: 12px;
  margin-bottom: 4px;
}
.wf-failure-banner .wf-failure-cause-summary {
  color: var(--ink-2);
  word-break: break-word;
  margin-bottom: 4px;
}
/* <details> Show-details toggle — collapsed by default. Native <summary>
   marker on the left; small + tight to keep the banner compact. */
.wf-failure-banner .wf-failure-details {
  margin-top: 4px;
}
.wf-failure-banner .wf-failure-details > summary {
  /* The whole label lives inside .sec-toggle, so the summary itself
     keeps the default cursor and the native disclosure marker is
     hidden in favour of a custom caret (consistent with the other
     collapsible sections). */
  cursor: default;
  font-size: 12px;
  color: var(--red-d);
  user-select: none;
  list-style: none;
}
.wf-failure-banner .wf-failure-details > summary::-webkit-details-marker { display: none; }
.wf-failure-banner .wf-failure-details > summary .sec-toggle::before {
  content: "▸"; color: var(--red-d); transition: transform 0.15s;
  font-size: 10px; display: inline-block; width: 10px; text-align: center;
}
.wf-failure-banner .wf-failure-details[open] > summary .sec-toggle::before { transform: rotate(90deg); }
.wf-failure-banner .wf-failure-details > summary .sec-toggle:hover { text-decoration: underline; }
.wf-failure-banner .wf-failure-cause-full {
  margin: 6px 0 0;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--red-bg);
  border-radius: 4px;
  font-family: 'Menlo','Monaco',monospace;
  font-size: 11px;
  line-height: 1.4;
  white-space: pre-wrap;       /* preserve newlines in multi-line causes */
  word-break: break-word;      /* but wrap absurdly long lines so the
                                  panel doesn't horizontal-scroll */
  max-height: 320px;
  overflow: auto;
  color: var(--ink);
}


/* SF history flowchart */
.flowchart { display: flex; align-items: stretch; flex-wrap: wrap; gap: 6px; padding: 0.5rem 0 1rem; }

.flowchart .stage {
  display: flex; flex-direction: column; align-items: center;
  padding: 10px 14px; border: 1px solid var(--line-strong); border-radius: 8px;
  min-width: 160px; background: var(--surface-2);
}

.flowchart .stage .icon { font-size: 1.4rem; line-height: 1.4rem; }

.flowchart .stage .name { font-size: 0.75rem; color: var(--ink-3); margin-top: 4px; text-align: center; font-weight: 600; }

.flowchart .stage .ts {
  font-size: 0.65rem; color: var(--muted-2); margin-top: 4px; text-align: center;
  font-family: 'Menlo','Monaco',monospace; line-height: 1.25;
}

.flowchart .stage .ts span { display: block; }

.flowchart .stage .ts .ts-utc { color: var(--ink-2); }

.flowchart .stage .ts .ts-est { color: var(--muted-2); }

.flowchart .stage .duration { font-size: 0.7rem; color: var(--ink-3); margin-top: 4px; font-weight: 500; }

.flowchart .stage .duration.in-progress { color: var(--accent); font-style: italic; }

.flowchart .stage.done { border-color: var(--green); background: var(--green-bg-soft); }

.flowchart .stage.failed { border-color: var(--red); background: var(--red-bg-soft); }

.flowchart .stage.running { border-color: var(--accent); background: var(--accent-bg); }

.flowchart .arrow { align-self: center; color: var(--muted-2); font-size: 1.2rem; padding: 0 4px; }


/* JSON / code blocks */
.json {
  background: var(--code-bg); color: var(--code-fg); padding: 12px; border-radius: 4px;
  max-height: 320px; overflow: auto; font-size: 12px;
  font-family: 'Menlo','Monaco',monospace; white-space: pre-wrap; word-break: break-all;
}

.json-block { margin-bottom: 12px; }

.json-block-header { font-weight: 600; font-size: 13px; margin-bottom: 4px; color: var(--ink); }

.json-block-header .dim { color: var(--muted); font-weight: normal; margin-left: 8px; font-family: 'Menlo','Monaco',monospace; font-size: 11px; }


/* AWS console execution link */
.exec-link {
  color: var(--accent); text-decoration: none;
  font-family: 'Menlo','Monaco',monospace; font-size: 12px;
}

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

.dim { color: var(--muted-2); }

/* ============================================================================
   Validation page (left/right containers, line-item table, crowd-input)
   ============================================================================ */


.container {
  display: flex;
  max-height: 90vh;
  overflow: hidden;
  width: 100%;
  max-width: 98vw;
}

.leftContainer {
  display: flex;
  flex-direction: column;
  width: 45vw;
  max-width: 45vw;
  max-height: 90vh;
  overflow-y: scroll;
  padding: 30px;
  padding-right: 15px;
}

.rightContainer {
  display: flex;
  flex-direction: column;
  width: 50vw;
  max-width: 50vw;
  overflow-x: scroll;
  max-height: 90vh;
  overflow-y: scroll;
  padding: 30px;
  padding-right: 0px
}

h1 {
  color: var(--ink-strong);
  margin-top: 0px;
  margin-bottom: 10px;
}

p { color: var(--ink); margin: 0; }

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

h3 { color: var(--accent); margin: 5px 0; }

.originalForm { color: var(--ink-strong); }

.pageIcon { width: 30px; height: 30px; }

.formName { color: var(--accent); font-weight: bolder; }

.inputMainContainer {
  display: grid;
  grid-template-columns: repeat(2, auto);
  gap: 20px;
  padding: 20px 0px;
  max-width: 40vw;
  margin: 0 auto;
  align-items: start;
  margin: 0px;
}

.input-pair { display: flex; flex-direction: column; gap: 10px; justify-content: flex-start; }

.confirmed { background-color: var(--accent-bg-3) !important; border: 1px solid var(--accent-bg-3); color: var(--ink-2); }

.label-container { display: flex; align-items: center; justify-content: space-between; }

.label-container label { width: 120px; font-size: 15px; width: 65%; }

.editRadio { visibility: hidden; }

.commentIcon { width: 20px; height: 20px; cursor: pointer; width: 20%; }

.commentIcon:hover path { stroke: var(--accent); }

.input-pair input { flex: 1; }

.lineItems { display: flex; flex-direction: column; }

.table-container {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 2fr 1fr 1fr 1fr auto;
  background: var(--surface);
  border-radius: 8px;
  overflow-wrap: break-word;
}

.table-header { display: contents; background-color: var(--accent); color: var(--ink); text-align: center; }

.table-row { display: contents; }

.header-cell { background-color: var(--surface); }

.table-cell { display: flex; justify-content: center; align-items: center; flex-direction: column; }

.table-cell-actions { display: flex; justify-content: center; align-items: center; }

.table-cell crowd-input {
  margin-top: 10px; width: 95%; border-radius: 4px; text-align: center; box-sizing: border-box; height: 50px;
}

.table-cell crowd-input:hover { background: var(--accent-bg-3); }

.table-cell .action-button { background: none; border: none; cursor: pointer; font-size: 1.2rem; }

.dynamic-input-wrapper { display: flex; align-items: center; gap: 8px; margin-top: 10px; }

.editLabel { color: var(--accent); padding: 8px; border-radius: 15px; background: var(--accent-bg-3); margin-right: -17px; font-size: 13px; }

.checkIcon { cursor: pointer; width: 30px; height: 30px; }

.actions { display: flex; align-items: center; gap: 10px; }

.actionIconHeader { width: 20px; height: 20px; cursor: pointer; }

.revertIcon { cursor: pointer; width: 25px; height: 25px; }

.revertIcon path { stroke: var(--faint); }

.revertIcon:hover { opacity: 0.8; }

.actionIcon { width: 20px; height: 20px; cursor: pointer; }

.deleteIcon { width: 20px; height: 20px; cursor: pointer; color: var(--muted-2); }

.actionIcon:hover path { stroke: var(--accent); }

.deleteIcon:hover path { fill: var(--accent); }

.deleteIcon:hover { color: var(--accent); }



.addLineItem { display: flex; align-items: center; gap: 5px; margin-top: -20px; cursor: pointer; }

.addIcon { width: 25px; height: 25px; }

crowd-input { margin-top: -5px; width: 100%; box-sizing: border-box; border-radius: 9px; height: 50px; }

crowd-input:hover { border-color: var(--muted-2); background: var(--accent-bg-3) !important; }

crowd-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }

crowd-input::placeholder { color: var(--muted-2); }

textarea::placeholder { color: var(--muted-2); }

.pdf-zoom-button { position: static !important; margin: 0px; }

.toolbar-button { padding: 6px 10px; border-radius: 6px; border: 1px solid var(--line-strong); background: var(--surface); cursor: pointer; }

.hidden { display: none; }

/* ============================================================================
   Help page (help-header, help-content, btn-back)
   ============================================================================ */



.help-header {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 24px;
}


.btn-back {
    display: flex;
    align-items: center;
    gap: 8px;
    background-color: var(--surface);
    color: var(--ink-2);
    border: 1px solid var(--line-strong);
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    transition: all 0.2s;
}


.btn-back:hover {
    background-color: var(--surface-3);
    border-color: var(--accent);
    color: var(--accent);
}


.help-header h1 {
    margin: 0;
    font-size: 28px;
    color: var(--ink-2);
}


.help-content {
    background-color: var(--surface);
    padding: 24px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    min-height: 400px;
}

/* ============================================================
   Round 79 · clean detail view
   Replaces the legacy detail-panel chrome (section pills +
   bulky disclosure triangles + Row-1/Row-2 invoice headers)
   with a minimalist aesthetic that visually rhymes with the
   skeleton loader (.sc-skel-detail):
     · uppercase muted section labels (var(--muted-2), 11px,
       semi-bold, letter-spacing 0.5px — same as .sc-skel-label)
     · thin border-bottom row separators (no rounded card
       backgrounds inside the panel)
     · sections REMAIN collapsible (native <details>/<summary>)
       with the browser disclosure triangle hidden and a
       minimal text caret (▸ / rotate to ▾) in the same gray
   Default-open states per Round 79:
     · EMAIL INFO / FILE UPLOAD INFO  → OPEN
     · INVOICES wrapper               → OPEN (so cards visible)
     · individual INVOICE DOCUMENT    → CLOSED
     · WORKFLOW                       → OPEN
     · CAPTURED FIELDS                → CLOSED
     · FINAL JSON                     → CLOSED
   ============================================================ */
/* Nested inside the existing .detail-body (which provides the
   white card chrome + padding) — so .clean-detail itself is
   just the vertical-flex layout container, no redundant
   background / shadow / padding. */
.clean-detail {
  display: flex; flex-direction: column; gap: 14px;
}
/* The legacy .detail-header blue gradient bar still owns the
   subject heading at the top of the panel, so the inner
   .clean-subject style isn't exercised on the live page. Kept
   defined for any future case where we want the subject
   inside the clean-detail wrapper. */
.clean-detail .clean-subject {
  margin: 0 0 4px 0;
  font-size: 18px; font-weight: 600; color: var(--ink);
  line-height: 1.3;
}

/* Top-level collapsible section. <details> + <summary>; the
   summary IS the uppercase label, no section-pill chrome. */
.clean-detail details.clean-section {
  padding: 12px 0; border-bottom: 1px solid var(--surface-4);
}
.clean-detail details.clean-section:last-of-type { border-bottom: 0; }
.clean-detail details.clean-section > summary.clean-section-label {
  list-style: none;
  /* Only the chevron+title hit-area (.sec-toggle) reads as clickable;
     the rest of the header bar no longer toggles, so it gets the
     default cursor. */
  cursor: default;
  color: var(--muted-2); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.5px;
  display: flex; align-items: center; gap: 6px;
  padding: 2px 0;
  user-select: none;
}
.clean-detail details.clean-section > summary.clean-section-label::-webkit-details-marker {
  display: none;
}
.clean-detail details.clean-section > summary.clean-section-label .sec-toggle::before {
  content: '▸';
  color: var(--muted-2);
  font-size: 10px;
  transition: transform 0.12s ease;
  display: inline-block;
  width: 10px;
  text-align: center;
}
.clean-detail details.clean-section[open] > summary.clean-section-label .sec-toggle::before {
  transform: rotate(90deg);
}
.clean-detail details.clean-section > .clean-section-body {
  display: flex; flex-direction: column; gap: 8px;
  padding-top: 8px;
  /* Round 79 refinement: indent body content from the summary
     label so nested sub-sections (and direct content like the
     email-headers dl or wf-steps table) read as belonging to
     this section. Indent is CUMULATIVE through nesting — a
     level-3 element gets the level-1 + level-2 + level-3
     padding-left values stacked. */
  padding-left: 16px;
}

/* Nested sub-section (invoice card OR Workflow/Captured/Final
   JSON inside an invoice card). Same collapsible behavior,
   slightly tighter padding + dashed top separator so nesting
   reads visually. */
.clean-detail details.clean-subsection {
  padding: 10px;
  border-top: 1px solid var(--line-strong);
}
.clean-detail details.clean-subsection:first-of-type { border-top: 0; }
.clean-detail details.clean-subsection > summary.clean-subsection-label {
  list-style: none;
  /* See .clean-section-label: only .sec-toggle is clickable. */
  cursor: default;
  color: var(--muted-2); font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.4px;
  display: flex; align-items: center; gap: 6px;
  padding: 2px 0;
  user-select: none;
  flex-wrap: wrap;
}
.clean-detail details.clean-subsection > summary.clean-subsection-label::-webkit-details-marker {
  display: none;
}
.clean-detail details.clean-subsection > summary.clean-subsection-label .sec-toggle::before {
  content: '▸';
  color: var(--muted-2);
  font-size: 9px;
  transition: transform 0.12s ease;
  display: inline-block;
  width: 10px;
  text-align: center;
  flex-shrink: 0;
}
/* Invoice-card summary holds a two-row header (.clean-invoice-
   header). Top-align the summary's flex children so the caret
   sits with Row 1 (identity strip) instead of vertically
   centering between Row 1 and Row 2 (pills). Scoped via the
   descendant .clean-invoice-header so other clean-subsection
   summaries (Workflow / Captured / Final JSON) keep their
   default center alignment. */
.clean-detail details.clean-subsection > summary.clean-subsection-label:has(.clean-invoice-header) {
  align-items: flex-start;
}
.clean-detail details.clean-subsection > summary.clean-subsection-label:has(.clean-invoice-header) .sec-toggle::before {
  /* Nudge the caret down a touch so it visually centers with
     the Row-1 baseline text rather than the very top of the
     uppercase summary line. */
  margin-top: 3px;
}
.clean-detail details.clean-subsection[open] > summary.clean-subsection-label .sec-toggle::before {
  transform: rotate(90deg);
}
.clean-detail details.clean-subsection > .clean-subsection-body {
  display: flex; flex-direction: column; gap: 6px;
  padding-top: 6px;
  /* Round 79 refinement: nested-subsection indent. Smaller
     than .clean-section-body's padding-left so deeper levels
     don't run off the right edge when the panel is narrow. */
  padding-left: 14px;
}

/* Chevron+title hit-area for collapsible section/subsection summaries.
   The visible caret (::before, defined on .sec-toggle above) plus the
   title text live inside this span; only it reads as clickable so a
   click on the rest of the header bar (counts, PDF link, empty space)
   doesn't toggle the section. The summary itself keeps cursor:default. */
.clean-detail summary.clean-section-label .sec-toggle,
.clean-detail summary.clean-subsection-label .sec-toggle,
details.cf-additional > summary .sec-toggle,
details.cf-lines > summary .sec-toggle,
.wf-failure-banner .wf-failure-details > summary .sec-toggle {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 5px;
}
.clean-detail summary.clean-section-label .sec-toggle:hover,
.clean-detail summary.clean-subsection-label .sec-toggle:hover,
details.cf-additional > summary .sec-toggle:hover,
details.cf-lines > summary .sec-toggle:hover {
  background: var(--accent-bg);
}
/* Invoice-card summary: .sec-toggle carries only the caret (the
   identity/pills live outside it). Keep it from stretching across the
   row and align its caret with the Row-1 baseline. */
.clean-detail summary.clean-subsection-label .sec-toggle.sec-toggle-caret {
  align-self: flex-start;
}
/* Bare toggle = the "INVOICE #:" LABEL as a click-to-toggle hit-area with
   NO chevron of its own (the .sec-toggle-caret span already shows it). Mirror
   the base .sec-toggle::before selector + the modifier class so content:none
   beats it on specificity. */
.clean-detail details.clean-subsection > summary.clean-subsection-label .sec-toggle.sec-toggle-bare::before {
  content: none;
}

/* Two-row invoice header — replaces the legacy single
   fixed-grid status strip. Lives INSIDE the invoice-card's
   <summary class="clean-subsection-label"> so both rows are
   visible even when the card is collapsed.
     Row 1 (.clean-invoice-row-identity): INVOICE # ·
       filename · pages — left-aligned, mixed-case.
     Row 2 (.clean-invoice-row-pills): all status pills +
       View PDF action button — right-aligned (justify-content:
       flex-end), wraps when the panel is narrow.
   Mixed-case + normal-weight resets defeat the inherited
   uppercase muted treatment from the surrounding
   .clean-subsection-label since this carries actual invoice
   content, not a section label. */
.clean-detail .clean-invoice-header {
  display: flex; flex-direction: column;
  /* Round 78g: bump row gap so identity row (INVOICE# + Open Image)
     reads as visually distinct from the pills row below it. Was 4px;
     felt cramped at the spec'd layout (pills row sat too close under
     the title). */
  gap: 10px;
  flex: 1; min-width: 0;
  font-size: 13px; color: var(--ink-2);
  text-transform: none; letter-spacing: 0; font-weight: 400;
}
.clean-detail .clean-invoice-row-identity {
  display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap;
  /* Round 80: row 1 is now just INVOICE# (Open Image moved to row 2),
     so default flex-start (left-align) is what we want. The earlier
     space-between was for the INVOICE# + Open Image split layout. */
}
.clean-detail .clean-invoice-row-pills {
  display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.clean-detail .clean-invoice-header .clean-invoice-number {
  font-weight: 600; color: var(--ink);
}
/* The invoice number VALUE stays selectable so it can be copied — the
   summary otherwise inherits user-select:none. Only the label toggles. */
.clean-detail .clean-invoice-header .clean-invoice-value {
  user-select: text;
}
.clean-detail .clean-invoice-header .clean-invoice-filename {
  color: var(--muted); font-size: 12px;
}
.clean-detail .clean-invoice-header .clean-invoice-pages {
  /* Round 78f: pages info lives on row 2 alongside the split badge
     and status pills now (was a muted subtitle on row 1 previously).
     Pill-shaped to match siblings; subtle gray treatment to read as
     metadata rather than a status. */
  display: inline-block;
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--surface-3);
  color: var(--ink-3);
  border: 1px solid var(--line);
  font-size: 11px;
  font-weight: 600;
  font-family: 'Menlo','Monaco',monospace;
}
/* Clickable page-count pill — opens the PDF modal (same action as the PDF
   icon and the split "Pages a-b" badge). */
.clean-detail .clean-invoice-header a.clean-invoice-pages-link {
  text-decoration: none; cursor: pointer;
}
.clean-detail .clean-invoice-header a.clean-invoice-pages-link:hover {
  background: var(--accent-bg); border-color: var(--accent-border); color: var(--accent-d);
}
/* Round 79 refinement: View PDF link sits in the invoice card
   header summary as the rightmost item — one-click access
   without expanding the card. Subtle button styling so it
   reads as an action without competing with the status pill
   / duration pill next to it. Block-level pointer-events so
   the link is clickable even though it lives inside a
   <summary> element (the shared handleArtifactClick handler
   uses capture-phase + preventDefault to suppress the
   summary's default toggle behavior on PDF clicks). */
.clean-detail .clean-invoice-header .clean-pdf-link {
  font-size: 12px;
  font-weight: 500;
  color: var(--accent-strong);
  background: var(--accent-bg);
  border: 1px solid var(--accent-bg-2);
  border-radius: 4px;
  padding: 2px 8px;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  text-transform: none;
  letter-spacing: 0;
}
.clean-detail .clean-invoice-header .clean-pdf-link:hover {
  background: var(--accent-bg-2);
  border-color: var(--accent-border);
}
/* PDF icon link (replaces the "Open Image" text pill): icon-only, no pill chrome. */
.clean-detail .clean-invoice-header .pdf-icon-link,
.invoice-actions .pdf-icon-link {
  display: inline-flex;
  align-items: center;
  padding: 0;
  background: none;
  border: 0;
  line-height: 0;
}
.clean-detail .clean-invoice-header .pdf-icon-link:hover,
.invoice-actions .pdf-icon-link:hover {
  background: none;
  opacity: 0.75;
}
.pdf-icon-link svg { display: block; }
/* Round 80: left-side group on row 2 — wraps Open Image + Pages a-b
   so they cluster together on the left. `margin-right: auto` consumes
   the available gap and pushes the badge group to the right edge
   (works in concert with the row's `justify-content: flex-end`).
   Robust to the empty-pages case: a non-split doc with no page_count
   yet means pagesCell is an empty string, but the wrapper still
   exists with just pdfLink inside and the layout reads the same. */
.clean-detail .clean-invoice-header .clean-invoice-left-group {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-right: auto;
}

/* Email-header dl rewrite — compact two-column grid with
   right-aligned labels. Mixed-case content; the muted gray
   label provides the visual hierarchy. */
.clean-detail dl.clean-email-headers {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 16px; row-gap: 4px;
  margin: 0;
}
.clean-detail dl.clean-email-headers dt {
  color: var(--muted); font-size: 12px; font-weight: 500;
  text-align: right;
}
.clean-detail dl.clean-email-headers dd {
  margin: 0; color: var(--ink); font-size: 13px;
}
/* Round 80: primary identifier row (Mailbox) — bolder + slightly larger
   than the rest so it reads as the email-card's headline. Label loses
   its muted treatment; value goes semibold. A subtle bottom border on
   the value cell creates a visual divider between the primary row
   and the standard email-header rows below it (From / Subject / etc.). */
.clean-detail dl.clean-email-headers dt.primary {
  color: var(--ink); font-size: 13px; font-weight: 600;
}
.clean-detail dl.clean-email-headers dd.primary {
  color: var(--ink-strong); font-size: 14px; font-weight: 600;
  /* Spacer below the primary row so the subsequent fields read as a
     separate group. The grid's row-gap (4px) still applies; this
     bottom-margin adds another 4-6px without expanding every row. */
  padding-bottom: 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--surface-3);
}
.clean-detail dl.clean-email-headers dt.primary {
  padding-bottom: 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--surface-3);
}

/* =====================================================================
 * Round 78 — Lazy workflow expand: skeleton loader
 * =====================================================================
 * Shown inside <details class="lazy-workflow"> while the GET
 * /inspector/workflow/{name} fetch is in flight. Replaced by the real
 * workflow table once the fetch resolves. Reuses the .wf-steps grid so
 * the layout doesn't jump when content swaps in.
 *
 * Visual: shimmering placeholder bars at the same height/width as real
 * stage rows. Subtle gradient sweep across each bar gives the standard
 * "loading" affordance without being noisy. Animation is short (1.4s)
 * and infinite so it loops cleanly through a typical 100-300ms fetch.
 */
.lazy-workflow > summary { cursor: pointer; }
/* Tiny hint next to the "Workflow" label telling the operator the
 * section is lazy-loaded. Removed by the JS handler once content is
 * injected (no need to keep nagging once the fetch has fired). */
.lazy-workflow-hint {
  color: var(--muted-2);
  font-size: 11px;
  font-style: italic;
  margin-left: 8px;
}
/* Skeleton row marker — disables hover effects + dot color while
 * loading so the placeholder doesn't look interactive. */
.wf-steps.sk-table .sk-row td { color: transparent; }
.wf-steps.sk-table .wf-rail .sk-shimmer {
  display: inline-block;
  width: 18px; height: 18px; border-radius: 50%;
  vertical-align: middle;
}
/* The shimmer bar — width tuned to look like the real text it replaces.
 * sk-bar-wide for the step-name row (spans 3 cols); sk-bar-narrow for
 * the duration cell. */
.sk-shimmer {
  background: linear-gradient(
    90deg,
    var(--skeleton-1) 0%,
    var(--skeleton-2) 50%,
    var(--skeleton-1) 100%
  );
  background-size: 200% 100%;
  animation: sk-shimmer-pulse 1.4s ease-in-out infinite;
  border-radius: 3px;
}
.sk-bar {
  display: inline-block;
  width: 60%; height: 12px;
  vertical-align: middle;
}
.sk-bar-wide { width: 80%; }
.sk-bar-narrow { width: 40%; }
@keyframes sk-shimmer-pulse {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
/* Error state: shown when the lazy-workflow fetch fails. The Retry
 * button triggers re-fetch (see inspector-detail.js
 * handleLazyWorkflowToggle's catch branch). */
.lazy-workflow-error {
  background: var(--red-bg-soft);
  border-left: 3px solid var(--red-strong);
  border-radius: 4px;
  margin: 4px 8px;
}
.lazy-workflow-retry {
  background: var(--surface);
  border: 1px solid var(--red-strong);
  color: var(--red-strong);
  border-radius: 3px;
}
.lazy-workflow-retry:hover { background: var(--red-bg); }

/* =====================================================================
 * Round 78e — sum-based duration footer below the Invoice Documents list
 * =====================================================================
 * Mirrors the per-doc workflow tfoot pattern (.wf-total above) so the
 * email-level "Total processing time" row reads as the natural
 * roll-up of all the per-doc workflow timings the operator sees on
 * individual docs.
 *
 * Layout: label on the left (uppercase, muted), duration value on the
 * right (monospace, bold). Same color tokens as .wf-total to keep the
 * "totals row" feel consistent across scopes.
 *
 * Variants (added via wf-total / wf-total.running classes from the JS):
 *   - default            → final/terminal, dark gray value
 *   - .running           → in-flight, blue-tinted value
 */
.docs-duration-summary {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  background: var(--surface-2);
  border-top: 1px solid var(--line);
  /* Round 78f: more breathing room around the value so the duration
     string ("⏱ 4m 12s · 3 still in flight · 2 docs unavailable") never
     crowds the right edge. */
  padding: 14px 20px;
  margin-top: 8px;
  border-radius: 4px;
  gap: 16px;
}
.docs-duration-summary .docs-duration-label {
  font-size: 11.5px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  white-space: nowrap;
}
.docs-duration-summary .docs-duration-value {
  font-family: 'Menlo', 'Monaco', monospace;
  font-size: 12.5px;
  color: var(--ink);
  font-weight: 600;
  /* Keep the duration + qualifiers on one line; the wrapper's flex
     layout will let the label push left if the value runs long. */
  text-align: right;
}
/* In-flight variant — same blue token as the per-doc wf-total.running. */
.docs-duration-summary.running .docs-duration-value { color: var(--accent-strong); }
.docs-duration-summary.failed  .docs-duration-value { color: var(--red-d); }

/* ============================================================
   Home / Dashboard page (index_page.html, <body class="page-home">)
   Ported from docs/mocks/index-dashboard-mockup.html. Every rule is
   scoped under .page-home so it cannot affect the Tasks / Inspector /
   Validation pages. The design tokens are declared on body.page-home
   so they cascade only inside the home page. The combobox, env-strip,
   and top-nav chrome are intentionally NOT re-ported here — those
   already live in app.css and are shared across pages.
   ============================================================ */
body.page-home {
  /* Tokens now come from the global :root palette (see top of file). */
  background: var(--bg); color: var(--ink);
}

/* page layout */
.page-home .page{margin:24px 0;padding:0 30px;display:grid;
  grid-template-columns:340px 1fr;gap:24px;align-items:start}
@media (max-width:980px){
  .page-home .page{grid-template-columns:1fr}
  .page-home .left{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start}
  .page-home .left .greeting,
  .page-home .left .sub{grid-column:1 / -1;margin:0}   /* span full width above the 2-up cards */
  .page-home .left .role-card{margin-bottom:0;padding:14px}
  .page-home .left .role-card p{display:none}      /* hide the long blurb in the compact 2-up */
  .page-home .left .role-card .ic{width:38px;height:38px;font-size:18px;margin-bottom:8px}
}

.page-home h1.greeting{font-size:22px;font-weight:600;margin:0 0 4px}
.page-home .sub{color:var(--muted);font-size:14px;margin:0 0 18px}

/* left role cards */
.page-home .role-card{background:var(--surface);border-radius:var(--radius);box-shadow:var(--elev);
  padding:22px;margin-bottom:18px;border:1px solid var(--line);transition:.15s;cursor:pointer;position:relative}
.page-home .role-card:hover{transform:translateY(-2px);box-shadow:0 10px 24px -8px var(--shadow-strong)}
.page-home .role-card .ic{width:46px;height:46px;border-radius:11px;display:flex;align-items:center;
  justify-content:center;font-size:22px;margin-bottom:14px}
.page-home .role-card.tasks .ic{background:var(--accent-bg-3);color:var(--accent-d)}
.page-home .role-card.insp .ic{background:var(--green-bg-soft);color:var(--green)}
.page-home .role-card h3{margin:0 0 6px;font-size:18px}
.page-home .role-card p{margin:0 0 16px;color:var(--muted);font-size:13.5px;line-height:1.5}
.page-home .role-card .go{display:inline-flex;align-items:center;gap:8px;background:var(--accent);color:var(--on-accent);
  border:0;border-radius:8px;padding:9px 16px;font-size:14px;cursor:pointer}
.page-home .role-card.insp .go{background:var(--green)}
.page-home .role-card .meta{margin-top:14px;font-size:12px;color:var(--slate);display:flex;gap:14px}
.page-home .role-card .meta b{color:var(--ink)}
/* dim-locked state — show both cards, lock the one the user's groups don't grant */
.page-home .role-card.locked{cursor:not-allowed}
.page-home .role-card.locked > :not(.lock){opacity:.4;filter:grayscale(.4);pointer-events:none}
.page-home .role-card.locked:hover{transform:none;box-shadow:var(--elev)}
.page-home .role-card .lock{display:none;position:absolute;inset:0;border-radius:var(--radius);
  background:rgba(255,255,255,.35);align-items:center;justify-content:center;text-align:center}
.page-home .role-card.locked .lock{display:flex}
.page-home .role-card .lock span{background:var(--surface);border:1px solid var(--line);border-radius:999px;
  padding:6px 14px;font-size:12px;color:var(--slate);box-shadow:var(--elev)}

/* right widgets */
/* Gridstack owns card positioning now, so .widgets is just a vertical stack of
   sections (header + grid). The cards live in #grid-live / #grid-datalake. */
.page-home .widgets{display:block}
/* Each card fills its grid-stack cell; the item-content box is transparent. */
.page-home .grid-stack-item-content{inset:0;overflow:visible;display:flex}
.page-home .grid-stack-item-content > .w,
.page-home .grid-stack-item-content > .secthead,
.page-home .grid-stack-item-content > .note{flex:1;min-width:0;height:100%;margin:0;box-sizing:border-box;overflow:auto}
.page-home .grid-stack-item-content > .secthead{margin:0;align-items:center}
/* Layout edit controls */
.page-home .layout-controls{display:flex;align-items:center;gap:10px;margin:0 0 6px;flex-wrap:wrap}
.page-home .lc-btn{font:inherit;font-size:12px;font-weight:600;color:var(--accent-d);background:var(--accent-bg-3);
  border:1px solid var(--line);border-radius:8px;padding:5px 12px;cursor:pointer}
.page-home .lc-btn:hover{background:var(--accent-bg-3)}
.page-home .lc-hint{font-size:11px;color:var(--muted)}
.page-home .lc-status{font-size:11px;color:var(--green);font-weight:600}
.page-home .lc-edit{display:none}                       /* only while editing */
.page-home.layout-editing .lc-edit{display:inline-block}
/* Edit-mode affordance: dashed outline + grab cursor on each card. */
.page-home.layout-editing .grid-stack-item-content{outline:1px dashed var(--muted-2);outline-offset:-1px;cursor:grab}
.page-home.layout-editing .grid-stack-item-content:active{cursor:grabbing}
/* #live-queue is a logical grouping container that index-page.js targets
   (getElementById + innerHTML). display:contents removes it from layout so the
   .w widgets it holds become direct grid items of .widgets, matching the mockup
   where the live widgets sat directly in the grid. */
.page-home #live-queue{display:contents}
.page-home .w{background:var(--surface);border-radius:var(--radius);box-shadow:var(--elev);
  border:1px solid var(--line);padding:16px;position:relative}
.page-home .w.span2{grid-column:span 2}
.page-home .w.span4{grid-column:span 4}
/* Mobile-only transposed pivot for the reviewed-but-unchanged table (top 3
   customers as columns). Hidden by default — this base rule MUST precede the
   ≤768 block below so the display:block override wins (equal specificity → the
   later rule wins). */
.page-home .ru-pivot{display:none}
.page-home .ru-pivot table.tbl{margin-top:0}
/* Pivot pager (mobile) — "Showing X–Y of N" + a Next-3 button. */
.page-home .ru-pager{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:10px;font-size:12px;color:var(--muted)}
.page-home .ru-next{font:inherit;font-size:12px;font-weight:600;color:var(--accent-d);background:var(--accent-bg-3);border:1px solid var(--line);border-radius:8px;padding:5px 12px;cursor:pointer}
.page-home .ru-next:hover{background:var(--accent-bg-3)}
/* Phones: the multi-column grid renders 3–4 cards across and overflows the
   viewport. Stack EVERYTHING into one full-width column — the layout that
   reliably fits a phone. */
@media (max-width:768px){
  /* Responsive: every card spans the full viewport width and stacks vertically.
     Use BLOCK FLOW (not a grid) so no column/track sizing can push a card past
     the screen edge, and clip any residual overflow as a final guard. */
  /* no horizontal page scroll at all (scoped to the home page) */
  html:has(.page-home){overflow-x:hidden}
  .page-home{overflow-x:hidden}
  /* Header reflow (chip top-right, nav on next line) is shared by ALL pages in
     the @media(max-width:768px) block above — not duplicated here. */
  .page-home .page{display:block;overflow-x:hidden}
  .page-home .left,
  .page-home .widgets{display:block}
  .page-home .w{display:block;width:auto;max-width:100%;box-sizing:border-box;margin:0 0 12px;grid-column:auto}
  .page-home .w > *{max-width:100%}
  /* Section headers ("Your review queue", etc.) use a negative bottom margin
     tuned for the grid's row-gap; in block flow there's no gap, so the next card
     rides up and overlaps the header. Restore positive bottom spacing. */
  .page-home .secthead{margin:10px 0 14px !important}  /* beat the base rule defined later in the file */
  /* Role cards become short full-width bars. The whole card is tappable
     (index-page.js wireRoleCardClicks), so the big "Open …" button is dropped
     and the icon/title/meta tightened. */
  .page-home .left .role-card{display:block;width:auto;max-width:100%;box-sizing:border-box;margin:0 0 12px;padding:14px}
  .page-home .left .role-card p{display:block}   /* full-width stack has room for the blurb */
  .page-home .left .role-card .ic{width:30px;height:30px;font-size:15px;border-radius:8px;margin-bottom:8px}
  .page-home .left .role-card h3{font-size:16px;margin:0 0 2px}
  .page-home .left .role-card .go{display:none}
  .page-home .left .role-card .meta{margin-top:8px;font-size:12px;gap:12px}
  /* min_confidence: transposed pivot instead of the wide 5-col table. */
  .page-home .ru-table{display:none}
  .page-home .ru-pivot{display:block}
  /* keep the recent-tasks status pill from wrapping/distorting */
  .page-home .list .li .s{white-space:nowrap}
}
.page-home .w .wt{font-size:12.5px;color:var(--muted);font-weight:600;margin:0 0 10px;
  display:flex;align-items:center;justify-content:space-between;gap:8px}
.page-home .tag{font-size:10px;font-weight:700;padding:2px 7px;border-radius:999px;letter-spacing:.03em;white-space:nowrap}
.page-home .tag.live{background:var(--accent-bg-3);color:var(--accent-d)}
.page-home .tag.athena{background:var(--violet-bg);color:var(--violet-d)}
.page-home .kpi{font-size:32px;font-weight:700;line-height:1}
.page-home .kpi small{font-size:13px;font-weight:600;color:var(--muted)}
.page-home .delta{font-size:12px;margin-top:6px}
.page-home .delta.up{color:var(--green)} .page-home .delta.down{color:var(--red)}
.page-home .src{position:absolute;bottom:8px;right:12px;font-size:10px;color:var(--chart-grid)}

/* donut */
.page-home .donut{width:120px;height:120px;border-radius:50%;margin:4px auto;
  -webkit-mask:radial-gradient(circle 36px,transparent 98%,#000);mask:radial-gradient(circle 36px,transparent 98%,#000)}
.page-home .lgrid{display:flex;flex-direction:column;gap:5px;font-size:12px;margin-top:6px}
.page-home .lgrid .row{display:flex;align-items:center;gap:8px}
.page-home .lgrid .row b{margin-left:auto}

/* bars */
.page-home .bars{display:flex;flex-direction:column;gap:9px;font-size:12px}
.page-home .bars .r{display:grid;grid-template-columns:120px 1fr 44px;gap:8px;align-items:center}
.page-home .bars .track{background:var(--surface-3);border-radius:6px;height:14px;overflow:hidden}
.page-home .bars .fill{height:100%;border-radius:6px;background:var(--accent)}

/* sparkline / line */
.page-home svg.line{width:100%;height:70px;display:block}
/* monthly multi-line volume chart (global + top-10 mailboxes) */
.page-home svg.line.ml-svg{height:130px}
.page-home .ml-wrap{position:relative}
.page-home .ml-xaxis{margin-top:4px}
/* Hover markers are HTML overlays (not SVG circles) so they stay round — an
   SVG <circle> would stretch into an ellipse under the chart's non-uniform
   viewBox scaling. Positioned in px by the chart's mousemove handler. */
.page-home .lc-dot{position:absolute;width:7px;height:7px;border-radius:50%;
  background:var(--surface);border:2px solid var(--ink);box-sizing:border-box;
  transform:translate(-50%,-50%);pointer-events:none;display:none;z-index:5}
.page-home .ml-wrap .lc-y0{position:absolute;top:118px;left:1px;font-size:9px;font-weight:700;color:var(--chart-grid);background:var(--label-bg);padding:0 3px;border-radius:3px}
.page-home .ml-tip{position:absolute;top:2px;z-index:6;background:var(--tooltip-bg);color:var(--tooltip-fg);border:1px solid var(--tooltip-border);font-size:10px;
  padding:6px 9px;border-radius:7px;pointer-events:none;box-shadow:var(--elev);min-width:130px;max-width:250px}
.page-home .ml-tip .ml-tip-h{font-weight:700;margin-bottom:4px}
.page-home .ml-tip .ml-tip-row{display:flex;align-items:center;white-space:nowrap;line-height:1.55}
.page-home .ml-tip .ml-tip-row i{display:inline-block;width:7px;height:7px;border-radius:2px;margin-right:5px;flex:0 0 auto}
.page-home .ml-tip .ml-tip-row b{margin-left:4px}
.page-home .ml-legend{display:flex;flex-wrap:wrap;gap:6px 16px;margin-top:10px;font-size:11px;color:var(--muted)}
.page-home .ml-leg{display:inline-flex;align-items:center}
.page-home .ml-leg i{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:5px}
.page-home .linewrap{position:relative}
.page-home .linewrap .x-axis{display:flex;justify-content:space-between;font-size:10px;color:var(--chart-grid);margin-top:2px}
.page-home svg.line{overflow:visible}                 /* let the peak label/crosshair show */
.page-home .linewrap .lc-tip{position:absolute;top:0;transform:translateY(-100%);background:var(--tooltip-bg);color:var(--tooltip-fg);border:1px solid var(--tooltip-border);
  font-size:10px;font-weight:600;padding:3px 7px;border-radius:6px;white-space:nowrap;pointer-events:none;z-index:5;box-shadow:var(--elev)}
/* y-axis annotations for fixed-ceiling charts (e.g. touchless rate): left = max
   scale (100%), right = the data peak. Sit at the dashed ceiling gridline. */
.page-home .linewrap .lc-ymax,
.page-home .linewrap .lc-peak{position:absolute;top:2px;font-size:9px;font-weight:700;pointer-events:none;
  background:var(--label-bg);padding:0 3px;border-radius:3px;line-height:1.3}
.page-home .linewrap .lc-ymax{left:1px;color:var(--chart-grid)}
.page-home .linewrap .lc-peak{right:1px}
.page-home .linewrap svg.line{cursor:crosshair}

/* recent list */
.page-home .list{display:flex;flex-direction:column}
.page-home .list .li{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:center;padding:9px 0;border-top:1px solid var(--line);font-size:13px}
.page-home .list .li:first-child{border-top:0}
.page-home .list .li .s{font-size:11px;font-weight:700;padding:2px 8px;border-radius:999px}
.page-home .s.pending{background:var(--surface-3);color:var(--slate)} .page-home .s.inprog{background:var(--accent-bg-3);color:var(--accent-d)}
.page-home .s.done{background:var(--green-bg-soft);color:var(--green)} .page-home .s.term{background:var(--red-bg-soft);color:var(--red)}
.page-home .muted{color:var(--muted)}
/* Collapsible Data Lake analytics section — toggle bar + collapse. The body uses
   display:contents so its widgets remain direct grid items when expanded; the
   .collapsed modifier (higher specificity) hides everything. */
.page-home .dl-toggle{grid-column:1 / -1;display:flex;align-items:center;gap:10px;width:100%;
  background:var(--surface-2);border:1px solid var(--line);border-radius:10px;padding:11px 14px;margin-top:6px;
  font:inherit;font-size:13px;font-weight:700;color:var(--accent-d);cursor:pointer;text-align:left}
.page-home .dl-toggle:hover{background:var(--accent-bg-3)}
.page-home .dl-toggle .dl-chev{font-size:11px;transition:transform .15s}
.page-home .dl-toggle[aria-expanded="true"] .dl-chev{transform:rotate(90deg)}
.page-home .dl-toggle .tag{margin-left:auto}
.page-home .dl-body{display:contents}
.page-home .dl-body.collapsed{display:none}
.page-home .secthead{grid-column:span 4;font-size:13px;font-weight:700;color:var(--accent-d);
  margin:6px 0 -4px;display:flex;align-items:center;gap:10px}
.page-home .secthead .sh-note{font-weight:400;color:var(--slate);font-size:11px}
/* segmented time-range control (replaces the range <select>) */
.page-home .rangeseg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--surface)}
.page-home .rangeseg button{font:inherit;font-size:11px;font-weight:600;color:var(--slate);background:transparent;
  border:0;border-left:1px solid var(--line);padding:3px 9px;cursor:pointer;line-height:1.6}
.page-home .rangeseg button:first-child{border-left:0}
.page-home .rangeseg button:hover{background:var(--accent-bg-3)}
.page-home .rangeseg button.on{background:var(--accent);color:var(--on-accent)}
.page-home .secthead::after{content:"";flex:1;height:1px;background:var(--accent-bg-2)}
/* Analytics refresh indicator — dim + pulse the widgets while /analytics/summary
   is in flight (range change / mailbox change). */
.page-home .widgets.updating .w[data-src="athena"]{opacity:.5;transition:opacity .15s}
.page-home .widgets.updating .w[data-src="athena"]::after{
  content:"";position:absolute;top:10px;right:10px;width:10px;height:10px;
  border:2px solid var(--line);border-top-color:var(--accent);border-radius:50%;
  animation:home-spin .7s linear infinite}
@keyframes home-spin{to{transform:rotate(360deg)}}
.page-home .hidden{display:none!important}
.page-home .note{grid-column:span 4;font-size:12px;color:var(--muted);background:var(--surface);border:1px dashed var(--line);
  border-radius:10px;padding:10px 14px}

/* data table (reviewed-but-unchanged) */
.page-home table.tbl{width:100%;border-collapse:collapse;font-size:12.5px;margin-top:4px}
.page-home table.tbl th,.page-home table.tbl td{text-align:right;padding:7px 10px;border-top:1px solid var(--line)}
.page-home table.tbl th:first-child,.page-home table.tbl td:first-child{text-align:left}
.page-home table.tbl th{color:var(--muted);font-weight:600;font-size:11.5px;border-top:0}
.page-home table.tbl .pct{font-weight:700}
.page-home table.tbl .hot{color:var(--red)}
.page-home .pill-thr{display:inline-block;background:var(--accent-bg-3);border:1px solid var(--line);border-radius:999px;
  padding:1px 9px;font-weight:700}

/* ---- mailbox filter bar (reuses the shared combobox component) ---- */
/* Filter bar = two stacked rows sharing the page's full-bleed 30px content box so
   the combobox left edge lines up with the nav logo and the page content below. */
.page-home .filterbar{margin:14px 0 0;padding:0 30px;display:flex;flex-direction:column}
/* Row 1 never wraps: labels stay to the right of the combobox; legend hard-right. */
.page-home .filterbar .fb-main{display:flex;gap:14px;align-items:center;flex-wrap:nowrap}
/* Combobox left-most, capped at 400px (shrinks before labels would overflow). */
.page-home .filterbar .combobox{flex:0 1 400px;max-width:400px}
.page-home .filterbar .fb-hint,
.page-home .filterbar .fb-count{white-space:nowrap}
/* Scope pill always on its own line below; no gap reserved while idle. */
.page-home .filterbar:has(.scope-pill.on) .scope-row{margin-top:10px}
/* On phones the no-wrap row can't fit (combobox + labels + legend) and would
   overflow horizontally, which makes the full-bleed header look narrower than
   the page. Allow it to wrap and let the combobox span the row. */
@media (max-width:600px){
  .page-home .filterbar .fb-main{flex-wrap:wrap}
  .page-home .filterbar .combobox{flex:1 1 100%;max-width:100%}
  .page-home .filterbar .fb-legend{margin-left:0}
}
.page-home .filterbar .fb-label{font-size:13px;font-weight:700;color:var(--accent-d)}
.page-home .filterbar .fb-hint{font-size:12px;color:var(--muted)}
.page-home .filterbar .fb-count{font-size:12px;color:var(--muted)}
.page-home .fb-legend .dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px;vertical-align:middle}
.page-home .scope-pill{display:none;align-items:center;gap:8px;background:var(--violet-bg);border:1px solid var(--violet-border);
  color:var(--violet-d);border-radius:999px;padding:4px 6px 4px 12px;font-size:12.5px;font-weight:700}
.page-home .scope-pill.on{display:inline-flex}
.page-home .scope-pill button{border:0;background:var(--surface);color:var(--violet-d);border-radius:999px;width:20px;height:20px;
  cursor:pointer;font-size:13px;line-height:1}

/* Theme cycle toggle in the top nav */
.theme-toggle {
  width: 34px; height: 34px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink-2);
  cursor: pointer; line-height: 0;
}
.theme-toggle:hover { border-color: var(--accent); color: var(--accent); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme-toggle svg { display: block; }

/* Inspector unified Search card — segmented Find an Email / Find a Record */
.search-seg { display: inline-flex; align-self: flex-start; border: 1px solid var(--line-strong);
  border-radius: 8px; overflow: hidden; margin-bottom: 14px; }
.search-seg .seg-btn { border: 0; background: var(--surface); color: var(--muted);
  font-size: 12.5px; font-weight: 600; padding: 8px 18px; cursor: pointer; }
.search-seg .seg-btn.on { background: var(--accent); color: var(--on-accent); }
.search-pane { display: none; }
.search-pane.on { display: block; }
.search-date-note { font-size: 11px; color: var(--muted-2); margin: 0 0 14px; }
.search-results { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.rcard { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--surface); cursor: pointer; }
.rcard:hover { border-color: var(--accent); box-shadow: 0 2px 8px -3px rgba(0,0,0,0.18); }
.rcard .match { display: flex; align-items: center; gap: 8px; padding: 8px 12px;
  background: var(--accent-bg); border-bottom: 1px solid var(--accent-border); font-size: 12px; }
.rcard .match .tick { color: var(--green); font-weight: 800; }
.rcard .match .lbl { color: var(--accent-d); font-weight: 700; text-transform: uppercase; letter-spacing: .03em; font-size: 10.5px; }
.rcard .match .val { font-weight: 700; color: var(--ink-strong); font-family: 'Menlo','Monaco',monospace; }
.rcard .fgrid { display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px; padding: 12px; }
.rcard .fgrid .k { color: var(--muted); font-size: 11.5px; }
.rcard .fgrid .v { color: var(--ink-strong); font-size: 13px; font-weight: 600; }
.rcard .fgrid .v.amt { color: var(--green); }
.search-cap { font-size: 11.5px; color: var(--muted); margin: 10px 0 0; }
.search-cap b { color: var(--amber-d); }
/* "No records found" infobox (Find a Record empty state) */
.search-empty { display: flex; gap: 10px; align-items: flex-start; margin-top: 12px;
  padding: 12px 14px; border: 1px solid var(--accent-border); border-radius: 8px;
  background: var(--accent-bg); color: var(--ink-2); font-size: 12.5px; }
.search-empty .ic { flex: 0 0 auto; color: var(--accent-d); line-height: 1; margin-top: 1px; }
.search-empty .msg { line-height: 1.5; }
.search-empty .msg b { color: var(--ink-strong); display: block; margin-bottom: 2px; }
.search-empty .hint { color: var(--muted); }
.search-busy { display: flex; align-items: center; gap: 10px; margin-top: 12px;
  background: var(--accent-bg); border: 1px solid var(--accent-border); border-radius: 8px;
  padding: 10px 12px; font-size: 12.5px; color: var(--accent-d); }
.search-spin { width: 16px; height: 16px; border: 2px solid var(--accent); border-top-color: transparent;
  border-radius: 50%; animation: search-spin 1s linear infinite; }
@keyframes search-spin { to { transform: rotate(360deg); } }
.clean-subsection.search-hit { outline: 2px solid var(--accent); outline-offset: 2px;
  animation: search-hit-fade 2.4s ease-out; }
@keyframes search-hit-fade { 0% { background: var(--accent-bg); } 100% { background: transparent; } }
