* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--color-parchment);
  color: var(--color-ink);
  font-family: var(--font-body);
  line-height: 1.5;
  min-height: 100vh;
}
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 300; margin: 0 0 var(--space-3); color: var(--color-franciscan-green); }
a { color: var(--color-franciscan-green); }
p { margin: 0 0 var(--space-3); }

/* ---- Header / nav ---- */
.site-header {
  background: var(--color-franciscan-green);
  color: #f4f1e8;
  /* 2026-08-29: vertical padding moved OFF the bar itself and onto .brand/
     nav individually (same total space, just applied one level down) so
     .brand-logo -- a direct child of .brand-group, which now stretches --
     can span the bar's full, un-padded height edge to edge. Horizontal
     padding stays here, it never affected the logo's height problem.

     height is now EXPLICIT, not left to size itself from content -- a
     real bug found live: .brand-logo's height:100% needs a DEFINITE
     ancestor height to resolve against, and with no fixed height here the
     browser instead sized the whole bar around the logo's own raw pixel
     dimensions (a 160px-tall source file blew the header up to 164px
     tall). Fixing the bar's height first is what lets the logo actually
     scale INTO it instead of dictating it, whatever the uploaded file's
     native size is. */
  height: 56px;
  padding: 0 var(--space-5);
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  /* 2026-08-16, item 9: the green top bar should never scroll out of
     view on any screen. z-index above .impersonation-bar's own 100 (also
     sticky, top:0) so the two don't fight for the same spot when both are
     visible at once (impersonating/previewing while scrolled) -- a real
     but rare edge case, deliberately left as-is for now per Jay. */
  position: sticky;
  top: 0;
  z-index: 150;
}
.site-header .brand {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  color: #f4f1e8;
  text-decoration: none;
  letter-spacing: 0.02em;
  display: flex;
  align-items: center;
  padding: var(--space-3) 0;
}
.site-header nav { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-3) 0; }
.site-header nav a { color: #d8d2c0; text-decoration: none; font-size: 0.92rem; }
.site-header nav a:hover { color: var(--color-gold-light); }

.entity-badge {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: var(--radius-md);
  padding: var(--space-1) var(--space-3);
}
.entity-badge select {
  background: transparent;
  color: #f4f1e8;
  border: none;
  font-family: var(--font-body);
  font-size: 0.9rem;
  cursor: pointer;
}
.entity-badge select option { color: var(--color-ink); }
.entity-badge .label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-gold-light); }

/* ---- Notification bell (In-App Notifications, 2026-08-02) -- same
   click-to-reveal popup mechanism as .status-pill-btn/.status-popup (My
   Requests' status-history popup), adapted for the site header rather
   than a table cell. See static/js/notifications.js for the toggle/
   outside-click/mark-read-on-click behavior. ---- */
.notif-bell-wrap { position: relative; display: flex; align-items: center; }
.notif-bell-btn {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-size: 1.2rem;
  line-height: 1;
  color: #d8d2c0;
  position: relative;
}
.notif-bell-btn:hover { color: var(--color-gold-light); }
.notif-badge {
  position: absolute;
  top: -7px;
  right: -10px;
  background: var(--color-danger);
  color: #fff;
  border-radius: 999px;
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 1;
  padding: 2px 5px;
  min-width: 16px;
  text-align: center;
  font-family: var(--font-body);
}
.notif-popup {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 300;
  margin-top: var(--space-2);
  background: var(--color-card-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card-hover);
  padding: var(--space-3);
  width: 380px;
  max-width: min(90vw, 380px);
  max-height: 480px;
  overflow-y: auto;
  color: var(--color-ink);
  font-family: var(--font-body);
  white-space: normal;
}
.notif-popup-title { font-weight: 700; font-size: 0.9rem; margin-bottom: var(--space-2); color: var(--color-franciscan-green); }
.notif-empty { font-size: 0.85rem; color: var(--color-text-muted); padding: var(--space-2) 0; }
.notif-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-2) 0;
  cursor: pointer;
  font-family: var(--font-body);
}
.notif-item:last-child { border-bottom: none; }
.notif-item.unread .notif-message { font-weight: 700; }
.notif-item .notif-message { font-size: 0.85rem; color: var(--color-ink); }
.notif-item .notif-time { font-size: 0.72rem; color: var(--color-text-muted); margin-top: 2px; }

/* User menu dropdown (2026-08-08 feedback batch) -- reuses .notif-popup's
   positioning/card styling directly, .user-menu-popup only adds the
   width/padding that differ from the notification list. */
.user-menu-btn {
  background: none; border: none; padding: 0; margin: 0; cursor: pointer;
  color: #d8d2c0; font-size: 0.92rem; font-family: inherit;
}
.user-menu-btn:hover { color: var(--color-gold-light); }
.user-menu-popup { min-width: 200px; padding: var(--space-2) 0; }
/* 2026-08-16, Jay, live test: "almost impossible to see the options" --
   real bug, not a new one: `.site-header nav a` (line ~33, a pale color
   meant for links sitting directly on the dark header bar) has higher
   specificity (1 class + 2 type selectors) than a bare `.user-menu-item`
   (1 class), so every <a>-based item in this white popup was silently
   rendering in that pale header color instead of --color-ink -- pale text
   on a white popup background. Scoping the selector under
   `.user-menu-popup` (2 classes) beats it regardless of source order. */
