/* 2026-09-15 redesign - "Disbursement Voucher" direction, picked from a 9-way comparison
   (design_directions.html at the project root). Every real Construction collection here
   eventually needs an SI, a 2307, a DV, sometimes an SWA - real Philippine accounting forms:
   bond paper, boxed ruled fields, all-caps labels, a navy letterhead. This redesign borrows
   that language for the whole app rather than an arbitrary palette. Every class name below
   is unchanged from the original theme (see git history for the maroon/near-black JCC
   branding it replaces) - this is a token + rule rewrite, not a markup change, so
   public/js/app.js needed zero edits for the re-skin itself (its doc-type color-coding
   calls are the one deliberate exception - see the "Document-type badges" block below).
   Login wallpaper photo lives at public/images/wallpaper.jpg (kept from the prior theme). */
:root {
  --bg: #F3EFE2;        /* bond paper - the page's own ground, one shade off the form face */
  --panel: #FBF9F2;     /* the form face itself - closer to pure bond white */
  --border: #D8D2BE;    /* a document's own ruled line, not a UI grey */
  --text: #2B2B28;      /* typewritten ink */
  --muted: #6B6656;     /* the same ink, faded */
  --maroon: #A6192E;    /* required-field red - JCC's own brand red, shifted to the voucher's exact hue */
  --maroon-dark: #841222;
  --navy: #1E3A5F;      /* the form's own letterhead navy - topbar/sidenav chrome */
  --navy-light: #2C4E78;
  --danger: #A6192E;    /* one red family with --maroon, not a second unrelated red */
  --success: #0F6B5C;   /* an approved rubber-stamp teal, not a generic UI green */
  --warning: #B8862E;   /* a caution gold, not a generic UI amber */
  --radius: 4px;        /* boxed document fields read sharper than a soft SaaS radius */

  /* Document-type badges (SI/2307/DV/SWA) - one fixed color per type, reused everywhere a
     doc type appears: the attachment list, the "still needed" chips, and the Lacking
     Documents report (public/js/app.js's docTypePillClass()). The real functional piece of
     this redesign - a color always means the same document, not just a different theme. */
  --doc-si: #0F6B5C;
  --doc-2307: #B8862E;
  --doc-dv: #1E3A5F;
  --doc-swa: #6B4C7A;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: 'Work Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
}
h1, h2, h3 { font-family: 'Spectral', Georgia, serif; font-weight: 600; }
.hidden { display: none !important; }
.mono { font-family: 'IBM Plex Mono', "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; }
/* Every table's money/numeric column uses class="mono" on its <td> (and usually its <th>) -
   right-aligning just these two (not the bare .mono class, which is also used on a few
   standalone figures like the receivable tiles that should stay left-aligned under their
   own label) pushes every number flush to its column's right edge, the normal convention
   for a financial table, instead of sitting wherever the text happens to fall in a much
   wider column. */
table td.mono, table th.mono { text-align: right; }
button {
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--panel);
  padding: 7px 12px;
}
button.primary { background: var(--maroon); border-color: var(--maroon); color: #fff; }
button.primary:hover { background: var(--maroon-dark); }
button.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.danger-link { color: var(--danger); }
button:disabled { opacity: .5; cursor: not-allowed; }
/* Row action buttons (Sales/Ledger/etc.) shown as a single glyph instead of a text label -
   a fixed square instead of the default text padding, so the icon centers cleanly. */
button.icon-btn { padding: 4px 7px; line-height: 1; font-size: 14px; }
.nowrap { white-space: nowrap; }
input, select, textarea {
  font-family: inherit;
  font-size: 13px;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 4px;
  width: 100%;
}
/* The rule above is meant for text/number/date/select fields - applied to a checkbox or
   radio too, "width:100%" stretches its invisible hit box to fill the whole flex row (the
   native tick square still renders small, top-left of that box), so its label text (right
   after it, separated only by a `gap`) ends up rendered far away from the visible checkbox
   instead of right beside it. Reset those two input types back to their natural small size. */
input[type="checkbox"], input[type="radio"] {
  width: 16px; height: 16px; padding: 0; border-radius: 3px; flex: 0 0 auto;
}
label { display: block; font-size: 12px; color: var(--muted); margin: 8px 0 4px; }
/* table-layout defaults to auto (columns size to content) - combined with nowrap below and
   .card's own overflow-x:auto, a wide row scrolls sideways inside its card instead of each
   cell wrapping into several lines and making every row tall. .wrap opts a specific column
   (e.g. free-text Remarks) back into normal wrapping where that reads better than a very
   wide column. */
table { width: 100%; border-collapse: collapse; font-size: 13px; font-family: 'IBM Plex Mono', "SFMono-Regular", Consolas, monospace; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; }
td.wrap { white-space: normal; }
/* .table-scroll-top - a wide, un-height-bounded table (SWA/Billed's own "All
   Accomplishments" lists, MODULE_CATALOG etc.) has its horizontal scrollbar sit below every
   row, at the very bottom of a potentially very long table - per the user, that meant
   scrolling all the way down past dozens of rows just to reach the control that scrolls
   sideways, then back up again. Flips the scrolling container upside down (rotateX) and
   flips its contents back the right way up again - a standard CSS-only "scrollbar at the
   top" trick that needs no JavaScript to keep a fake control in sync. overflow-y stays
   hidden (not auto) since these cards were never meant to scroll vertically in the first
   place - the page itself scrolls for a long table; only the sideways scrollbar's own
   position moves. */
.table-scroll-top { overflow-x: auto; overflow-y: hidden; transform: rotateX(180deg); }
.table-scroll-top > table { transform: rotateX(180deg); }
/* Table headers stay in the UI body face (not the mono data face) and read like a form's
   own boxed field labels - a navy rule underneath instead of the plain border every other
   row gets, so the header reads as the table's own letterhead. */
th {
  color: var(--navy); font-weight: 700; font-size: 11px; text-transform: uppercase;
  letter-spacing: .04em; font-family: 'Work Sans', sans-serif; border-bottom: 2px solid var(--navy);
}

/* A wide/tall data grid (the Reports tables - Receivable, Monthly Comparison, Contract
   Value, Retention Receivable, Mobilization Recoupment) - .card's plain overflow-x:auto
   puts the horizontal scrollbar below however many rows there are, which for 100+ rows is
   effectively unreachable. This bounds the panel's own height (both scrollbars stay right
   at the viewport, not below all the data) and shrinks the type a bit so more columns fit
   before scrolling is even needed. The header row stays pinned while scrolling down. */
.table-scroll {
  overflow: auto;
  max-height: 65vh;
  font-size: 12px;
}
/* border-collapse:collapse (table's own base rule) merges each cell's border into its
   neighbor's, which breaks the sticky header's opaque background at those shared border
   seams - a scrolled-past row's text can bleed through right at the header's edge instead of
   being fully covered. border-separate (with border-spacing:0 so it still looks collapsed)
   gives every cell, including the sticky header's, its own real box the sticky positioning
   and background can cover completely. */
.table-scroll table { border-collapse: separate; border-spacing: 0; }
.table-scroll th, .table-scroll td { padding: 6px 8px; }
.table-scroll thead th {
  position: sticky; top: 0; background: var(--panel); z-index: 1;
  box-shadow: 0 1px 0 var(--border);
}
/* Higher specificity than the plain ".table-scroll td" rule above, so the Ledger's inline
   edit-row panel keeps its own roomier padding and normal (not compacted) field text
   instead of inheriting the tight data-row sizing meant for the read-only grid. */
.table-scroll tr.edit-row > td { padding: 14px 10px; font-size: 13px; white-space: normal; }
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
  overflow-x: auto; /* a wide table inside can scroll itself instead of blowing out the page */
}
/* Every .card is, conceptually, one boxed field on the form - a second, fainter inset rule
   just inside the border reads like a form's own printed field-box, not a generic UI panel. */
.card { box-shadow: inset 0 0 0 3px var(--bg); }
.card h3 { margin-top: 0; }
.grid { display: grid; gap: 16px; }
.grid-2 { display: grid; gap: 0 16px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* CSS Grid rather than flex-wrap, for predictable, equal-width field tracks instead of
   flexbox's flex-basis/min-width sizing (which a long label + <select> could out-grow in
   inconsistent ways). Grid alone doesn't stop overflow, though - a cell's default
   overflow:visible lets content past its edge into the next cell same as flexbox would,
   which is what actually caused the Sales edit row to overlap once Payment Method was added
   (see the white-space:normal fix on tr.edit-row > td below - that's the piece that stops a
   <select> from being forced onto one unbreakable line wide enough to spill over). */
.form-inline { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px 14px; align-items: end; }
.form-inline label { margin: 0; min-width: 0; }
.form-inline label select, .form-inline label input { max-width: 100%; }
.form-inline button { justify-self: start; align-self: end; }

/* Inline "edit this row" panel (Team/Bank Accounts/Ledger/Sales) - a light tint + generous
   padding so it reads as its own distinct editing panel, not just another table row.
   white-space:normal overrides the table's own nowrap (th,td above), which these panels
   would otherwise inherit - without it, a <select>'s label text and its own content get
   forced onto one unbreakable line, pushing the select past its grid cell's edge and over
   the next field instead of wrapping below the label the way it does everywhere else. */
tr.edit-row > td { background: var(--bg); padding: 14px 10px; white-space: normal; }

/* This <td> has colspan across every column, so its rendered width is the table's FULL
   column width - for a money-heavy table (Ledger, Projects) that can run 2000-3000px+.
   Letting .form-inline's own grid (defined above) fill whatever width it's given spread a
   row's fields edge to edge and pushed Save/Cancel thousands of pixels off to the right -
   unreachable without a long horizontal scroll (root cause of "the edit button does
   nothing": it worked, Save was just off-screen).
   First fix here forced the whole form into one narrow column (display:block) - that kept
   Save reachable, but made a form with a lot of fields (Ledger's edit row has ~18) read as
   one very tall, cramped list instead of a normal-looking form. Capping the grid's own
   max-width instead - rather than removing the grid - keeps it wrapping into a proper
   multi-column layout (same auto-fill behavior as every other form-inline in the app) while
   still guaranteeing Save/Cancel sit within a fixed, short, reachable distance no matter how
   wide the table itself happens to be. */
tr.edit-row .form-inline { max-width: 960px; }