.user-menu-popup .user-menu-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  padding: var(--space-2) var(--space-4); font-size: 0.88rem; color: var(--color-ink);
  text-decoration: none; cursor: pointer; font-family: inherit;
}
.user-menu-popup .user-menu-item:hover { background: #f4f1e8; }
.user-menu-item-form { margin: 0; }

/* ---- Full-screen toggle (2026-08-02) -- fixed-positioned just under the
   green header bar, on EVERY page including /login (Jay: "for all
   screens"), so it can't be gated inside `.site-header`'s own
   `{% if user %}` block. See static/js/fullscreen.js for the toggle/icon-
   swap/fullscreenchange-listener behavior; the button starts `hidden` in
   the markup and JS only reveals it once document.fullscreenEnabled is
   confirmed true, so a context that disallows fullscreen (e.g. an
   embedding iframe) never shows a button that would just error. ---- */
.fullscreen-toggle-btn {
  position: fixed;
  top: 66px;
  right: var(--space-5);
  z-index: 400;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-card-bg);
  color: var(--color-franciscan-green);
  box-shadow: var(--shadow-card);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}
.fullscreen-toggle-btn:hover { box-shadow: var(--shadow-card-hover); color: var(--color-gold); }

/* ---- Impersonation bar -- deliberately NOT gold/green, must read as a
   distinct, unmissable "you are not really you right now" state. ---- */
.impersonation-bar {
  background: var(--color-warning-bg);
  color: var(--color-warning);
  font-weight: 600;
  font-size: 0.9rem;
  padding: var(--space-2) var(--space-5);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  text-align: center;
  position: sticky;
  top: 0;
  z-index: 100;
}
.impersonation-bar form { display: inline; }
.impersonation-bar button {
  background: var(--color-warning);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  padding: 2px 12px;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: var(--font-body);
  cursor: pointer;
}
.impersonation-bar button:hover { opacity: 0.88; }

/* ---- Parish Mode theme (2026-08-08) -- overriding the variable itself,
   not individual rules, retints the header/links/buttons/badges everywhere
   they already reference --color-franciscan-green with zero duplication.
   See tokens.css for why these exact values are a placeholder. ---- */
body.parish-mode {
  --color-franciscan-green: var(--color-parish-red);
  --color-franciscan-green-dark: var(--color-parish-red-dark);
}

/* ---- Cornerstone Mode theme (2026-08-16) -- same override mechanism as
   Parish Mode above, deliberately a distinct color (rich dark blue, not
   red) since it's a different concept: a Cornerstone Franciscan Ministries
   staffer working a served parish's own full AP process, not a parish's
   own portal view. Mutually exclusive with body.parish-mode in practice
   (main.py only ever applies one class), but written as its own selector
   rather than an :not(.parish-mode) guard for clarity. ---- */
body.cornerstone-mode {
  --color-franciscan-green: var(--color-cornerstone-blue);
  --color-franciscan-green-dark: var(--color-cornerstone-blue-dark);
}

/* ---- Dev environment banner (Dev/Prod Split Plan.md, 2026-07-31) --
   BEACON_ENV=dev is allowed to post real Bills/Vendors to the real, live
   production QuickBooks company (there is no QBO sandbox to test against
   safely), so this must be unmistakable app-wide, not a small label --
   Jay's explicit requirement. Rendered on EVERY page (base.html, not gated
   on `user`) as plain normal-flow content -- deliberately NOT `position:
   sticky`/`fixed` like .impersonation-bar, to avoid a z-index/stacking fight
   with that bar when both are visible at once (a real, already-documented
   edge case for .impersonation-bar itself); scrolling away with the rest of
   the page is an acceptable tradeoff for something shown at the top of
   every single page load. The site-header's own red bottom border is the
   second, persistent-while-scrolling reinforcement. ---- */
.dev-env-banner {
  background: var(--color-dev-env);
  color: #fff;
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.01em;
  text-align: center;
  padding: var(--space-2) var(--space-5);
}
body.beacon-env-dev .site-header {
  border-bottom: 4px solid var(--color-dev-env);
}

.page-shell { max-width: 1100px; margin: 0 auto; padding: var(--space-6) var(--space-5); }

/* 2026-08-02 feedback batch, Item 3: "all screens need a back hyperlink
   underneath the name of the screen" -- one consistent style for that link
   across every screen. */
.back-link { margin: 2px 0 var(--space-3); font-size: 0.9rem; }
.back-link a { color: var(--color-muted, #667); text-decoration: none; }
.back-link a:hover { text-decoration: underline; }

/* 2026-09-10: generic h1-plus-primary-action row, for a page whose title
   needs a button next to it (e.g. My Requests' "+ New Request"). Same
   shape as new_request.css's own scoped .form-header-row -- that one isn't
   reusable here since most pages don't load new_request.css -- kept in
   base.css (loaded everywhere) so any future page can reuse this instead
   of inventing its own. */
.page-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.page-header-row h1 { margin: 0; }

/* 2026-08-02 feedback batch, Item 5/7: the standing entity-filter rule for
   any screen showing cross-entity data, plus All Requests' own vendor/
   submitted-by/status filters. One shared row layout. */
.filter-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3);
              margin-bottom: var(--space-4); padding: var(--space-3); background: var(--color-surface, #f7f7f5);
              border-radius: var(--radius-md); }
.filter-bar .field { margin-bottom: 0; min-width: 160px; }
.filter-bar label { display: block; font-size: 0.8rem; font-weight: 600; margin-bottom: 2px; }
.filter-bar select, .filter-bar input[type="text"] { width: 100%; }

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md);
  border: none;
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  transition: transform 0.08s ease, box-shadow 0.15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary {
  background: linear-gradient(135deg, var(--color-gold-light), var(--color-gold));
  color: var(--color-franciscan-green-dark);
  box-shadow: 0 2px 6px rgba(201,162,39,0.35);
}
.btn-primary:hover { box-shadow: 0 4px 12px rgba(201,162,39,0.45); }
.btn-secondary {
  background: #fff;
  color: var(--color-franciscan-green);
  border: 1px solid var(--color-border);
}
.btn-secondary:hover { border-color: var(--color-franciscan-green); }
.btn-block { width: 100%; }
/* Compact button for in-table actions (Approve/Reject/Post to QBO/etc.) --
   moved here from new_request.css (2026-07-29): My Approvals, AP Review,
   and Vendor Approvals all use class="btn ... btn-sm" but none of them
   load new_request.css, so this rule was silently never applying on any of
   them -- every action button on those 3 pages had been rendering at full
   default .btn padding (12px/24px) crammed into a table row, which is
   exactly the "buttons look off/way out of whack" bug reported repeatedly
   across all three pages. */
.btn-sm { padding: 4px 12px; font-size: 0.82rem; }

/* 2026-09-10 (Jay): "there is no indication that it is working... changing
   the button to a red border circling." A spinning red ring around the
   button's own shape (border-radius: inherit tracks whatever radius the
   real .btn/.btn-sm rule gave it) -- the label text stays visible/readable
   underneath, only the button becomes non-interactive. Cleared naturally
   by the page navigating/reloading on completion (Post to QBO / Submit
   Request), never by JS -- see static/js/submit_loading.js. */
.btn-loading { position: relative; pointer-events: none; opacity: 0.85; }
.btn-loading::after {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: inherit;
  border: 3px solid rgba(156, 0, 6, 0.25); /* pale ring, always visible */
  border-top-color: var(--color-danger, #9c0006); /* bright arc, rotates */
  animation: btn-loading-spin 0.8s linear infinite;
}
@keyframes btn-loading-spin { to { transform: rotate(360deg); } }

/* ---- Cards / forms (shared) ---- */
.card {
  background: var(--color-card-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}
.form-card { background: var(--color-card-bg); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); padding: var(--space-5); margin-bottom: var(--space-5); }
.form-card h2 { font-size: 1.1rem; border-bottom: 1px solid var(--color-border); padding-bottom: var(--space-2); margin-bottom: var(--space-4); }
.field { margin-bottom: var(--space-4); }
.field label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: var(--space-1); color: var(--color-ink); }
.field .hint { font-size: 0.8rem; color: var(--color-text-muted); margin-top: var(--space-1); }
input[type=text], input[type=email], input[type=number], input[type=date], select, textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 0.95rem;
  background: #fff;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--color-gold);
  box-shadow: 0 0 0 3px rgba(201,162,39,0.18);
}
.field-readonly {
  padding: var(--space-2) var(--space-3);
  background: #f4f0e4;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-size: 0.95rem;
}
.field-readonly a { margin-left: var(--space-2); font-size: 0.85rem; }

/* Generic muted small-text utility -- .entity-picker .sub (portal.css) predates
   this and stays scoped there; this is the reusable version for any other page
   (added 2026-07-25 for vendor_requests.html / vendor_w9_upload.html). */
.sub { color: var(--color-text-muted); font-size: 0.85rem; }