.topbar {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--navy); color: #fff; border-bottom: 1px solid var(--navy-light);
  padding: 10px 20px;
}
.brand { font-weight: 700; display: flex; align-items: center; gap: 10px; font-family: 'Spectral', serif; }
.brand-title { font-size: 13px; color: #D9D2C2; font-weight: 600; }
.badge {
  background: var(--maroon); color: #fff; border-radius: 4px; padding: 3px 8px;
  font-size: 10px; font-weight: 700; letter-spacing: .4px;
}
.whoami-wrap { display: flex; align-items: center; gap: 14px; }
.whoami { color: #D9D2C2; font-size: 12px; }
.whoami button {
  margin-left: 10px; color: #D9D2C2; font-size: 12px; background: transparent;
  border: 1px solid rgba(255,255,255,.25); border-radius: 4px; padding: 5px 10px;
}
.whoami button:hover { background: rgba(255,255,255,.1); }

.landing {
  display: flex; align-items: center; justify-content: center; min-height: calc(100vh - 49px - 40px);
  position: relative;
}
.landing-bg {
  background-image: linear-gradient(rgba(30,58,95,.72), rgba(30,58,95,.72)), url('/images/wallpaper.jpg');
  background-size: cover;
  background-position: center;
}
.landing-card { max-width: 340px; width: 100%; text-align: center; }
.login-badge { display: inline-block; margin-bottom: 12px; }
.login-subtitle { color: var(--muted); font-weight: 600; margin-top: 0; }
.landing-card input { text-align: left; }

.shell { display: flex; min-height: calc(100vh - 49px - 40px); }
.sidenav { width: 210px; background: var(--navy); padding: 12px; flex-shrink: 0; }
/* Desktop sidebar collapse (the mobile off-canvas drawer below has its own separate
   mechanism) - lets Billing reclaim the sidebar's 210px for the Reports tables, which are
   wide enough that every extra column of width helps. Collapsed state is a per-browser
   convenience (localStorage, app.js), not synced - each person's own screen, not shared
   data. display:none rather than width:0 so nothing in the sidenav (dropdowns, focus) is
   reachable while collapsed. */
.shell.sidenav-collapsed .sidenav { display: none; }
.navbtn {
  display: block; width: 100%; text-align: left; background: transparent; border: none;
  padding: 9px 10px; margin-bottom: 2px; border-radius: 4px; color: #D9D2C2; font-size: 12px;
  font-weight: 600; text-transform: uppercase; letter-spacing: .03em;
}
.navbtn:hover { background: var(--navy-light); color: #fff; }
.navbtn.active { background: var(--maroon); color: #fff; }
.navgroup-label {
  color: #9a9482; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; padding: 14px 10px 4px;
}
.navbtn-sub { padding-left: 22px; }
/* 1100px was tight enough that wide tables (Team, Access Rights, Ledger, Sales) needed a
   horizontal scrollbar even on an ordinary desktop monitor, wasting several hundred pixels
   of empty space to its right. This is the ONE wrapper every page renders inside (index.html
   <main class="content">), so raising it here widens every module at once - 1600px still
   caps line length on very wide/ultrawide monitors, it just stops being the bottleneck for
   an 8-10 column table on a normal screen.
   min-width:0 overrides a flex item's default min-width:auto, which otherwise lets this
   grow past its allotted space to fit a wide table's intrinsic width instead of shrinking to
   it - without it, a table wide enough (Access Rights' one-column-per-module grid) pushes
   the whole PAGE wider than the viewport instead of scrolling inside its own .card, taking
   the sidenav and everything else along with it off-screen. */
.content { flex: 1; min-width: 0; padding: 20px; max-width: 1600px; }

.appfooter {
  text-align: center; color: var(--muted); font-size: 12px; padding: 12px; height: 40px;
  box-sizing: border-box; border-top: 1px solid var(--border); background: var(--panel);
}

/* Notifications (toasts + confirm dialogs) - centered on screen, one consistent maroon
   family for both, differentiated by icon rather than a red/green split. */
.toast {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  padding: 14px 22px; border-radius: 6px; color: #fff; font-size: 14px; font-weight: 600;
  z-index: 500; max-width: 420px; text-align: center; display: flex; align-items: center;
  justify-content: center; gap: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.4);
  border: 1px solid rgba(255,255,255,.18);
}
.toast.ok { background: linear-gradient(135deg, var(--maroon), var(--maroon-dark)); }
.toast.error { background: linear-gradient(135deg, var(--navy-light), var(--navy)); }
.toast::before { font-size: 15px; }
.toast.ok::before { content: "\2713"; } /* checkmark */
.toast.error::before { content: "\26A0"; } /* warning triangle */

.modal-overlay { z-index: 200; }
.modal-card {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 201; max-width: 360px; width: 90%; border-top: 4px solid var(--maroon);
  box-shadow: 0 16px 40px rgba(0,0,0,.35);
}
.modal-card h3 { color: var(--navy); }
.modal-card .row { justify-content: flex-end; margin-top: 16px; }

.pill {
  display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px;
  font-weight: 600; background: var(--bg); color: var(--muted);
}
.pill-active { background: color-mix(in srgb, var(--success) 16%, var(--panel)); color: var(--success); }
.pill-pending { background: color-mix(in srgb, var(--warning) 18%, var(--panel)); color: var(--warning); }
/* Document-type badges (SI/2307/DV/SWA) - the Disbursement Voucher direction's one real
   functional piece: a fixed color per document type, used everywhere that type is named
   (credit detail's attachment list and "still needed" chips, Reports > Lacking Documents),
   applied via public/js/app.js's docTypePillClass(). A color always means the same document,
   not just a different look per page. */
.pill-doc-si { background: color-mix(in srgb, var(--doc-si) 16%, var(--panel)); color: var(--doc-si); }
.pill-doc-2307 { background: color-mix(in srgb, var(--doc-2307) 18%, var(--panel)); color: var(--doc-2307); }
.pill-doc-dv { background: color-mix(in srgb, var(--doc-dv) 14%, var(--panel)); color: var(--doc-dv); }
.pill-doc-swa { background: color-mix(in srgb, var(--doc-swa) 16%, var(--panel)); color: var(--doc-swa); }
.text-muted, .muted { color: var(--muted); }
.small { font-size: 12px; }

/* Click-to-sort column headers (public/js/app.js makeTableSortable) - the faint ⇅ on every
   sortable header says "this is clickable"; the active column swaps it for ▲/▼ in the brand
   red. Left as plain text in the th, so it never affects the header's own sizing much. */
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--maroon); }
th.sortable:focus-visible { outline: 2px solid var(--maroon); outline-offset: -2px; }
/* An unticked "Include" checkbox row - Reports > Contract Value (a project) and Reports >
   Receivable (a milestone) both use this same class/pattern: left out of that report's
   totals, but still listed (so it can be ticked back in) and just dimmed, then dropped from
   the printout entirely (print block below). The class name predates Receivable growing the
   same checkbox - kept as-is rather than renaming everywhere for a cosmetic-only rename. */
tr.cv-excluded { opacity: .5; }
.sort-ind { margin-left: 5px; font-size: 10px; opacity: .35; }
.sort-ind.active { opacity: 1; color: var(--maroon); }

/* Master Data > Projects: the SWA / Billed / Collected activity chips beside each project
   code (public/js/app.js projectTxChipsHtml) - answers "does anything depend on this project
   before I delete it". Reuses the document-type hues (SWA purple, Billed navy, Collected
   teal) so a color means the same stage as everywhere else; a zero is a faded dashed outline
   rather than hidden, so all three always line up in the same order down the column. */
.proj-code-cell { white-space: normal; min-width: 250px; }
.proj-code { font-weight: 600; }
.tx-chips { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-left: 8px; vertical-align: middle; }
.tx-chip {
  display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 10px; font-weight: 600;
  letter-spacing: .02em; white-space: nowrap; font-family: 'Work Sans', sans-serif;
}
.tx-swa { background: color-mix(in srgb, var(--doc-swa) 16%, var(--panel)); color: var(--doc-swa); }
.tx-billed { background: color-mix(in srgb, var(--doc-dv) 14%, var(--panel)); color: var(--doc-dv); }
.tx-collected { background: color-mix(in srgb, var(--success) 16%, var(--panel)); color: var(--success); }
.tx-zero { color: var(--muted); border: 1px dashed var(--border); font-weight: 500; }
.tx-empty { background: color-mix(in srgb, var(--warning) 18%, var(--panel)); color: var(--warning); }

.empty-state { color: var(--muted); font-style: italic; padding: 12px 0; text-align: center; }

/* Access Rights page: per-user module checkbox list (public/js/app.js moduleChecklistHtml).
   td's default vertical-align is "middle" - with one row's Pages cell far taller than its
   Name/Role cells (an Admin-level person can have up to 10 checkboxes vs. one line for
   everyone else), that centers the short cells around the tall row's middle instead of its
   top, scattering every row across the ones above and below it. Pin every cell in this
   table to the top instead so a tall row just pushes the next row down, like normal. */
.access-rights-table td { vertical-align: top; padding-top: 12px; }
/* One column per module (renderAccessRightsPage) runs wide enough that Name would scroll
   off the left edge along with everything else, leaving no way to tell which row you're
   looking at once you've scrolled a few columns in - pinned in place instead, same trick
   spreadsheets use for a frozen first column. Needs its own opaque background (matching the
   .card it sits in) so scrolled-under checkbox columns don't show through behind it. */
.access-rights-table th:first-child, .access-rights-table td:first-child {
  position: sticky; left: 0; background: var(--panel); z-index: 1;
  border-right: 1px solid var(--border);
}
/* Same two freezes combined, for the Receivable report's breakdown popup (openReceivableBreakdownModal) -
   per the user, once "On Process"/"For Payment"/etc. runs to dozens of milestones the Project
   column scrolls out of view sideways at the same time the header scrolls out of view upward,
   so a row far into either scroll direction has nothing left to say which project or which
   column it's showing. border-collapse:separate (not the table's own collapsed default) is the
   same fix .table-scroll already needs for its own sticky header - without it, adjoining
   cells' borders merge into the neighbor's, leaving a seam scrolled-under content can bleed
   through even with an opaque background set. */
.breakdown-table { border-collapse: separate; border-spacing: 0; }
.breakdown-table th, .breakdown-table td { padding: 6px 8px; }
.breakdown-table thead th {
  position: sticky; top: 0; background: var(--panel); z-index: 1;
  box-shadow: 0 1px 0 var(--border);
}
.breakdown-table th:first-child, .breakdown-table td:first-child {
  position: sticky; left: 0; background: var(--panel); z-index: 1;
  border-right: 1px solid var(--border);
}
/* The one cell frozen on BOTH axes at once (top-left corner) needs to sit above whichever of
   the other two sticky layers scrolls underneath it last, or its own background wouldn't
   fully cover a scrolled-under row/column at that corner. */
.breakdown-table thead th:first-child { z-index: 2; }
.checkbox-inline { display: flex; align-items: center; gap: 6px; font-weight: normal; white-space: normal; margin: 2px 0; }

.error-text, .error { color: var(--danger); font-size: 12px; text-align: left; margin: 8px 0 0; }

/* Type-to-search combobox (Project picker in Breakdown/Confirm) - a plain text input plus
   a positioned dropdown of matches below it, filtered client-side against a list already
   loaded in full (no extra request per keystroke). */
.suggestions { position: relative; }
.suggestion-list {
  position: absolute; z-index: 20; top: 100%; left: 0; right: 0; margin-top: 2px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 4px;
  max-height: 240px; overflow-y: auto; box-shadow: 0 8px 20px rgba(0,0,0,.12);
}
.suggestion-item { padding: 8px 10px; cursor: pointer; font-size: 13px; }
.suggestion-item:hover, .suggestion-item.active { background: var(--bg); }
.suggestion-item .muted { font-size: 11px; }
/* .card's own overflow-x:auto (for wide tables) has a side effect per the CSS spec: once
   either overflow axis isn't "visible", the OTHER axis is forced to "auto" too - so a card
   hosting this dropdown would clip it instead of letting it float below the card's own
   bounds. Any .card wrapping a .suggestions field needs this to undo that clipping. */
.card.suggestions-host { overflow: visible; }

/* Checkbox multi-select dropdown (Reports > Receivable's Client filter - pick several clients
   at once, not just one) - same positioned-panel-below-a-button shape as .suggestions above,
   just a list of checkboxes instead of clickable text matches. A card hosting one needs the
   same .suggestions-host overflow fix as a suggestions field does, for the same reason (a
   .card's own overflow-x:auto side effect would otherwise clip the panel instead of letting
   it float below the card's bounds) - reuse that class rather than duplicating the rule. */
.ms-dropdown { position: relative; }
.ms-dropdown-btn {
  width: 100%; text-align: left; background: var(--panel); border: 1px solid var(--border);
  border-radius: 4px; padding: 8px 10px; font-size: 13px; font-family: 'Work Sans', sans-serif;
  cursor: pointer; color: var(--text);
}
.ms-dropdown-panel {
  position: absolute; z-index: 20; top: 100%; left: 0; min-width: 240px; margin-top: 2px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 4px;
  box-shadow: 0 8px 20px rgba(0,0,0,.12); padding: 6px;
}
.ms-dropdown-actions { display: flex; gap: 6px; padding: 2px 4px 6px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.ms-dropdown-actions button { font-size: 11px; padding: 3px 8px; }
.ms-dropdown-options { max-height: 260px; overflow-y: auto; }
.ms-dropdown-option {
  display: flex; align-items: center; gap: 6px; padding: 5px 6px; font-size: 13px;
  border-radius: 3px; cursor: pointer; margin: 0;
}
.ms-dropdown-option:hover { background: var(--bg); }
.ms-dropdown-option input { width: 14px; height: 14px; flex: 0 0 auto; }

/* Monthly summary cards (Monthly Collections, SWA Summary, Billed Summary) - month rows are
   only clickable when the card was given an onMonthClick handler (see wireMonthlySummaryYearFilters
   in app.js), so this only affects the cards that opt in. */
tr.month-row-clickable { cursor: pointer; }
tr.month-row-clickable:hover { background: var(--bg); }

.nav-toggle {
  /* Was display:none here (shown only <=860px, below) back when this button just opened the
     mobile drawer - now doubles as the desktop sidebar collapse toggle too, so it needs to
     be visible at every width. */
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid rgba(255,255,255,.3); color: #fff;
  padding: 6px 10px; font-size: 16px; line-height: 1; border-radius: 6px;
}
.nav-toggle:hover { background: rgba(255,255,255,.1); }
.nav-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 40; }
.nav-overlay.open { display: block; }

/* ---------- Mobile / narrow viewport ---------- */
@media (max-width: 860px) {
  .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
  .sidenav {
    position: fixed; top: 49px; bottom: 0; left: 0; width: 240px; max-width: 80vw;
    transform: translateX(-100%); transition: transform .2s ease-out; z-index: 50;
    overflow-y: auto; box-shadow: 2px 0 12px rgba(0,0,0,.25);
  }
  .sidenav.open { transform: translateX(0); }
  .content { padding: 12px; max-width: 100%; }
  /* Name and the Sign out button sit on one line of their own under the brand. They used to be
     clipped to 120px, which hid the Sign out button on a phone. */
  .whoami-wrap { flex: 1 1 100%; }
  .sidenav { top: 86px; } /* the topbar is two lines tall on a phone */
  .whoami { max-width: 100%; white-space: normal; display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; }
  .topbar { padding: 10px 12px; flex-wrap: wrap; gap: 8px; }
  .brand { gap: 8px; }
  button, input, select, textarea { font-size: 15px; }
  button { padding: 9px 12px; }
  input, select, textarea { padding: 9px 10px; }
  th, td { padding: 8px; font-size: 12px; white-space: nowrap; }
}

@media (max-width: 480px) {
  .brand-title { max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .landing-card { padding: 20px 14px; }
  .toast, .modal-card { max-width: calc(100vw - 24px); }
}

/* ---------- Executive Dashboard (public/js/app.js, showDashboard) ---------- */
.dash-kpis { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; margin-top: 8px; }
.dash-kpi { margin: 0; }
.dash-kpi-label { text-transform: uppercase; letter-spacing: .04em; font-size: 11px; }
.dash-kpi-value { font-size: 20px; font-weight: 700; margin-top: 4px; color: var(--navy); }
.dash-kpi-sub { margin-top: 4px; line-height: 1.4; }
.dash-kpi-main { border-color: var(--navy); box-shadow: inset 0 0 0 2px var(--navy); }
.dash-h { margin: 26px 0 8px; color: var(--navy); }
.dash-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 12px; }
.dash-grid-2 .card { margin: 0; }
.dash-grid-2 h4 { margin: 0 0 10px; font-family: 'Spectral', Georgia, serif; font-size: 15px; }
.dash-bars { display: grid; gap: 8px; }
.dash-bar-row { display: grid; grid-template-columns: minmax(90px, 30%) 1fr auto; gap: 10px; align-items: center; font-size: 12px; }
.dash-bar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-bar-track { background: var(--bg); border: 1px solid var(--border); height: 14px; }
.dash-bar-fill { background: var(--navy-light); height: 100%; }
.dash-bar-fill.dash-bar-warn { background: var(--maroon); }
.dash-bar-val { text-align: right; white-space: nowrap; }
.dash-chart { width: 100%; height: auto; display: block; }
.dash-grid { stroke: var(--border); stroke-width: 1; }
.dash-axis { fill: var(--muted); font-size: 11px; font-family: 'IBM Plex Mono', monospace; }
.dash-line { fill: none; stroke: var(--navy); stroke-width: 2; }
.dash-dot { fill: var(--maroon); }
.dash-c-swa { fill: var(--doc-swa); background: var(--doc-swa); }
.dash-c-billed { fill: var(--navy); background: var(--navy); }
.dash-c-collected { fill: var(--success); background: var(--success); }
.dash-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; margin-bottom: 6px; }
.dash-legend i { display: inline-block; width: 10px; height: 10px; margin-right: 5px; }
@media (max-width: 600px) {
  /* One card per row on a phone: the full amounts (up to PHP 1,999,999,999.99) need the width, and
     were being cut off in a two-column grid. */
  .dash-kpis { grid-template-columns: 1fr; gap: 10px; }
  .dash-kpi-value { font-size: 22px; overflow-wrap: anywhere; }
  .dash-grid-2 { grid-template-columns: 1fr; }
  .dash-bar-row { grid-template-columns: 1fr auto; }
  .dash-bar-row .dash-bar-track { grid-column: 1 / -1; order: 3; }
}
@media print { .dash-kpis { grid-template-columns: repeat(4, 1fr); } .dash-grid-2 { grid-template-columns: 1fr 1fr; } .dash-kpi, .dash-grid-2 .card { break-inside: avoid; } }

/* A figure that opens the entries behind it (Monthly Comparison). */
.dash-figure-link { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
.dash-figure-link:hover { color: var(--maroon); }
/* Printing only the open Monthly Comparison popup (its Print button sets body.print-detail). */
@media print {
  body.print-detail > *:not(#monthlyDetailOverlay) { display: none !important; }
  body.print-detail #monthlyDetailOverlay { position: static !important; display: block !important; background: none !important; padding: 0 !important; }
  body.print-detail #monthlyDetailOverlay div { max-height: none !important; overflow: visible !important; }
  body.print-detail #monthlyDetailOverlay > .card { box-shadow: none !important; border: 0 !important; max-width: none !important; }
}

/* ---------- Phone layout (600px and under) ----------
   Wide data tables turn into one card per row: each cell shows its column name (the data-label
   public/js/app.js's labelTablesForPhone() copies from the header) on the left and its value
   on the right, so nothing needs sideways scrolling to read. Screen only - printing and the
   desktop layout are untouched. Tables that are not plain lists (the sticky-column breakdown
   popup, the Access Rights grid, anything print-only) are skipped by that function. */
@media (max-width: 600px) {
  .badge { font-size: 10px; padding: 5px 8px; line-height: 1.25; }
  .brand-title { display: none; }
  .content { padding: 10px; }
  h2 { font-size: 20px; }
  .form-inline { grid-template-columns: 1fr 1fr; }
  .form-inline button { justify-self: stretch; }
  .card { padding: 10px; }
  .row { gap: 6px; }

  /* The fixed-height / flipped-scrollbar wrappers only make sense for a wide grid. */
  .table-scroll { max-height: none; overflow: visible; }
  .table-scroll-top, .table-scroll-top > table { transform: none; }

  table.mcard, table.mcard tbody, table.mcard tfoot, table.mcard tr, table.mcard td { display: block; width: 100%; }
  table.mcard thead { display: none; }
  table.mcard tr {
    border: 1px solid var(--border); border-radius: 4px; background: var(--panel);
    margin: 0 0 10px; padding: 2px 0;
  }
  table.mcard td {
    display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
    text-align: right; white-space: normal; padding: 5px 10px; font-size: 13px;
    border-bottom: 1px dashed var(--border); overflow-wrap: anywhere;
  }
  table.mcard td[data-empty="1"] { display: none; }
  table.mcard td > .mcell { flex: 1 1 0; min-width: 0; text-align: right; overflow-wrap: anywhere; }
  table.mcard td:last-child { border-bottom: none; }
  table.mcard td::before {
    content: attr(data-label); flex: 0 0 34%; text-align: left; font-family: 'Work Sans', sans-serif;
    font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--navy);
  }
  table.mcard td[data-label=""]::before, table.mcard td:not([data-label])::before { display: none; }
  /* Action buttons (edit / delete / open) sit left, as a row of their own. */
  table.mcard td[data-label=""] { justify-content: flex-start; gap: 8px; }
  /* Group / subtotal / total rows (one wide cell) read as a heading, not a labelled field. */
  table.mcard td[colspan] { display: block; text-align: left; font-weight: 600; }
  table.mcard td.mono, table.mcard th.mono { text-align: right; }
  table.mcard tr.edit-row { padding: 0; }
  table.mcard tr.edit-row > td { display: block; text-align: left; padding: 10px; }
  table.mcard tr.total-row, table.mcard tr.receivable-client-header { background: var(--bg); }
  table.mcard tbody tr[style*="display: none"], table.mcard tr.hidden { display: none; }
}

/* .print-only elements (the split Collection tables built in renderBillingHistory, app.js)
   exist purely for print - the on-screen page already has the full 15-column table with its
   own horizontal scroll, so this duplicate stays hidden until @media print below swaps them. */
.print-only { display: none; }

/* print-letterhead (app.js's renderBillingHistory) - stands in for the app's own navy
   topbar, which @media print hides along with the rest of the chrome. Centered, serif
   company name over a smaller report title, with a navy double rule underneath - the same
   "printed form" language as a real Disbursement Voucher letterhead, not a generic header. */
.print-letterhead { text-align: center; padding: 10px 16px 12px; }
.print-co-name {
  font-family: 'Spectral', Georgia, serif; font-weight: 700; font-size: 20px;
  letter-spacing: .04em; color: var(--navy);
}
.print-report-title {
  font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin-top: 2px;
}
.print-footer-note { text-align: right; font-size: 11px; color: var(--muted); margin: 6px 2px 0; }

/* Print (Reports > Project Billing History's "Print" button, window.print()) - the app
   chrome (nav, topbar, footer, toasts) has no place on paper, and any element marked
   .no-print (the report tab picker, the project search/landing table, the Print/Excel
   buttons themselves) is specific to the on-screen browsing flow, not the printed report.
   Landscape + a smaller base font, since these are wide financial tables. */
@media print {
  @page { size: landscape; margin: 12mm 8mm 8mm; }
  .topbar, .sidenav, #navOverlay, #toast, .appfooter, .no-print { display: none !important; }
  .shell { display: block !important; min-height: 0 !important; }
  .content { max-width: none !important; padding: 0 !important; }
  body { font-size: 11px; background: #fff; }
  .card {
    overflow: visible !important; box-shadow: none !important; border: 1px solid #999 !important;
    page-break-inside: avoid; margin-bottom: 10px; background: #fff !important;
  }
  /* .table-scroll-top's flip trick (top scrollbar) is a screen-only convenience - a printed
     page has no scrollbar to reposition, and the .card rule right above already unclips it
     (overflow: visible) so the whole table just flows normally instead. Undoing the
     transform here is only a precaution against any print-renderer quirk with a transformed,
     unclipped element - not something observed, just cheap to rule out. */
  .table-scroll-top, .table-scroll-top > table { transform: none !important; }
  .print-letterhead { border: none !important; border-bottom: 3px double var(--navy) !important; margin-bottom: 14px; }
  /* The base .card rule above says "keep this whole block on one page" - correct for a small
     card (a tile, the letterhead), but the Reports tables reuse .card as their .table-scroll
     wrapper too, and an 81-row table obviously can't fit on one page. Chrome's answer to
     "keep it together but it doesn't fit" is to push the ENTIRE block onto the next page
     rather than let it split - which left a large blank gap at the bottom of page 1 and
     the whole table starting fresh on page 2 (reported after the earlier header-padding fix
     looked right in isolation but the actual printout still wasted most of page 1). Only the
     .table-scroll cards need to flow across pages; every other .card size is small enough
     that avoid was never doing anything harmful for them, so this is scoped narrowly rather
     than flipping the base rule for everyone. */
  .card.table-scroll { page-break-inside: auto; }
  .sort-ind { display: none !important; } /* on-screen sort arrows only */
  tr.cv-excluded { display: none !important; } /* Contract Value/Receivable: unticked rows aren't part of the report */
  /* The full 15-column Collection table (Milestone through Documents) has no room to shrink
     into - even at the smallest still-legible font/padding, 15 real financial columns ran
     well past a landscape page's printable width and got silently cut off mid-number (paper
     has no horizontal scroll - anything past the page edge just disappears). Printing the
     split-in-two version instead (app.js's print-only tables, ~8 columns each, Milestone
     repeated as the join key) keeps every figure fully visible at a readable size, with
     headroom left over to size the font up from the split table's own minimum. */
  .screen-collection-table { display: none !important; }
  .print-only { display: block; }
  /* The base th/td rule (style.css above) uses 8px/10px padding sized for on-screen
     browsing, too roomy for a printed financial table - tightening both brings even the
     narrower print-only tables comfortably under a landscape Letter/A4 page's width. Text
     columns switch to the app's own UI (sans) face instead of the screen table's monospace -
     .mono keeps its own font regardless (a class selector outranks this element selector),
     so money stays perfectly column-aligned while everything else reads like a normal
     printed report instead of a code listing. */
  table { font-size: 10px; width: 100%; font-family: 'Work Sans', -apple-system, "Segoe UI", Roboto, Arial, sans-serif; }
  th, td { padding: 3px 5px; }
  th { border-bottom: 1.5px solid var(--navy); }
  /* A free-text Remarks note (e.g. "no royalty deducted. Other deduction - Liq. Damages...")
     left on one unbreakable line (the base th/td rule's own white-space:nowrap) was blowing
     the Collection print table's width well past a landscape page's printable area all by
     itself - this table's own .wrap column wraps normally instead, capped narrow enough that
     even a long note stays a few short lines instead of one very wide one. */
  .print-only td.wrap { white-space: normal; max-width: 150px; }
  /* Receivable's on-screen table used to print as itself (no print-only duplicate) - its own
     13 columns (SWA Date/Amount and Billed Date/Amount as separate pairs, plus two duration
     columns) ran off the edge of a landscape page even with .table-scroll's own print rules
     below. It now has its own condensed print-only table instead (app.js's renderBillingMonitoring,
     receivable-print-wrap/#receivablePrintTable) - collapses SWA/Billed into one Date/Amount
     pair with a Source column saying which, sorted by client. .table-scroll's rules below
     still apply to Contract Value/Retention Receivable/Mobilization Recoupment, which DO still
     print as themselves. */
  .table-scroll { max-height: none !important; overflow: visible !important; }
  /* .receivable-print-table - fixed, narrow per-column widths (the <colgroup> in app.js)
     instead of the browser's own content-driven auto layout, so Status/Remarks - the actual
     point of this printout, per the user - gets the widest share instead of the browser
     guessing. Every column wraps (word-break too, for a long unbroken project/milestone name)
     since table-layout: fixed never shrinks a column to fit its content the way auto layout
     does. Shared by both the main (Total Receivable) print table and the separate "Non-current
     Receivables" one further down (app.js) - same column shape either way. */
  .receivable-print-wrap { page-break-inside: auto; }
  .receivable-print-table { table-layout: fixed; }
  .receivable-print-table th, .receivable-print-table td { white-space: normal; word-break: break-word; }
  /* Overrides .print-only td.wrap's own max-width: 150px above - that rule assumes an
     auto-layout table sizing columns by content, where capping one column's content width is
     how you keep it from blowing the whole table out. This table is table-layout: fixed
     instead (its width comes from the <colgroup> alone), so the same 150px cap would just
     needlessly shrink the Status/Remarks column back down below its actual, wider share. */
  .receivable-print-table td.wrap { max-width: none; }
  /* The client-name header row (app.js's receivableGroupedBodyHtml) that now stands in for a
     repeated-per-row Client column - per the user, printing the same client name on every one
     of its rows wasted space and wrapped awkwardly for a long name. Styled as its own small
     heading (bold, shaded, a rule above to separate it from the previous client's Subtotal
     row) rather than looking like just another data row. */
  .receivable-client-header td {
    font-weight: 700; background: #F1EEE3; border-top: 1.5px solid var(--navy);
    border-bottom: 1px solid var(--border); padding-top: 8px;
  }
  /* !important on the padding too - .table-scroll th/td's own on-screen rule (style.css,
     outside this @media block) sets padding as a class selector, which beats a bare "thead
     th" element selector's specificity regardless of source order, so without !important
     here the 4 .table-scroll-based reports (Receivable, Contract Value, Retention
     Receivable, Mobilization Recoupment) would silently keep the tighter on-screen padding
     in print while Project Billing History and Monthly Comparison (plain tables, no
     .table-scroll) correctly picked up the roomier print padding below - caught by manually
     diffing computed styles against the fetched CSS after the fix looked "applied" but a
     live test still showed the old 6px. */
  .table-scroll thead th {
    position: static !important; box-shadow: none !important;
    padding-top: 7px !important; padding-bottom: 7px !important;
  }
  /* Scoped to .table-scroll specifically (not a bare td.wrap) - Ledger and Activity Log have
     their own .wrap Remarks/Message columns with their own wider inline min-width (180px/
     280px, sized for on-screen browsing), and neither has a print button of its own, but
     both are still reachable via the browser's native print. A bare td.wrap rule here would
     silently squeeze those too the next time either page gets printed, which was never the
     intent - this was only ever meant for the Reports tables built with .table-scroll.
     !important on min-width too, since the Monitoring table's own inline min-width:180px
     would otherwise win over this max-width and cancel the cap out (when min-width exceeds
     max-width, min-width wins per the CSS spec). */
  .table-scroll td.wrap { max-width: 150px; min-width: 0 !important; }
  /* A faint zebra stripe reads far easier across a wide row of numbers than an unbroken
     block of white - standard for a printed financial table, and cheap enough on toner that
     it doesn't read as wasteful. */
  tbody tr:nth-child(even) { background: #F1EEE3; }
  tr { page-break-inside: avoid; }
  /* thead's own page-header repeat (display:table-header-group, below) sits flush against
     the page's top margin with no breathing room on every page after the first - fine on
     page 1, where the letterhead already provides space above it, but cramped/"jammed at
     the top" on every later page. The bottom border alone read as too tight against the
     first data row too. A little padding on the repeating header cells fixes both without
     touching the letterhead or non-repeating pages. */
  thead th { padding-top: 7px; padding-bottom: 7px; }
  thead { display: table-header-group; page-break-after: avoid; } /* repeats column headers on every printed page */
  tfoot { display: table-row-group; } /* keeps the Total row from being orphaned onto its own page */
  .total-row { font-weight: 700; border-top: 2px solid var(--navy); background: #E9E4D2 !important; }
  .total-row td { border-bottom: none; }
}