.banner { padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); margin-bottom: var(--space-4); font-size: 0.92rem; }
.banner-error { background: #fbe9e9; color: var(--color-danger); border: 1px solid #f0c2c2; }
.banner-success { background: #e9f5ee; color: var(--color-success); border: 1px solid #b9e0c8; }
.banner-info { background: #eaf1ec; color: var(--color-franciscan-green); border: 1px solid #c9dccf; }

.badge { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 0.78rem; font-weight: 600; }
.badge-draft { background: #ece7da; color: var(--color-text-muted); }
.badge-submitted { background: #eaf1ec; color: var(--color-franciscan-green); }
/* "Submitted" (the payment_requests.status VALUE) was renamed "UnderReview"
   2026-07-26 (Task 3) -- badge-submitted kept above too, harmlessly unused,
   in case any historical audit_log-derived display ever needs the old
   status word rendered as a badge again. */
.badge-underreview { background: #eaf1ec; color: var(--color-franciscan-green); }
.badge-pending, .badge-pending-approval { background: #fdf0d6; color: #8a5a00; }
.badge-approved, .badge-cfo-override { background: #e0f0e6; color: var(--color-success); }
.badge-rejected { background: #fbe6e6; color: var(--color-danger); }
.badge-posted-to-qbo { background: #dfe9e2; color: var(--color-franciscan-green); }
.badge-cancelled { background: #ece7da; color: var(--color-text-muted); }
/* AP Review Workflow Plan.md (2026-07-26): 'Returned by AP' -- a payment_
   requests.status value, distinct from mid-chain 'Rejected' (Decision 1).
   'skipped' -- a checkreq.approval_actions row status (superseded-by-edit
   or the rest of a chain once one reject fires), used in the chain-history
   popup on /my-approvals. */
.badge-returned-by-ap { background: #fdf0d6; color: #8a5a00; }
.badge-skipped { background: #ece7da; color: var(--color-text-muted); }

/* Budget Overspend Tracking Plan.md (2026-07-26): a GL line exceeded its
   QBO-native annual budget but was allowed through (allow_overspend=true) --
   surfaced on /admin/ap-review per the plan's Section 6 resolution. */
.badge-overspend { background: #fbe6e6; color: var(--color-danger); }
.overspend-detail { color: var(--color-danger); }

table.simple-table { width: 100%; border-collapse: collapse; }
table.simple-table th { text-align: left; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-text-muted); border-bottom: 2px solid var(--color-border); padding: var(--space-2) var(--space-3); }
table.simple-table td { padding: var(--space-3); border-bottom: 1px solid var(--color-border); font-size: 0.92rem; }

/* Compact row-density modifier (2026-09-14) -- for a simple-table whose
   list is the point of the section (e.g. a user's Entity Roles), not a
   handful of rare rows -- tighter padding and a slightly smaller font so
   more of the list is visible without scrolling. */
table.simple-table.compact th { padding: var(--space-1) var(--space-2); }
table.simple-table.compact td { padding: 4px var(--space-2); font-size: 0.85rem; }

/* ---- My Requests table (2026-07-26 redesign, Task 1) ----
   table-layout:fixed + explicit per-column widths so a normal row fits on
   one line and never wraps to 2 lines (Jay's real screenshot showed this
   happening under the old auto-layout table, mostly on Program Area and
   the full org name -- which is why Entity now shows organizations.code
   ("EDOM"/"Claggett") instead of the full name too). white-space:nowrap on
   the columns that are short by nature (request #, type, entity, amount,
   status, date) so they can never wrap even under a narrow viewport;
   Program Area is deliberately the one column allowed to wrap (long
   program-area titles are the one field genuinely too long to guarantee
   single-line at every viewport width, and it's the least harmful one to
   wrap since it isn't a number/code/date the eye needs on one line). */
table.my-requests-table { table-layout: fixed; }
table.my-requests-table .col-reqnum { width: 10%; }
table.my-requests-table .col-type { width: 4%; }
table.my-requests-table .col-entity { width: 7%; }
table.my-requests-table .col-program { width: 15%; }
/* Vendor column (Task 3, 2026-07-26) -- who the payment is actually to,
   previously missing entirely from this table. */
table.my-requests-table .col-vendor { width: 12%; }
table.my-requests-table .col-submitter { width: 11%; }
table.my-requests-table .col-amount { width: 8%; }
table.my-requests-table .col-status { width: 11%; }
table.my-requests-table .col-date { width: 7%; }
/* Widened 13% -> 17% and right-aligned (2026-07-26, Jay: actions were
   getting silently ellipsis-truncated in the old 13% column -- "if you
   allow [truncation] then you have to allow the user to hover over it and
   see what is not displayed," which nothing here actually supported. Real
   estate borrowed from Type/Entity/Vendor/Submitter/Amount/Date, none of
   which need their full previous width for realistic real content -- Type
   is 2 chars, Entity is a short code, Date is a fixed-format string. */
table.my-requests-table .col-actions { width: 17%; }
table.my-requests-table td, table.my-requests-table th { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The status cell must never clip -- it's the popup's own positioning
   anchor (position:relative), and .status-popup's own overflow (set below)
   is independent of this cell's overflow:hidden regardless, but this avoids
   any ambiguity. */
table.my-requests-table td.status-cell { overflow: visible; }
/* Actions must show completely, never truncate -- a chopped "Edi…" link
   would be non-functional, not just ugly. Right-aligned per Jay's explicit
   preference (2026-07-26): "the options on each row to the right." If a
   future action gets added and this still doesn't fit even with the width
   above, that's the signal to revisit (Jay: "we will remove it later if
   you can't show it completely") -- not to silently let it truncate again. */
table.my-requests-table td.col-actions { overflow: visible; text-overflow: clip; text-align: right; }
/* Program Area is the one column allowed to actually wrap onto a second
   line rather than truncate with an ellipsis (see comment above). Targeted
   by class, not nth-child, since Program Area's column position shifts
   when the CFO "All Requests" view inserts the Submitted By column. */
table.my-requests-table td.program-area-cell { white-space: normal; }

.type-legend { color: var(--color-text-muted); font-size: 0.82rem; margin-bottom: var(--space-3); }

/* ---- Admin review tables (My Approvals / AP Review / Vendor Approvals) ----
   2026-07-28: first attempt at My Approvals (fixed-percentage columns,
   ellipsis truncation) genuinely made things worse -- Jay's own screenshot
   showed truncated HEADER TEXT ("REQU...", "SUBMIT...") and the Chain
   column's "View chain" link text overflowing sideways into the Submitted
   column, because a too-narrow fixed-width cell with overflow:visible (kept
   for the chain popup's own positioning) lets its OWN text bleed into the
   neighbor, not just the popup. Rebuilt 2026-07-29 on a fundamentally
   different, more robust approach: auto table layout (browser sizes columns
   to real content, not a guessed percentage), generous min-widths as a
   floor rather than a hard cap, wrap instead of truncate everywhere, and
   the whole table wrapped in a horizontally-scrolling container (see
   .table-scroll below) as the actual safety net for a narrow viewport --
   instead of fighting to cram 10+ columns into a fixed 1100px page width. */
.page-shell-wide { max-width: 1500px; margin: 0 auto; padding: var(--space-6) var(--space-5); }
.table-scroll { overflow-x: auto; }
table.approvals-table { table-layout: auto; width: 100%; }
table.approvals-table th, table.approvals-table td { white-space: normal; vertical-align: middle; }
/* 2026-09-10: AP Review's batch-select checkbox column. */
table.approvals-table .col-select { width: 32px; text-align: center; }
table.approvals-table .col-reqnum { min-width: 95px; }
table.approvals-table .col-reqnum a { font-weight: 600; white-space: nowrap; }
table.approvals-table .col-type, table.approvals-table .col-entity { min-width: 55px; white-space: nowrap; }
table.approvals-table .col-program { min-width: 150px; }
table.approvals-table .col-neededby, table.approvals-table .col-date { min-width: 95px; white-space: nowrap; }
table.approvals-table .col-vendor { min-width: 200px; }
table.approvals-table .col-submitter { min-width: 130px; }
table.approvals-table .col-amount { min-width: 85px; white-space: nowrap; }
table.approvals-table .col-chain { min-width: 90px; white-space: nowrap; }
table.approvals-table .col-status { min-width: 130px; }
table.approvals-table .col-actions { min-width: 260px; text-align: right; white-space: nowrap; }
/* The chain-popup/status-pill cell must never clip -- .status-popup itself
   is position:absolute and needs to render outside this cell's own bounds. */
table.approvals-table td.status-cell { overflow: visible; position: relative; }

/* ---- Impersonate a User (2026-09-14) ----
   Same auto-layout + min-width + wrap-not-truncate approach as
   .approvals-table above -- the one this codebase already learned (2026-07-29)
   works better than a fixed-percentage layout for a table with a variable-
   width badge column. The real fix for "Become this user" wrapping across 3
   lines was this (a real min-width floor on the action column, plus
   .btn-sm + nowrap on the button) combined with shortening the label to
   "Impersonate". */
table.impersonate-table { table-layout: auto; width: 100%; }
table.impersonate-table th, table.impersonate-table td { white-space: normal; vertical-align: middle; padding: var(--space-2) var(--space-3); }
table.impersonate-table .col-name { min-width: 140px; }
table.impersonate-table .col-email { min-width: 220px; }
table.impersonate-table .col-roles { min-width: 160px; }
table.impersonate-table .col-action { min-width: 110px; white-space: nowrap; text-align: right; }
table.impersonate-table .col-action .btn { white-space: nowrap; }

/* ---- CFO "My Requests" / "All Requests" toggle (Task 6) ---- */
.view-toggle { display: flex; gap: var(--space-2); margin-bottom: var(--space-4); }
.view-toggle-tab {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--color-text-muted);
  background: #ece7da;
  border: 1px solid var(--color-border);
  border-bottom: none;
}
.view-toggle-tab.active { background: var(--color-card-bg); color: var(--color-franciscan-green); }

/* ---- Status pill popup (Task 4) -- the first real modal-style UI in this
   app, per Jay's explicit instruction ("a real popup... not inline
   expansion"). Positioned relative to .status-cell, dismissed by
   my_requests.js's outside-click listener, never by CSS alone. ---- */
.status-cell { position: relative; }
.status-pill-btn { background: none; border: none; padding: 0; cursor: pointer; font: inherit; }
.status-popup {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 200;
  margin-top: var(--space-1);
  background: var(--color-card-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card-hover);
  padding: var(--space-3);
  /* Task 9 (2026-07-26 batch): the original 340px min-width forced every
     column (Date/Action/Status/Comment) to wrap onto multiple lines even
     for a typical short row. Widened substantially and anchored the popup
     to the RIGHT edge of .status-cell (was left:0) -- Status is one of the
     later columns in this table, so a much wider popup growing left
     instead of right stays inside the page rather than overflowing off the
     right edge of the viewport. max-width still caps it on a narrow screen.
     A first attempt left table-layout as auto (letting columns size to
     their own content) -- live-tested and found this actually made things
     WORSE: auto layout's percentage-width hint on the Comment column
     doesn't reliably translate to real space in practice, and a real
     26-character comment ("Group 1: mdickson-patrick") wrapped onto 3
     lines even at 460px wide (confirmed via getClientRects() in a live
     browser check, not assumed). Switched to table-layout:fixed with
     explicit pixel widths per column instead -- fixed layout's widths are a
     hard guarantee, not a hint. */
  min-width: 800px;
  max-width: min(90vw, 800px);
  white-space: normal;
}
.status-popup-title { font-weight: 700; font-size: 0.85rem; margin-bottom: var(--space-2); color: var(--color-franciscan-green); }
table.status-history-table { width: 100%; border-collapse: collapse; font-size: 0.8rem; table-layout: fixed; }
table.status-history-table th { text-align: left; color: var(--color-text-muted); border-bottom: 1px solid var(--color-border); padding: var(--space-1) var(--space-2); }
table.status-history-table td { padding: var(--space-1) var(--space-2); border-bottom: 1px solid var(--color-border); }
/* Date/Action/Status are short, fixed-format strings -- given a generous
   fixed pixel width each (live-measured against real data to comfortably
   fit the longest real values in this app -- "2026-07-25 23:53", "Edited —
   Approval Reset", "Submitted → Submitted" -- all confirmed one line at
   these widths) and never wrap. Comment gets whatever's left (fixed
   layout: an unspecified column absorbs remaining table width -- live-
   measured at ~210px of real usable text width here, enough for a typical
   short comment like "Group 1: mdickson-patrick" to render on one line)
   and is the only column still free to wrap when a comment genuinely is
   long (e.g. the vendor/amount-change audit comment, which will always
   need multiple lines regardless of column width). */
table.status-history-table th:nth-child(1), table.status-history-table td:nth-child(1) { width: 150px; white-space: nowrap; }
table.status-history-table th:nth-child(2), table.status-history-table td:nth-child(2) { width: 195px; white-space: nowrap; }
table.status-history-table th:nth-child(3), table.status-history-table td:nth-child(3) { width: 205px; white-space: nowrap; }
table.status-history-table th:nth-child(4), table.status-history-table td:nth-child(4) { white-space: normal; word-break: break-word; }

/* ---- Action modal (2026-07-29) ----
   Jay: "not a Chrome dialogue box." Replaces window.prompt()/confirm() for
   Approve-with-Note/Reject/Return-to-Submitter -- a real full-screen
   overlay + centered dialog, styled to match the rest of the app. Built
   once here, shared by any page via action_modal.js (approve.confirm()
   works the same as approve.prompt() did, minus the native chrome). */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 28, 22, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.modal-box {
  background: var(--color-card-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card-hover);
  padding: var(--space-5);
  width: 100%;
  max-width: 440px;
  margin: var(--space-4);
}
.modal-title { font-weight: 700; font-size: 1.05rem; margin-bottom: var(--space-2); color: var(--color-franciscan-green); }
.modal-hint { font-size: 0.85rem; color: var(--color-text-muted); margin-bottom: var(--space-3); }
.modal-box textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 0.92rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  resize: vertical;
  box-sizing: border-box;
}
.modal-error { color: var(--color-danger); font-size: 0.82rem; margin-top: var(--space-2); min-height: 1.2em; }
.modal-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-3); }

/* Approve split-button (Approve / Approve with Note options) */
.split-btn { position: relative; display: inline-flex; }
.split-btn .btn-caret {
  padding: 4px 6px;
  border-left: 1px solid rgba(0,0,0,0.15);
}
.split-btn-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 50;
  margin-top: 2px;
  background: var(--color-card-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card-hover);
  white-space: nowrap;
}
.split-btn-menu button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: var(--space-2) var(--space-3);
  font-size: 0.82rem;
  font-family: var(--font-body);
  cursor: pointer;
}
.split-btn-menu button:hover { background: #f4f0e4; }

/* ---- Plain link-styled buttons (Cancel, Task 2) -- a <button> inside a
   <form> so the POST can't be triggered by a bare GET link, but styled to
   read exactly like the adjacent PDF/Edit <a> links. ---- */
.cancel-form { display: inline; }
.link-button { background: none; border: none; padding: 0; margin: 0; font: inherit; color: var(--color-franciscan-green); text-decoration: underline; cursor: pointer; }
.cancel-btn { color: var(--color-danger); }

/* ---- Feedback (Task 10) -- Jay's exact required header text, styled to
   read as a warm callout, not a plain paragraph. ---- */
.feedback-card { max-width: 900px; } /* widened 2026-08-02 feedback batch, Item 4 -- was 640px */
.feedback-gift-line {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--color-franciscan-green);
  background: #eaf1ec;
  border: 1px solid #c9dccf;
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}
.feedback-card textarea { width: 100%; }

/* ---- Conversational Feedback (2026-08-02) -- replaced the one-shot
   textarea with a real back-and-forth. Simple bubble list, no framework. ---- */
.chat-messages {
  max-height: 420px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-2);
  margin-bottom: var(--space-3);
  background: #faf8f2;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.chat-msg { display: flex; }
.chat-msg-assistant { justify-content: flex-start; }
.chat-msg-user { justify-content: flex-end; }
.chat-bubble {
  max-width: 78%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: 0.92rem;
  line-height: 1.45;
  white-space: pre-wrap;
}
.chat-msg-assistant .chat-bubble { background: #eaf1ec; color: var(--color-ink); border: 1px solid #c9dccf; }
.chat-msg-user .chat-bubble { background: var(--color-franciscan-green); color: #f4f1e8; }
.chat-input-row { display: flex; gap: var(--space-2); align-items: flex-end; margin-bottom: var(--space-3); }
.chat-input-row textarea { flex: 1; resize: vertical; }
.chat-actions { display: flex; align-items: center; gap: var(--space-3); }

/* ---- Parish Portal S4+S5 (2026-08-08): announcements feed, document
   listings, parish-request cards. Reuses existing tokens/card shape --
   nothing here overrides the parish-mode red theme, it just inherits it via
   --color-franciscan-green like everything else already does. ---- */
.announcement-item { border-bottom: 1px solid var(--color-border); padding: var(--space-3) 0; }
.announcement-item:last-child { border-bottom: none; }
.announcement-title { font-weight: 700; color: var(--color-franciscan-green); }
.announcement-date { font-size: 0.78rem; color: var(--color-text-muted); margin-bottom: var(--space-1); }
.announcement-body { font-size: 0.92rem; white-space: pre-wrap; }

.doc-area { margin-bottom: var(--space-5); }
.doc-area h2 { font-size: 1.05rem; margin-bottom: var(--space-2); }
/* Collapsible Documents-page sections (2026-08-16, Jay: "allow for
   collapsing the sections so you don't have to scroll so much") -- same
   proven <details>/<summary> pattern as admin_setup.css's .setup-section,
   reused rather than reinvented. Each .doc-area is now a <details>, its
   <h2> moved inside a <summary>. Includes the same author-rule-beats-UA-
   rule fix that pattern's own comment already documents: the browser's UA
   stylesheet hides a closed <details>'s children for free, but ANY author
   rule setting `display` on a child (e.g. .doc-upload-row{display:flex})
   overrides that regardless of specificity -- re-asserting display:none as
   an author rule of our own is what actually makes collapse work. */
.doc-area > summary { display: flex; align-items: center; gap: var(--space-2); cursor: pointer; list-style: none; padding: var(--space-1) 0; }
.doc-area > summary::-webkit-details-marker { display: none; }
.doc-area > summary::before { content: '\25be'; display: inline-block; transition: transform 0.15s ease; }
.doc-area:not([open]) > summary::before { transform: rotate(-90deg); }
.doc-area > summary h2 { margin: 0; }
.doc-area:not([open]) > *:not(summary) { display: none !important; }
table.doc-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
table.doc-table th { text-align: left; text-transform: uppercase; letter-spacing: 0.03em; font-size: 0.72rem; color: var(--color-text-muted); border-bottom: 2px solid var(--color-border); padding: var(--space-2) var(--space-3); }
table.doc-table td { border-bottom: 1px solid var(--color-border); padding: var(--space-2) var(--space-3); vertical-align: middle; }
.doc-empty { color: var(--color-text-muted); font-size: 0.88rem; padding: var(--space-2) 0; }

/* Recursive file-tree browser (2026-08-16, replacing the click-into-a-
   folder/breadcrumb design per Jay's direct correction: "load the entire
   tree... let the user open the tree and select files"). Nested <details>
   give free expand/collapse with no JS -- same reasoning as .doc-area's
   own collapsible-section pattern, just one level deeper and repeatable. */
.doc-tree, .doc-tree ul { list-style: none; margin: 0; padding-left: 0; }
.doc-tree ul { padding-left: var(--space-4); }
.doc-tree li { margin: 1px 0; }
.doc-tree details > summary { cursor: pointer; list-style: none; padding: 3px 0; font-size: 0.88rem; }
.doc-tree details > summary::-webkit-details-marker { display: none; }
.doc-tree details > summary::before { content: '\25b8'; display: inline-block; width: 1em; transition: transform 0.15s ease; }
.doc-tree details[open] > summary::before { transform: rotate(90deg); }
.doc-tree-file { display: flex; align-items: center; gap: var(--space-2); font-size: 0.88rem; padding: 3px 0; }
.doc-tree-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.doc-tree-size { color: var(--color-text-muted); font-size: 0.78rem; white-space: nowrap; }
.doc-tree-actions { display: flex; gap: var(--space-2); white-space: nowrap; align-items: center; }
.doc-tree-actions form { display: inline; }

.doc-upload-row { display: flex; gap: var(--space-2); align-items: center; margin-top: var(--space-3); }
.doc-upload-row input[type=file] { flex: 1; }

.badge-request-open { background: #eaf1ec; color: var(--color-franciscan-green); }
.badge-request-reviewed { background: #f5c04a33; color: var(--color-warning); }
.badge-request-closed { background: #ece7da; color: var(--color-text-muted); }

.btn-link { background: none; border: none; padding: 0; margin-left: var(--space-2); color: var(--color-danger); text-decoration: underline; font-size: 0.85rem; cursor: pointer; }
.btn-link:hover { opacity: 0.8; }

/* Moved here from portal.css (2026-08-08) -- these were previously only
   defined in portal.css, which only /portal itself loads. Any other page
   using the same .module-grid/.module-tile pattern (parish_view.html, and
   whatever comes next) rendered them as bare unstyled <a> tags -- Jay:
   "why did you just do options as underscored lines... use buttons like
   you did there." Belongs in base.css (loaded by every page) since it's a
   general-purpose tile-button pattern, not portal-specific. */
.module-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-5);
}
.module-tile {
  background: var(--color-card-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
  text-decoration: none;
  color: var(--color-ink);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  position: relative;
  transition: box-shadow 0.15s ease, transform 0.1s ease;
}
a.module-tile:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-3px); }
.module-tile .icon { width: 40px; height: 40px; color: var(--color-gold); }
.module-tile h3 { margin: 0; font-size: 1.05rem; }
.module-tile .desc { color: var(--color-text-muted); font-size: 0.88rem; margin: 0; }
.module-tile.disabled { opacity: 0.55; cursor: default; }
.module-tile.disabled .icon { color: var(--color-text-muted); }

/* Tile unread-count badges (2026-08-08, Jay: "put a number in the upper
   right hand of the box... bold the number [if new/unseen]"). Count always
   shows (matches every other badge in this app); bold + a slightly
   stronger color is the only "unseen" signal, not a second number. */
.tile-badge {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 999px;
  background: #ece7da;
  color: var(--color-text-muted);
  font-size: 0.78rem;
  font-weight: 500;
  display: flex;
  align-items: center;
  justify-content: center;
}
.tile-badge-new {
  background: var(--color-danger);
  color: #fff;
  font-weight: 800;
}

/* Diocese-name brand suffix, next to "Beacon" (2026-08-08) -- shown only
   when the entity switcher is suppressed (parish_view set), replacing the
   context it would otherwise show. */
/* Wraps .brand + .brand-diocese so .site-header's own space-between rule
   treats them as ONE item, not two spread across the row (real bug, Jay:
   "it is rendering, just too far to the right" -- .site-header's flex
   children were .brand/.brand-diocese/nav as 3 separate items). */
/* align-items: stretch (was center) so .brand-logo, the one child that
   wants the bar's full height, can actually reach it -- .brand/.brand-
   diocese keep themselves vertically centered via their OWN padding
   (base.css .site-header .brand rule, and below), unaffected by the
   stretch. */
.brand-group { display: flex; align-items: stretch; }
/* 2026-08-29 correction, Jay: the diocese's own logo was sitting BEFORE
   "Beacon," reading as if it were Beacon's own app icon (a lighthouse,
   still to come, separate from any of this). It belongs after the
   wordmark's own vertical separator, right before the diocese's name --
   so .brand-diocese itself is now the flex/stretch container (logo +
   name as its two children), not .brand-group directly. No vertical
   padding here (unlike .brand's own rule above) -- that's what lets
   .brand-logo's height:100% resolve against this element's FULL
   stretched height instead of a padding-shrunk one; .brand-diocese-name
   carries its own padding instead, so the TEXT still sits comfortably
   spaced while the logo fills the bar edge to edge. */
.brand-diocese {
  margin-left: var(--space-3);
  padding-left: var(--space-3);
  border-left: 1px solid rgba(255,255,255,0.35);
  display: flex;
  align-items: stretch;
}
.brand-diocese-name {
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-weight: 500;
  color: rgba(255,255,255,0.85);
  display: flex;
  align-items: center;
  padding: var(--space-3) 0;
}
/* A diocese's own uploaded logo (2026-08-29). The uploaded crests are
   transparent PNGs -- no background/padding here, the transparency sits
   directly on the header's own mode color, which is the whole point of
   supplying a transparent file. height:100% of .brand-diocese (itself
   stretched to .site-header's own explicit height, see that rule's
   2026-08-29 note) so the logo fills the bar vertically, edge to edge,
   whatever width that implies for its own aspect ratio. */
.brand-logo {
  height: 100%;
  width: auto;
  margin-right: var(--space-2);
  display: block;
}
