:root {
  --navy: #1e3a8a;
  --navy-light: #1e40af;
  --bg: #f5f6f8;
  --card-bg: #ffffff;
  --border: #dfe3e8;
  --text: #1c1f24;
  --text-muted: #667085;
  --danger: #b3261e;
  --radius: 8px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
}

.app { max-width: 1180px; margin: 0 auto; padding: 24px 20px 64px; }

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 16px;
}
.app-header-brand { display: flex; flex-direction: column; gap: 4px; }
.app-header .company-logo { height: 52px; width: auto; max-width: 240px; object-fit: contain; border-radius: 6px; }
.app-header .company-logo.admin-editable { cursor: pointer; }
.app-header .company-logo.admin-editable:hover { opacity: 0.85; }

/* ---------- Optek's own app logo -- fixed, code-drawn, not admin-editable
   (unlike the company logo just below it, which is). Shown above the
   company logo everywhere the company logo appears (login screen, signed-in
   header) -- see "Company logo" in README.md for why the two coexist. */
.app-logo { display: flex; align-items: center; gap: 3px; }
.app-logo-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: linear-gradient(135deg, var(--navy), var(--navy-light));
  color: #fff;
  font-weight: 800;
  font-size: 16px;
  line-height: 1;
}
.app-logo-text { font-weight: 800; font-size: 20px; color: var(--navy); letter-spacing: -0.01em; }
.app-logo-sm { gap: 2px; }
.app-logo-sm .app-logo-badge { width: 17px; height: 17px; border-radius: 5px; font-size: 11px; }
.app-logo-sm .app-logo-text { font-size: 13px; }

.app-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 24px;
}
.app-nav .nav-link {
  position: relative; /* anchors a .nav-alert-badge corner, e.g. Bidding's due-soon count */
  display: inline-block;
  padding: 10px 14px;
  font-size: 14px;
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.app-nav .nav-link:hover { color: var(--navy); }
.app-nav .nav-link.active {
  color: var(--navy);
  font-weight: 600;
  border-bottom-color: var(--navy);
}

/* ---------- in-page sub-navigation (Finance: General ledger accounts / Equipment) ---------- */
.subnav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 20px;
}
.subnav-link {
  position: relative;
  display: inline-block;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: none;
  background: #eef1f5;
  border-radius: 999px;
  white-space: nowrap;
}
.subnav-link:hover { color: var(--navy); }
.subnav-link.active { color: #fff; background: var(--navy); }

/* Small red alert-count circle in a nav tab's upper-right corner -- e.g.
   Project Management's "Cost Approvals" subnav tab (combined total of
   unapproved time entries + expense costs waiting on this PM), or the main
   nav's "Bidding" tab (count of still-open bids due within 48 hours -- see
   refreshBiddingDueBadge() in public/bidding.js). Hidden entirely (via the
   shared .hidden class) rather than shown as "0" when there's nothing
   pending. Works on either .subnav-link or .nav-link, both of which set
   position: relative for it to anchor against. */
.nav-alert-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: #c0392b;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

.page { display: none; }
.page.is-active { display: block; }

/* Page-independent modal — currently just the vendor editor (see the
   comment above #vendor-editor-overlay in index.html). Deliberately sits
   outside every .page div so it can be opened from more than one page;
   .hidden (display:none !important) beats the display:flex below whenever
   it's added, so no separate .modal-overlay.hidden rule is needed. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  overflow-y: auto;
  z-index: 1000;
}
.modal-card { width: 100%; max-width: 900px; margin: 0; }

/* Read-only key/value display -- currently just the Vendor register's
   vendor-information card (the master record fields, above the Edit
   button and the transaction report). Generic enough to reuse for a
   similar "here's the master record" card elsewhere later. */
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px 24px; margin-top: 12px; }
.detail-item-wide { grid-column: 1 / -1; }
.detail-label { font-size: 12px; color: var(--text-muted); margin-bottom: 3px; }
.detail-value { font-size: 14px; color: var(--text); word-break: break-word; }
.who { display: flex; align-items: center; gap: 10px; }
.who label { font-size: 13px; color: var(--text-muted); }
.whoami { font-size: 14px; color: var(--text); }
.whoami .role-badge {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--navy);
  background: #eef1f5;
  border-radius: 999px;
  padding: 2px 8px;
  margin-left: 6px;
}

/* ---------- login screen ---------- */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  padding: 20px;
}
.login-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 32px;
  width: 100%;
  max-width: 360px;
}
.login-card .app-logo { margin: 0 0 14px; }
.login-card .app-logo-badge { width: 32px; height: 32px; font-size: 19px; border-radius: 8px; }
.login-card .app-logo-text { font-size: 24px; }
.login-card .company-logo { display: block; height: 72px; border-radius: 8px; width: auto; max-width: 100%; object-fit: contain; margin: 0 0 16px; }
.login-card .muted { color: var(--text-muted); font-size: 13px; margin: 0 0 20px; }
.login-card .muted.small { margin: 16px 0 0; font-size: 12px; }
.login-card .field { margin-bottom: 14px; }
.login-card .actions { justify-content: space-between; }
.login-card button.btn-primary { width: 100%; }

/* ---------- modal ---------- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 24, 30, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
}
/* Scoped to .modal-backdrop specifically -- this is the small centered
   password-change dialog's own card. The page-independent .modal-overlay
   pattern above (vendor editor, task popup) also uses the bare .modal-card
   class at 900px; without this scope, this rule's later position in the
   stylesheet would win at equal specificity and clamp every modal on the
   site to 340px regardless of which pattern it's using. */
.modal-backdrop .modal-card {
  background: var(--card-bg);
  border-radius: 12px;
  padding: 24px;
  width: 100%;
  max-width: 340px;
}
.modal-card h2 { margin: 0 0 14px; font-size: 16px; color: var(--navy); }

#pu-preview { display: block; width: 100%; max-height: 200px; object-fit: contain; border-radius: 8px; background: var(--bg); margin: 0 0 14px; }
#pv-image { display: block; max-width: 100%; max-height: 65vh; margin: 0 auto 12px; border-radius: 8px; }

.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px 24px;
  margin-bottom: 20px;
}
.card h2 { font-size: 16px; margin: 0 0 14px; color: var(--navy); }

/* Splits one card into two visual sections (e.g. a workflow stepper on top,
   its tasks below) without the gap/border of a second stacked .card. */
.section-divider { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border); }
.section-divider h2:first-child { margin-top: 0; }

/* ---------- stat tiles (Management > Dashboard: Profit & Loss summary) ---------- */
.stat-grid { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 4px; }
.stat-tile {
  flex: 1 1 180px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
}
.stat-label { font-size: 13px; color: var(--text-muted); margin-bottom: 6px; }
.stat-value { font-size: 26px; font-weight: 600; color: var(--navy); }
.stat-value.positive { color: #1a7f37; }
.stat-value.negative { color: var(--danger); }

.row { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
.field { flex: 1; min-width: 160px; display: flex; flex-direction: column; gap: 4px; }
.field.small { flex: 0 0 120px; }
.field.search-field { max-width: 320px; margin-bottom: 12px; }

/* Log time card's Start/End/Break inputs, once Hours has been typed over
   directly instead of derived from them -- see wireLogTimeAutoCalc() in
   app.js. Deliberately just a text color change, not `:disabled` (which
   this app already styles distinctly, see .onboarding-form input:disabled
   above) -- these three stay fully editable; editing any one of them is
   itself what switches back to calculated mode and clears this class. */
.log-time-input-muted { color: var(--text-muted); }
.field label { font-size: 12px; color: var(--text-muted); }
/* A read-only figure inside a .field, same box shape as its input siblings
   (e.g. the Bid Letter tab's LER, shown next to its editable Gross Margin/
   Bid Amount fields) so they all line up in a row. */
.field .static-value {
  margin: 0;
  padding: 9px 10px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

/* A compact, wrapping row of "<label><input type=checkbox> Name</label>"
   pairs -- Cost Types assigned to a Standard Cost Code (in its table row
   and in the "Add a standard cost code" form), where .detail-grid's
   200px-minimum columns are too wide for what's usually just 2-3 short
   names. */
.cost-type-checkboxes { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.cost-type-checkboxes label { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; white-space: nowrap; }
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 14px;
  width: 100%;
  background: #fff;
  color: var(--text);
  font-family: inherit;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--navy-light); outline-offset: 1px; }

/* ---------- Searchable comboboxes (Log time's Project field, Costs' Vendor field, the task popup's To-Do's picker) ---------- */
.project-combobox, .vendor-combobox, .task-name-combobox { position: relative; }
.combobox-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  margin-top: 2px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  max-height: 240px;
  overflow-y: auto;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.combobox-result { padding: 8px 10px; font-size: 14px; cursor: pointer; }
.combobox-result:hover, .combobox-result.active { background: #f3f4f6; }
.combobox-empty { padding: 8px 10px; font-size: 13px; color: var(--text-muted); }
/* Checkboxes used inline in a label (e.g. "Active", "Subcontractor" on the
   Vendors/Customers editors) -- excluded from the width:100%/border/padding
   rule above (that's for text-like inputs), sized normally instead. */
input[type="checkbox"], input[type="radio"] { width: auto; margin-right: 4px; }
/* Section headers inside a longer editor card (e.g. Vendors/Customers'
   Payment address / Additional addresses / Notes groupings) -- smaller and
   quieter than the card's own <h2> so the hierarchy stays obvious. */
.card h3 { font-size: 14px; margin: 20px 0 8px; color: var(--text-muted); }

.actions { display: flex; align-items: center; gap: 12px; margin-top: 8px; }
.btn-primary, .btn-secondary, .btn-danger {
  border: none;
  border-radius: var(--radius);
  padding: 9px 16px;
  font-size: 14px;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
.btn-primary { background: var(--navy); color: #fff; }
.btn-primary:hover { background: var(--navy-light); }
.btn-secondary { background: #eef1f5; color: var(--navy); border: 1px solid var(--border); }
.btn-secondary:hover { background: #e2e7ee; }
/* Reserved for a genuinely destructive, permanent action (e.g. completely
   deleting a journal entry) -- distinct from .btn-secondary so it doesn't
   look like an equally-safe alternative next to Void/Cancel. */
.btn-danger { background: #fdeceb; color: var(--danger); border: 1px solid #f3c8c4; }
.btn-danger:hover { background: #f9d9d6; }

.message { font-size: 13px; color: var(--text-muted); }
.message.error { color: var(--danger); }
.message.success { color: #1a7f37; }

.card-header-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; }
/* Small one-off spacing utilities — kept as classes rather than inline
   `style="..."` attributes so the app's Content-Security-Policy can forbid
   inline styles outright (see server.js) without losing this spacing. */
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; min-width: 0; max-width: 100%; }
.filters select, .filters input { width: auto; max-width: 100%; min-width: 0; }
/* Projects list search box (see #project-list-search / renderProjectListTable
   in projects.js) -- a plain text input would otherwise shrink to near-zero
   under the .filters rule above, since it has no intrinsic content width. */
#project-list-search { min-width: 240px; }
.filters label { font-size: 13px; display: flex; align-items: center; gap: 4px; }
.filters-label { font-size: 13px; font-weight: 600; color: var(--text-muted); }
/* Toggle buttons (WIP report's Departments filter) reuse .subnav-link's pill
   look (see "in-page sub-navigation" above) so a selected department reads
   the same way an active subnav tab does, but as a real <button> since more
   than one can be selected at once rather than exactly one. */
button.subnav-link {
  border: none;
  cursor: pointer;
  font: inherit;
}

table { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 13.5px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
th { color: var(--text-muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; }
tbody tr:hover { background: #fafbfc; }
td.num { text-align: right; }
/* Every table on this app is wrapped in a `.table-scroll` div (see
   index.html) so a wide table (Cost approvals, GL accounts, and so on, most
   with 6+ columns) scrolls sideways in its own contained box on a narrow
   screen, instead of the table forcing the ENTIRE page to scroll
   horizontally along with the header and nav. `table` keeps width: 100%
   above so it still fills the card at desktop widths where everything
   fits; this only kicks in once the table's natural content width exceeds
   the container. -webkit-overflow-scrolling makes that scroll momentum-based
   on iOS instead of the stiff, step-by-step default. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* The Employees record's widest repeatable grids (Dedns/Liabs has ~44
   columns, Employee Leave ~32) -- built entirely from JS column defs (see
   employees.js), so cells need a sane fixed width instead of the usual
   input{width:100%}, or every column would stretch to fill whatever space
   is left and the table would become unreadable rather than just wide. */
.wide-grid-wrap table input,
.wide-grid-wrap table select { width: 90px; min-width: 70px; }
.wide-grid-wrap table input[type="checkbox"] { width: auto; min-width: 0; }
.wide-grid-wrap th, .wide-grid-wrap td { white-space: nowrap; padding: 4px 6px; }
.wide-grid-wrap td:last-child, .wide-grid-wrap th:last-child { white-space: normal; }
/* Project/GL/etc codes are short but were wrapping mid-code (e.g. "24-" /
   "007") once other columns started competing for space — codes read as a
   single token, so never let them break across lines. */
td.code-cell { white-space: nowrap; }
button.link-btn { background: none; border: none; color: var(--danger); cursor: pointer; font-size: 13px; padding: 0; }

/* ---------- "record" links (project/vendor/GL account code+name cells that
   navigate elsewhere in the app -- Projects, WIP report, Vendors, Open AP,
   GL accounts) ---------- */
/* A plain <a href="#/..."> here is a real link the browser tracks as
   visited, so left unstyled it comes out as the ordinary blue/underlined
   link that turns purple once clicked -- not the look we want for what's
   really an in-app navigation action. Styled instead as a small button: a
   pill, no underline, no color change once visited, a hover state matching
   the app's other pill controls (.subnav-link/.dept-toggle below). */
a.record-link,
a.record-link:visited {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 6px;
  background: #eef1f5;
  color: var(--navy);
  text-decoration: none;
  font-weight: 600;
  font-size: 13px;
  line-height: 1.3;
}
a.record-link:hover {
  background: var(--navy);
  color: #fff;
}

/* ---------- click-to-edit table cells (Manage projects: Name/Client) ---------- */
.editable-cell {
  cursor: pointer;
  border-bottom: 1px dashed transparent;
}
.editable-cell:hover {
  background: #f3f6fb;
  border-bottom-color: var(--navy-light);
}
.editable-cell-input {
  width: 100%;
  font: inherit;
  padding: 3px 5px;
  border: 1px solid var(--navy-light);
  border-radius: 4px;
  background: #fff;
}

/* ---------- Work In Progress report (Project Management > WIP) ---------- */
#wip-table tr.wip-department-row td {
  background: #eef1f6;
  font-weight: 700;
  color: var(--navy);
  border-bottom: 1px solid var(--border);
}
#wip-table tr.wip-subtotal-row td {
  font-weight: 600;
  border-top: 1px solid var(--border);
  border-bottom: 2px solid var(--border);
}
#wip-table tr.wip-grand-total-row td {
  font-weight: 700;
  background: #eef1f6;
  border-top: 2px solid var(--navy);
}
/* Flags a project with no cost budget estimated yet on Cost Budgets --
   without one, % Comp/Earned To Date/Billings Over-Under for that row are
   all meaningless zeros rather than a real 0% complete, so this is worth
   catching at a glance rather than reading as "just started." */
#wip-table td.wip-zero-estimate {
  color: var(--danger);
  background: #fbeae9;
  font-weight: 600;
}

/* ---------- Generic cost-type-grouped report rows -- the Project cost
   report and the Vendor register's Transactions report both group their
   rows by Cost Type with a subtotal per group and a grand total at the
   end (see costTypeGroupedTableHtml in public/expenses.js), same visual
   language as the WIP report's department groups above, just under
   report-scoped class names since more than one table uses these. ---------- */
tr.report-group-row td {
  background: #eef1f6;
  font-weight: 700;
  color: var(--navy);
  border-bottom: 1px solid var(--border);
}
tr.report-subtotal-row td {
  font-weight: 600;
  border-top: 1px solid var(--border);
  border-bottom: 2px solid var(--border);
}
tr.report-grand-total-row td {
  font-weight: 700;
  background: #eef1f6;
  border-top: 2px solid var(--navy);
}

/* A cost's dollar amount, styled and behaving like a link, that opens the
   shared "Edit cost" modal (#glr-review-overlay) for that specific row --
   used on the Project cost report and Vendor register Transactions report,
   in place of a separate "Edit" column, so re-coding a cost (cost type,
   project, cost code, GL account, or amount) is one click from the number
   itself. Navy rather than .link-btn's default danger-red, since this is a
   non-destructive inline action (same convention as .pd-edit-link above). */
button.amount-edit-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: inherit;
  color: var(--navy);
  cursor: pointer;
  border-bottom: 1px dashed var(--navy-light);
}
button.amount-edit-link:hover {
  border-bottom-style: solid;
}

/* ---------- "Permanently delete" danger zone (the edit-cost modal,
   #glr-review) — visually set apart from the ordinary Save/Close actions
   above it, so a destructive, no-audit-trail action never reads as just
   another button in the same row. ---------- */
.danger-zone {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px dashed #f3c8c4;
}
.danger-zone-confirm {
  margin-top: 18px;
  padding: 12px 14px;
  border: 1px solid #f3c8c4;
  border-radius: 8px;
  background: #fdeceb;
}
.danger-zone-confirm .message {
  color: var(--danger);
  margin: 0 0 10px;
}
.danger-zone-confirm .actions { margin-top: 10px; }
/* Solid dark divider after "To Date Billings" -- the last of the "input"
   columns (Contract Amount through To Date Billings, one hand-entered),
   separating them from "Billings Over"/"Billings Under", the two computed
   flag columns that follow. Deliberately heavier/darker than the thin gray
   row borders elsewhere in the table, since it's marking a column boundary
   rather than a row boundary. */
#wip-table th.wip-billings-boundary,
#wip-table td.wip-billings-boundary {
  border-right: 2px solid #000;
}
/* Billings Under highlights red once it's a serious shortfall -- more than
   half the contract amount, or more than $10,000, whichever check catches
   it first (a small contract can trip the dollar threshold well under 50%,
   and a large one can pass $10k long before it's really concerning, so
   either condition alone is enough to flag it). */
#wip-table td.wip-under-flag {
  color: var(--danger);
  background: #fbeae9;
  font-weight: 600;
}
/* Billings Over highlights yellow once it's more than half the contract
   amount -- billed well ahead of what's actually been earned so far. */
#wip-table td.wip-over-flag {
  color: #7a5c00;
  background: #fff3cd;
  font-weight: 600;
}

.empty { color: var(--text-muted); font-size: 14px; margin-top: 12px; }
.muted-note { color: var(--text-muted); font-size: 13px; margin: 0 0 12px; }
.muted-note strong { color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.hidden { display: none !important; }

details { margin-top: 10px; }
summary { cursor: pointer; font-size: 14px; color: var(--navy); margin-bottom: 10px; }
details form { margin-top: 12px; }

/* ---------- onboarding ---------- */
.role-badge.hr-badge { background: #fdecc8; color: #7a5200; margin-left: 4px; }
.role-badge.pm-badge { background: #dceefb; color: #0b5a86; margin-left: 4px; }
.role-badge.foreman-badge { background: #dcf3e3; color: #12693b; margin-left: 4px; }

/* ---------- status badges (Costs: pending approval / approved) ---------- */
.status-badge {
  display: inline-block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: 999px;
  padding: 2px 8px;
}
.status-badge.status-pending { background: #fdecc8; color: #7a5200; }
.status-badge.status-approved { background: #dcf3e3; color: #12693b; }
/* ---------- bid status (Project Management > Bidding) ---------- */
.status-badge.status-draft { background: #eef1f5; color: var(--navy); }
.status-badge.status-submitted { background: #dceefb; color: #0b5a86; }
.status-badge.status-won { background: #dcf3e3; color: #12693b; }
.status-badge.status-lost { background: #fbdede; color: #8a1f1f; }
/* ---------- vendor-not-on-file flag (Cost approvals) ---------- */
.status-badge.status-flagged { background: #fbe4d5; color: #8a4a12; margin-left: 6px; }
/* ---------- voided journal entry (Finance > Journal entries) ---------- */
.status-badge.status-voided { background: #eef1f5; color: var(--text-muted); margin-left: 6px; }

/* ---------- attachment icon (Costs / Cost approvals / GL register rows) ---------- */
.attachment-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 6px;
  color: var(--navy);
  text-decoration: none;
}
.attachment-icon:hover { background: #eef1f6; }
.attachment-icon svg { width: 16px; height: 16px; }

.checklist { list-style: none; margin: 0; padding: 0; }
.checklist-item { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 14px; color: var(--text-muted); border-bottom: 1px solid var(--border); }
.checklist-item:last-child { border-bottom: none; }
.checklist-item.done { color: var(--text); }
.checklist-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--border); font-size: 12px; }
.checklist-item.done .checklist-icon { background: #1a7f37; color: #fff; border-color: #1a7f37; }

.onboarding-form { max-width: 640px; }
.reveal-row { margin-top: 2px; }
.reveal-row .link-btn { color: var(--navy); font-size: 12px; }

/* Project homepage's inline "Edit"/"Add address" affordances (see pd-address
   in projects.js) — reuse .link-btn's button reset but in the neutral navy
   used for non-destructive inline actions elsewhere, not .link-btn's default
   danger-red (meant for Delete buttons). */
.pd-edit-link { color: var(--navy); font-size: 12px; margin-left: 6px; }

/* Bidding list's "Open" button (see loadBidList in public/bidding.js) --
   same reasoning as .pd-edit-link above: opening a bid isn't a destructive
   action, so it overrides .link-btn's default danger-red to the neutral
   navy used elsewhere for non-destructive inline actions. Two classes
   (rather than a single ".open-bid" selector) so this actually outweighs
   "button.link-btn" in specificity -- a single-class override loses that
   fight and stays red regardless of what color it names. */
.link-btn.open-bid { color: var(--navy); }

/* BOM "Needs attention" panel's paperclip view link (see bomLineRowHtml in
   public/submittals.js) -- viewing a suggested document is just as
   non-destructive as opening a bid above, so it gets the same neutral-navy
   override instead of .link-btn's default danger-red. */
.link-btn.bom-line-view { color: var(--navy); }

/* The BOM attachment file input on a 'not_found' line -- kept compact so it
   sits comfortably inline with the row's own Dismiss button rather than
   stretching to the row's full width like an ordinary form field. */
.bom-line-attach-input { display: inline-block; width: auto; max-width: 200px; margin-right: 8px; }
.pd-pm-select { display: inline-block; width: auto; margin-left: 6px; }
.onboarding-form input:disabled,
.onboarding-form select:disabled,
.onboarding-form textarea:disabled {
  background: #f3f4f6;
  color: var(--text);
  opacity: 1;
  cursor: default;
}

/* ---------- Onboarding — locked section + "request a change" ---------- */
.request-change-block { max-width: 640px; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
.request-change-block .locked-note { margin-bottom: 8px; }
.request-change-form { margin-top: 8px; }
.request-change-pending { margin: 0; font-style: italic; }

.disclaimer {
  background: #fff7e6;
  border: 1px solid #f0d385;
  border-radius: var(--radius);
  padding: 10px 14px;
  font-size: 13px;
  color: #6b4d00;
  margin: 0 0 14px;
}
.disclaimer a { color: #6b4d00; }

.dropzone {
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  padding: 24px;
  text-align: center;
  margin: 12px 0;
  transition: border-color 0.15s, background 0.15s;
}
.dropzone.dragover { border-color: var(--navy-light); background: #eef3fb; }
.dropzone p { margin: 0 0 12px; color: var(--text-muted); font-size: 14px; }
.dropzone-actions { display: flex; justify-content: center; gap: 10px; }

#hr-detail-body h3 { font-size: 14px; color: var(--navy); margin: 18px 0 8px; }
#hr-detail-body h3:first-child { margin-top: 0; }
#hr-detail-body .row { margin-bottom: 6px; }
#hr-detail-body .field label { margin-bottom: 0; }

/* ---------- my schedule calendar (Home) ----------
   A continuously-scrollable list of Sun-Sat weeks (see createWeekCalendar()
   in public/app.js), not a paged month grid: .schedule-calendar is a
   fixed-height, natively-scrollable viewport so an ordinary mouse-wheel/
   trackpad scroll over it moves seamlessly from week to week and across
   month boundaries -- no custom wheel handling, the browser's own scrolling
   does it. .calendar-weekday-row and .calendar-scroll-body are both direct
   children of that scrolling element (required for position:sticky to
   pin the weekday header against ITS scrolling, not the page's). */
.schedule-calendar {
  margin-top: 10px;
  height: 420px;
  overflow-y: auto;
  overflow-x: hidden;
  position: relative;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.calendar-weekday-row {
  position: sticky;
  top: 0;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
  background: #fff;
  border-bottom: 1px solid var(--border);
}
.calendar-scroll-body { padding: 5px; }
.calendar-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; margin-bottom: 5px; }
.calendar-head {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  text-align: center;
  padding: 4px 0 6px;
}
.calendar-cell {
  min-height: 76px;
  max-height: 150px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px;
  background: #fff;
}
/* The week containing today, highlighted so "defaults to the current week
   in the middle" (see boot()'s call into createWeekCalendar()) is
   obviously the current week once it's centered, not just coincidentally
   in the middle of the viewport. */
.calendar-cell.today { border-color: var(--navy); box-shadow: inset 0 0 0 1px var(--navy); }
.calendar-cell.today .cal-daynum { color: var(--navy); font-weight: 700; }
/* A visible demarcation line between months -- the 1st-of-the-month cell
   (see buildCalendarWeekHtml()/buildCalendarDayRowHtml() in app.js) gets a
   heavier border in a color distinct from .today's navy, so the two
   highlights (which occasionally coincide) don't read as the same thing.
   Top+left covers both cases in the Month/Week grid: the 1st landing at the
   start of a row (top border is the divider seen while scrolling) or
   mid-row (left border separates it from the prior day in the same row). */
.calendar-cell.month-start { border-top: 3px solid var(--text-muted); border-left: 3px solid var(--text-muted); }
.cal-daynum { font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
.cal-entry {
  background: #eef3fb;
  color: var(--navy);
  border-radius: 4px;
  padding: 2px 5px;
  margin-bottom: 3px;
  font-size: 11px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- PM schedule calendar (Schedule tab) ---------- */
.schedule-toggle-row { margin-bottom: 8px; }
/* Today button, sitting in its own row directly above the calendar --
   .card-header-row's default space-between would just left-align a lone
   child, so this overrides to push it to the far right instead. */
.schedule-today-row { justify-content: flex-end; }
.schedule-toggle-hint { font-size: 12px; color: var(--text-muted); }
.schedule-toggle-actions { display: inline-flex; gap: 10px; }
.schedule-employee-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  align-items: center;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.employee-toggle-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--text);
  cursor: pointer;
  user-select: none;
}
.employee-toggle-chip input[type="checkbox"] { margin: 0; }
.employee-toggle-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  display: inline-block;
  flex: none;
}
.schedule-calendar-pm { height: 520px; }
.schedule-calendar-pm .calendar-cell {
  min-height: 92px;
  max-height: 170px;
  overflow-y: auto;
}
.cal-entry-pm {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  border-left: 3px solid transparent;
  padding-left: 4px;
}
.cal-entry-pm .cal-entry-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Click-to-open affordance for the task popup (public/projects.js) — an
   entry or an open day cell, whenever the viewer isn't a read-only "user"-
   role account (see canManageSchedule() in public/planning.js). */
.cal-entry-pm.clickable { cursor: pointer; }
.cal-entry-pm.clickable:hover { background: #e3ebf8; }
.calendar-cell.clickable { cursor: pointer; }
.calendar-cell.clickable:hover { background: #f7f9fc; }

/* ---------- Schedule view-mode selector (Month/Week/Day) ---------- */
.schedule-view-mode-toggle { display: inline-flex; gap: 4px; }
.schedule-view-mode-btn { border: none; cursor: pointer; font: inherit; }

/* ---------- Schedule Week view ----------
   Same underlying engine/markup as Month (a stack of 7-day .calendar-week
   rows -- see createWeekCalendar() in app.js), just constrained to show
   roughly one week at a time instead of several: a shorter fixed viewport
   height plus more headroom per cell (since there's no competing row below
   it fighting for space), so "Week" reads as its own focused view even
   though scrolling still moves the exact same way, one week-row at a time,
   as Month does. */
.schedule-calendar.mode-week { height: 265px; }
.schedule-calendar.mode-week .calendar-cell { min-height: 160px; max-height: 220px; }

/* ---------- Schedule Day view ----------
   A second, separate calendar instance (createWeekCalendar({unit:"day"}) --
   see initScheduleCalendarDay() in planning.js) with its own viewport, one
   full-width day per row instead of Month/Week's 7-day grid. No Sun..Sat
   header row (a single day doesn't need one -- see the blank weekdayRow for
   unit:"day" in app.js); the day's own spelled-out "Wednesday, Sep 24"
   label (built into buildCalendarDayRowHtml) carries that context instead.
   Cells get much more room than Month/Week's, so a full day's assignments
   read as "all the scheduled tasks" per Dan's request rather than a
   truncated preview -- a day busier than that still scrolls within its own
   cell exactly like Month/Week cells already do (see .calendar-cell
   above), and continuing to scroll past that inner limit hands off to the
   viewport's own scroll, advancing to the next day. */
.schedule-calendar-day .calendar-weekday-row { display: none; }
.schedule-calendar-day .calendar-week.calendar-row-day { grid-template-columns: 1fr; gap: 0; }
.schedule-calendar-day .calendar-cell-day {
  min-height: 320px;
  max-height: 460px;
  padding: 10px 12px;
}
.schedule-calendar-day .calendar-cell-day .cal-daynum {
  font-size: 14px;
  font-weight: 700;
  color: var(--navy);
  margin-bottom: 8px;
}
.schedule-calendar-day .calendar-cell-day .cal-entry-pm { padding: 4px 6px; margin-bottom: 4px; }
.schedule-calendar-day .calendar-cell-day .cal-entry-text { white-space: normal; }
/* Day view's rows only ever stack vertically (nothing beside them), so the
   month-start demarcation (see .calendar-cell.month-start above) only needs
   its top edge -- overrides that rule's left border back to the ordinary
   cell border so a full-height colored stripe doesn't run down the side of
   an otherwise plain single-column row. */
.schedule-calendar-day .calendar-cell-day.month-start { border-top: 3px solid var(--text-muted); border-left: 1px solid var(--border); }

/* ---------- project stage badges (Projects list) ---------- */
.stage-badge {
  display: inline-block;
  padding: 3px 11px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  /* Fallback for a custom/renamed workflow step that has no dedicated
     color rule below — still readable, just neutral. */
  background: #eef1f5;
  color: #51586b;
}
.stage-planning { background: #eef1f5; color: #51586b; }
.stage-permitting { background: #fdecc8; color: #7a5200; }
.stage-procurement { background: #dceefb; color: #0b5a86; }
.stage-construction { background: #dbe7ff; color: #1f3864; }
.stage-punch-list { background: #f0e3fb; color: #5b2a86; }
.stage-closeout { background: #d9f2ea; color: #0d6b4f; }
.stage-complete { background: #d9f5db; color: #1a7f37; }

/* ---------- project detail — workflow stepper ---------- */
.workflow-stepper { display: flex; margin: 28px 0 8px; }
.workflow-step { flex: 1; text-align: center; position: relative; }
.workflow-step.clickable { cursor: pointer; }
.workflow-step .dot {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 8px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-muted);
  position: relative;
  z-index: 1;
}
.workflow-step .label { font-size: 12px; color: var(--text-muted); padding: 0 4px; }
.workflow-step::before {
  content: "";
  position: absolute;
  top: 14px;
  left: -50%;
  width: 100%;
  height: 2px;
  background: var(--border);
  z-index: 0;
}
.workflow-step:first-child::before { display: none; }
.workflow-step.reached::before { background: #1a7f37; }
.workflow-step.done .dot { background: #1a7f37; border-color: #1a7f37; color: #fff; }
.workflow-step.current .dot { background: var(--navy); border-color: var(--navy); color: #fff; }
.workflow-step.current .label { color: var(--navy); font-weight: 600; }
.workflow-step.clickable:hover .dot { border-color: var(--navy-light); }

/* ---------- Project Admin — per-project workflow step checklist ---------- */
.checklist-inline { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 4px; }
.checklist-inline .checklist-item { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 400; }

/* A project's own chosen workflow steps, drag-to-reorder -- see
   wireWfStepsDragAndDrop()/wfStepElementAfterPoint() in app.js for the
   mechanics (the same native-HTML5-drag-and-drop, live-DOM-reorder-then-
   commit-on-drop approach Board's own cards/lists use). Catalog steps not
   yet on the project render separately, as plain "+ Name" pills in
   #wf-steps-available (.checklist-inline above, reusing the pill look
   .subnav-link already has for the Departments filter chips) -- clicking
   one just appends it to the end of this list; this is what you then drag
   into place. */
.wf-steps-selected-list { list-style: none; margin: 6px 0 0; padding: 0; max-width: 460px; }
.wf-step-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  margin-bottom: 6px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: grab;
}
.wf-step-row.dragging { opacity: 0.4; }
.wf-step-drag-handle { color: var(--text-muted); font-size: 15px; line-height: 1; cursor: grab; }
.wf-step-name { flex: 1; font-size: 14px; }
.wf-step-row .wf-step-remove { font-size: 12px; }
button.wf-step-add { cursor: pointer; }

/* ---------- Project detail — tasks under each workflow step ---------- */
.workflow-step-tasks-block { margin-bottom: 24px; }
.workflow-step-tasks-block:last-child { margin-bottom: 0; }
.workflow-step-tasks-block h3 { margin: 0 0 8px; font-size: 15px; }

.task-card { border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; margin-bottom: 10px; }
.task-card-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.task-done-label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.task-done-label strong.done { color: var(--text-muted); text-decoration: line-through; }
.task-card-actions { display: flex; gap: 10px; flex-shrink: 0; }
.task-card-meta { margin-top: 6px; }

.task-checklist-block { margin-top: 10px; }
.task-checklist { list-style: none; margin: 0 0 6px; padding: 0; }
.task-checklist-item { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.task-checklist-item span.done { color: var(--text-muted); text-decoration: line-through; }
.task-checklist-text { flex: 1; border: 1px solid transparent; background: transparent; font-size: 14px; padding: 2px 4px; border-radius: 4px; }
.task-checklist-text:hover, .task-checklist-text:focus { border-color: var(--border); background: #fff; }
.task-checklist-add-form { display: flex; gap: 8px; margin-top: 4px; }
.task-checklist-new-text { flex: 1; }

/* ---------- Project detail — photo album ---------- */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.photo-grid-item {
  cursor: pointer;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: #fff;
  text-align: left;
  padding: 0;
  font: inherit;
}
.photo-grid-item:hover { border-color: var(--navy-light); }
.photo-grid-item img { display: block; width: 100%; height: 110px; object-fit: cover; background: var(--bg); }
.photo-grid-item .photo-caption { padding: 6px 8px; }
.photo-grid-item .photo-name {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.photo-grid-item .photo-meta { display: block; font-size: 11px; color: var(--text-muted); margin-top: 2px; }

/* ---------- Costs (Home) ---------- */
.receipt-review {
  margin-top: 16px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fafbfc;
}
.receipt-review-body { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }
.receipt-thumb {
  width: 140px;
  height: 140px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: #fff;
  flex: none;
}
/* Wraps the Home Costs card's own attachment preview (thumbnail + the same
   small attachment-icon link .attachment-icon uses elsewhere — see
   setReceiptThumb() in expenses.js) so a PDF, which has no thumbnail to
   show, still gets a visible way to actually look at it before saving, in
   the same spot an image's thumbnail would sit. */
.receipt-attachment-preview { display: flex; flex-direction: column; gap: 6px; flex: none; }
.receipt-thumb-link { display: block; flex: none; }
.receipt-thumb-link .receipt-thumb { cursor: pointer; }
.receipt-review-fields { flex: 1 1 320px; min-width: 260px; }
.receipt-review-fields .message { display: block; margin-bottom: 10px; }
/* Read-only stand-in for a field value, used where a review panel shows
   the submitted data without letting it be edited (e.g. a Project
   Manager's Expense Approvals review, which can only approve or leave a
   cost alone -- correcting it is Finance's job). */
.static-field {
  margin: 0;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fafbfc;
  min-height: 1.2em;
}
.receipt-drafts-list {
  list-style: none;
  margin: 8px 0 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.receipt-drafts-list li { font-size: 13.5px; }
#receipts-table td.num { text-align: right; }

/* Change history -- Contracts (Schedule of Values) and Cost Budgets, each
   save's required change note listed newest first alongside who made it
   and when. Same plain, unstyled-list-turned-flex-column approach as
   .receipt-drafts-list above rather than a new table. */
.change-history-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.change-history-list li {
  font-size: 13.5px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}
.change-history-list li:last-child { border-bottom: none; padding-bottom: 0; }
.change-history-list .change-log-meta {
  display: block;
  color: var(--text-muted);
  font-size: 12px;
  margin-bottom: 2px;
}

/* Submittals -- a package's selected-documents list and its drag-to-
   reorder list (public/submittals.js) both use plain .change-history-list
   <li>s with this row layout inside: the document's name/manufacturer on
   the left, one action (Remove, or nothing) on the right. */
.submittal-item-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.submittal-item-name { font-weight: 600; }
.submittal-item-manufacturer { color: var(--text-muted); font-size: 12px; margin-left: 6px; }
/* The BOM "Needs attention" panel's row-right action cluster (see
   bomLineRowHtml in public/submittals.js) -- View/Remove/Accept for a
   'suggested' line, or the attach-file input/Dismiss for a 'not_found' one
   -- needs its own gap between actions the way .actions gets elsewhere,
   since .submittal-item-row's own gap only separates the label side from
   the action side, not actions from each other. */
.bom-line-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
/* The reorder list's own <li>s are draggable -- same vanilla-HTML5-DnD,
   "live DOM reorder while dragging, one API call on drop" pattern as
   board.js's cards (see wireSubmittalReorder in public/submittals.js),
   just a flat list instead of cards-within-lists. */
.change-history-list li.submittal-reorder-item { cursor: grab; background: #fafbfc; border-radius: 6px; padding: 8px 10px; border-bottom: none; margin-bottom: 6px; }
.submittal-reorder-item.dragging { opacity: 0.4; }
.submittal-drag-handle { color: var(--text-muted); margin-right: 8px; }

/* Journal entries — Finance page: the editor's running debit/credit totals
   and balanced/out-of-balance indicator (see public/journal.js
   recalculateJeTotals()). */
#je-lines-table td.num,
#je-lines-table input[type="number"] { text-align: right; }
.je-totals {
  display: flex;
  gap: 20px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 10px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fafbfc;
  font-size: 13.5px;
}
.je-balance-indicator { font-weight: 600; padding: 2px 10px; border-radius: 999px; }
.je-balance-indicator.balanced { color: #1a7f37; background: #e6f4ea; }
.je-balance-indicator.unbalanced { color: var(--danger); background: #fdeceb; }

/* ===================================================================
   Mobile (narrow screens) — everything above this point already degrades
   reasonably on its own (forms/cards stack via flex-wrap, tables scroll in
   their own .table-scroll box, nav wraps instead of hiding tabs off-screen)
   without needing a breakpoint. This is the one thing that genuinely needs
   one: iOS Safari auto-zooms the whole page in when a focused text
   input/select is under 16px, which reads as broken/janky on a phone even
   though nothing is actually wrong. Scoped to narrow screens rather than
   raised globally so the desktop layout stays at the density it was
   already tuned for.
   =================================================================== */
@media (max-width: 640px) {
  input, select, textarea, .editable-cell-input {
    font-size: 16px;
  }
}

/* ---------- Bidding: bid letter preview (Project Management > Bidding) ---------- */
#bid-letter-preview-wrap { margin-top: 16px; }
#bid-letter-preview {
  width: 100%;
  height: 800px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #fff;
}

/* ---------- Bidding: Plans tab (live SharePoint drawings, browsed and
   auto-sorted by discipline -- deliberately built to look like Project
   documents below, reusing its .pdoc-* classes directly; see the file
   header comment on that Plans tab panel in index.html) ---------- */
/* Same look as #pdoc-connect-banner (see the "Project documents" CSS block
   below) -- its own rule since each page's banner is its own fixed id,
   not worth a shared class for what's otherwise a one-off per page. */
#bid-plans-connect-banner, #library-connect-banner {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 16px;
  margin: 8px 0 16px;
}
#bid-plans-connect-banner p, #library-connect-banner p { margin: 0 0 10px; }

/* ---------- Bidding: Cost Estimate tab draft grid ---------- */
/* Same "fixed-width input" convention as .line-qty/.line-unit-cost/
   .line-hours (set inline in public/bidding.js) -- the Cost Code column
   just needs a bit more room for "CODE — Name". */
.line-cost-code { width: 160px; }
/* A row a Save attempt flagged as missing its Cost Code -- cleared on the
   next full re-render (public/bidding.js's renderBidEstimateRows()). */
#bid-lines-table tr.row-error { background: #fbdede; }

/* ---------- Project Financials: change-order ledger ---------- */
/* Reuses the generic table/.table-scroll styling everywhere else in this
   app uses -- just a touch of breathing room above the "Add" row. */
#co-new-form { margin-bottom: 12px; }

/* ---------- Project documents (plans & specifications) ---------- */
/* See public/documents.js -- its own dedicated page, reached from Project
   detail's "Documents" button. #pdoc-connect-banner is a plain callout,
   not a .card -- it lives inside the page's own card. */
#pdoc-connect-banner {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 16px;
  margin: 8px 0 16px;
}
#pdoc-connect-banner p { margin: 0 0 10px; }

/* The live SharePoint file manager -- reuses the generic table/.table-scroll
   look elsewhere in this app, just with an extra drag-and-drop affordance
   around it (see #pdoc-dropzone below) and a name column that can hold
   either a clickable folder row or a real file link. */
.pdoc-browse-table { width: 100%; min-width: 620px; border-collapse: collapse; margin-top: 10px; }
.pdoc-browse-table th, .pdoc-browse-table td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--border); font-size: 14px; }
.pdoc-browse-table th { font-weight: 600; color: var(--text-muted); }
.pdoc-browse-name { display: inline-flex; align-items: center; gap: 6px; color: var(--navy); text-decoration: none; }
a.pdoc-browse-name:hover, .pdoc-folder-open:hover { text-decoration: underline; }
.pdoc-folder-open { cursor: pointer; }
.pdoc-crumb { cursor: pointer; color: var(--navy); }
.pdoc-crumb:hover { text-decoration: underline; }

/* #bid-plans-dropzone (Bidding Plans tab) and #library-dropzone (Project
   Management > Library tab) share this same drag-active look -- listed
   alongside #pdoc-dropzone rather than given their own rules, since it's
   the identical "table area doubles as a drop target" pattern each time. */
#pdoc-dropzone, #bid-plans-dropzone, #library-dropzone { border: 2px dashed transparent; border-radius: 8px; transition: border-color 0.15s, background 0.15s; }
#pdoc-dropzone.pdoc-dropzone-active, #bid-plans-dropzone.pdoc-dropzone-active, #library-dropzone.pdoc-dropzone-active { border-color: var(--navy); background: var(--bg); }

/* Back/Forward/Up -- a tight icon-button cluster, Explorer-style, rather
   than full-width text buttons. Disabled (no history/at the root) fades
   instead of disappearing, same as Explorer graying its own out. */
.pdoc-nav-group { display: inline-flex; gap: 2px; flex: none; }
.pdoc-nav-btn { padding: 5px 11px; line-height: 1; font-size: 15px; font-weight: 700; }
.pdoc-nav-btn:disabled { opacity: 0.35; cursor: default; }
.pdoc-nav-btn:disabled:hover { background: #eef1f5; }

/* Each row's Rename/Move/Delete live behind one "⋮" actions button instead
   of three separate buttons -- .pdoc-actions-wrap is the positioning
   context (like .project-combobox above) for its own dropdown menu. */
.pdoc-actions-wrap { position: relative; display: inline-block; }
.pdoc-actions-btn { padding: 4px 10px; line-height: 1; font-weight: 700; font-size: 16px; }
.pdoc-actions-menu {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 20;
  margin-top: 4px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  min-width: 130px;
  padding: 4px;
}
.pdoc-actions-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  border: none;
  background: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  color: var(--text);
}
.pdoc-actions-menu-item:hover { background: var(--bg); }
.pdoc-actions-menu-item.pdoc-actions-menu-item-danger { color: var(--danger); }

/* The "Move to…" modal's own folder-only picker -- a plain list, since it
   has no columns/actions of its own beyond "open this subfolder". */
.pdoc-browse-list { list-style: none; margin: 10px 0 0; padding: 0; max-height: 360px; overflow-y: auto; }
.pdoc-browse-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.pdoc-browse-item:last-child { border-bottom: none; }

/* ---------- Home > Board (personal Trello-style board) ---------- */

/* Board lists sit inside the same 960px-max .app shell every other page
   does -- rather than break that constraint, several lists just scroll
   horizontally within their own strip, same idea as .table-scroll for a
   wide table. */
.board-lists-scroll {
  display: flex;
  /* Without this, flex's default cross-axis behavior (stretch) made every
     list in the row match the height of the row's TALLEST list -- so a
     one-card list visually stretched to fill the same space as a
     ten-card list next to it. flex-start lets each .board-list size to
     its own content instead (bounded by its own max-height: 70vh/88vh
     rule above, same as before -- only the artificial matching-height
     stretch is what's removed here), at Dan's request. */
  align-items: flex-start;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 8px;
  margin-top: 4px;
}
.board-list {
  flex: 0 0 250px;
  background: #eef1f5;
  border-radius: var(--radius);
  padding: 8px;
  display: flex;
  flex-direction: column;
  max-height: 70vh;
}
.board-list.dragging { opacity: 0.4; }
/* Full screen -- shared by BOTH boards this app has: the Home > Board tab
   (#board-card, toggled by #boardFullscreenBtn in public/board.js) and a
   project's own Board (#pjb-card, toggled by #pjbFullscreenBtn in
   public/project-board.js). One class, not two copies, since both boards
   already share the exact same .board-list/.board-card markup. NOT the
   browser's native Fullscreen API -- `position: fixed; inset: 0` just
   expands whichever card has this class to cover the browser's own
   viewport/content area, above the app's header/nav (z-index below
   .modal-overlay's 1000 -- see the comment there -- so a card/labels modal
   opened while maximized still layers on top correctly), while the
   browser's address bar/tabs and the OS taskbar stay exactly where they
   are -- see the comment in board.js/project-board.js for why. */
.board-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: var(--card-bg);
  padding: 20px 24px;
  margin: 0;
  border-radius: 0;
  overflow-y: auto;
}
.board-fullscreen .board-list { max-height: 88vh; }
.board-list-header { display: flex; align-items: center; gap: 4px; margin-bottom: 6px; }
.board-list-name {
  flex: 1;
  font-weight: 700;
  font-size: 14px;
  border: 1px solid transparent;
  background: transparent;
  padding: 3px 6px;
  border-radius: 4px;
}
.board-list-name:hover, .board-list-name:focus { border-color: var(--border); background: #fff; }
.board-list-name-static { flex: 1; font-weight: 700; font-size: 14px; padding: 3px 6px; }
.board-list-cards {
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  /* Set to 200px at Dan's request -- this is the visible "background" area
     of a list with zero or few cards (keeps a real, easy-to-hit drop
     target, and shows a real slice of the board's background color/photo
     -- see applyBoardBackground() in board.js/project-board.js -- rather
     than a sliver), on both boards this app has (Home's personal board
     and a project's own -- shared by both since they use this exact same
     class). It's a floor, not a fixed height: a list with enough cards to
     need more room still grows past it (see align-items: flex-start on
     .board-lists-scroll above, which is what stops a short list from
     being stretched to match a taller sibling instead of just sizing to
     its own cards). */
  min-height: 200px;
  padding: 2px;
}
.board-card {
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);
  padding: 8px 10px;
  cursor: pointer;
}
.board-card.dragging { opacity: 0.4; }
.board-card-labels { display: flex; gap: 4px; margin-bottom: 5px; flex-wrap: wrap; }
.board-label-chip { display: inline-block; width: 32px; height: 8px; border-radius: 4px; }
.board-card-top { display: flex; align-items: flex-start; gap: 8px; }
.board-card-top input[type="checkbox"] { margin-top: 3px; flex: none; }
.board-card-title { font-size: 13px; line-height: 1.35; }
.board-card-title.done { color: var(--text-muted); text-decoration: line-through; }
.board-card-meta { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.board-card-checklist-count { font-size: 11px; color: var(--text-muted); background: #eef1f5; border-radius: 10px; padding: 2px 7px; }
.board-card-checklist-count.all-done { color: #12693b; background: #dcf3e3; }
.status-badge.status-due { background: #eef1f5; color: var(--navy); }
.status-badge.status-overdue { background: #fbdede; color: #8a1f1f; }
.board-add-card-form { margin-top: 4px; }
.board-add-card-form input { font-size: 13px; padding: 6px 8px; }
.board-add-list-form {
  flex: 0 0 250px;
}
.board-add-list-form input {
  width: 100%;
  background: #eef1f5;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 10px;
  font-size: 13px;
}

/* Card detail modal */
.board-card-modal-title-row { align-items: flex-start; }
.board-card-modal-title-row input[type="checkbox"] { margin-top: 10px; flex: none; }
.board-card-modal-title-input { flex: 1; font-size: 16px; font-weight: 700; }
.board-card-modal-labels { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
.board-label-toggle {
  border: none;
  border-radius: 4px;
  padding: 4px 10px;
  font-size: 12px;
  color: #fff;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
  cursor: pointer;
  opacity: 0.55;
}
.board-label-toggle.active { opacity: 1; }
.board-label-toggle.disabled, .board-label-toggle:disabled { cursor: default; }

/* Manage labels modal */
.board-label-color-picker { display: flex; gap: 6px; flex-wrap: wrap; }
.board-label-swatch {
  width: 22px;
  height: 22px;
  border-radius: 4px;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
}
.board-label-swatch.selected { border-color: var(--navy); }
.board-labels-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}
.board-labels-row:last-child { border-bottom: none; }
.board-label-color-picker-inline { flex: 0 0 auto; max-width: 130px; }
.board-label-name-input { flex: 1; }

/* Board background modal (public/board.js / public/project-board.js) --
   bigger than the label swatches above since these are meant to preview
   a large block of color, not a small chip. */
.board-bg-swatches { display: flex; gap: 8px; flex-wrap: wrap; }
.board-bg-swatch {
  width: 36px;
  height: 36px;
  border-radius: 6px;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
}
.board-bg-swatch:hover { border-color: var(--navy); }
#boardBgColorInput, #pjbBgColorInput { width: 44px; height: 34px; padding: 2px; border: 1px solid var(--border); border-radius: 6px; cursor: pointer; }

/* A board with a custom color/photo (see applyBoardBackground() /
   updateProjectBoardBackground()) sets background-color/-image as inline
   style directly on #board-card/#pjb-card, so it isn't affected by
   .board-fullscreen's own `background:` shorthand either way. This class
   just gives the header row and any muted note sitting directly on top of
   that background a legible backing -- without it, dark photos or colors
   would swallow the "Board" title, toolbar buttons, and read-only note. */
.board-has-background > .card-header-row {
  background: rgba(255, 255, 255, 0.85);
  border-radius: 8px;
  padding: 8px 12px;
  margin: -8px -4px 14px;
}
.board-has-background > .muted-note {
  display: inline-block;
  background: rgba(255, 255, 255, 0.85);
  border-radius: 6px;
  padding: 2px 8px;
}

/* ---------- Labor Forecast (Management > Labor Forecast) ----------
   A sticky-header, sticky-left-columns table -- this app's first use of
   that pattern (every other scrollable table, e.g. WIP, just scrolls the
   whole page/card). 6 frozen columns (Code/Project/Est/To Date/Remaining/
   To Schedule) stay pinned on the left while up to ~61 month columns scroll
   underneath; the header row stays pinned on top. See laborForecast.js for
   the LF_FROZEN_WIDTH constant that must stay in sync with the `left`
   offsets below -- it's how the "This Month" button computes where to
   scroll to without having to measure sticky elements at runtime. */
.labor-forecast-scroll {
  margin-top: 10px;
  max-height: 65vh;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  /* Positioned so it becomes the offsetParent for the table/header cells
     inside it -- without this, nothing between the table and <body> is
     positioned, so a cell's `.offsetLeft` measures from the page's own
     origin (shifted by the card's padding/nav width) instead of from this
     scroll container, which is what scrollLaborForecastToCurrentMonth() in
     laborForecast.js actually needs. */
  position: relative;
}
.labor-forecast-table {
  margin-top: 0;
  font-size: 13px;
  border-collapse: separate;
  border-spacing: 0;
  /* Fixed layout, not auto -- auto sizing lets a wide cell's content push a
     column wider than its stated `width`, which would silently throw off
     every sticky `left` offset below AND the LF_FROZEN_WIDTH/100px-per-month
     math scrollLaborForecastToCurrentMonth() in laborForecast.js relies on.
     With fixed layout, the widths declared below are authoritative; cells
     that don't fit truncate (lf-col-code/est/todate/remaining/toschedule,
     all single values) or wrap onto a second line (lf-col-name, header
     labels) instead of resizing the column. */
  table-layout: fixed;
}
.labor-forecast-table th, .labor-forecast-table td {
  border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border);
  background: #fff;
  /* border-box so each column's CSS `width` is its true rendered width
     (border/padding included) -- see the table-layout comment above. */
  box-sizing: border-box;
  overflow: hidden;
}
.labor-forecast-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  white-space: normal;
}
.labor-forecast-table .lf-frozen {
  position: sticky;
  z-index: 1;
}
.labor-forecast-table thead th.lf-frozen { z-index: 3; }
.lf-col-code { left: 0; width: 90px; white-space: nowrap; text-overflow: ellipsis; }
.lf-col-name { left: 90px; width: 170px; white-space: normal; }
.lf-col-est, .lf-col-todate, .lf-col-remaining, .lf-col-toschedule {
  text-align: right;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.lf-col-est { left: 260px; width: 90px; }
.lf-col-todate { left: 350px; width: 90px; }
.lf-col-remaining { left: 440px; width: 100px; }
/* The bold right border here is the sticky-table equivalent of the
   reference spreadsheet's vertical black divider line separating its
   summary columns from the monthly forecast grid. */
.lf-col-toschedule { left: 540px; width: 100px; border-right: 2px solid var(--navy); }
.lf-month-col { width: 100px; text-align: center; padding: 6px 8px; white-space: nowrap; }
.lf-month-actual, .lf-month-forecast { overflow: hidden; text-overflow: ellipsis; }
.labor-forecast-table thead th.lf-current-month,
.labor-forecast-table tbody td.lf-current-month {
  background: #eef3fb;
}
.lf-month-actual { font-weight: 700; font-size: 13px; }
.lf-month-forecast { color: var(--text-muted); font-size: 11px; margin-top: 2px; }
.lf-month-input {
  width: 72px;
  text-align: right;
  font-size: 12.5px;
  padding: 3px 5px;
  border: 1px solid var(--border);
  border-radius: 4px;
}
.lf-month-input:disabled { opacity: 0.6; }
/* Positive To Schedule = unscheduled work still needs a month; negative =
   the plan already overshoots what's remaining -- see the sign-convention
   comment in laborForecast.js, a deliberate flip from the old spreadsheet
   this feature replaces. */
.labor-forecast-table td.lf-needs-schedule { color: var(--danger); font-weight: 700; }
.labor-forecast-table td.lf-over-scheduled { color: #9a6700; font-weight: 700; }
.labor-forecast-toggle-row, .labor-forecast-today-row { margin-top: 10px; }

/* "Full screen" -- same mechanism as .board-fullscreen above (see its own
   comment for the full reasoning: NOT the native Fullscreen API, just this
   card expanded to cover the browser's own content area while the address
   bar/taskbar stay put), kept as its own class rather than reusing
   .board-fullscreen so this card isn't tied to the Board feature's name --
   see wireLaborForecastFullscreen() in laborForecast.js. The whole point
   here is the extra width: the app's own content column caps out at 960px
   (see .app), which is only enough room for 2-3 month columns alongside the
   6 frozen ones -- maximized, this card gets the full viewport width
   instead, showing several more months at once with no scrolling needed. */
.labor-forecast-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: var(--card-bg);
  padding: 20px 24px;
  margin: 0;
  border-radius: 0;
  overflow-y: auto;
}
.labor-forecast-fullscreen .labor-forecast-scroll { max-height: 82vh; }

/* =================================================================
   Opaero aviation pages (public/aviation.js)
   ================================================================= */
.av-form { margin: 8px 0 16px; }
.av-form textarea, .av-sq textarea { width: 100%; font: inherit; }
.av-check { display: flex; align-items: center; gap: 6px; font-size: 13px; margin-right: 12px; align-self: flex-end; padding-bottom: 8px; }
.av-mini-link { font-size: 11px; margin-left: 6px; color: var(--navy-light); }
.av-btn-link { text-decoration: none; display: inline-flex; align-items: center; }
.av-sm { padding: 4px 10px !important; font-size: 12px !important; }
.av-xs { width: 64px !important; }
.av-s { width: 96px !important; }
.av-nowrap { white-space: nowrap; }
.av-small { font-size: 12px; color: var(--text-muted); }
.av-click { cursor: pointer; }
.av-click:hover td { background: #f3f6fc; }
.av-inactive td { opacity: 0.5; }
.av-link-navy { color: var(--navy) !important; }
.av-flash { margin: 0 0 10px; font-weight: 600; }
.av-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-top: 12px; }
.av-kpi { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; background: #fafbfd; display: flex; flex-direction: column; gap: 4px; }
.av-kpi span { font-size: 12px; color: var(--text-muted); }
.av-kpi strong { font-size: 20px; color: var(--navy); }
.av-kpi small { color: var(--text-muted); }
.av-kpi-alert { border-color: #f3c8c4; background: #fdf3f2; }
.av-kpi-alert strong { color: var(--danger); }
.av-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.av-chip { display: inline-flex; gap: 6px; align-items: center; font-weight: 600; }
.av-badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 600; background: #eef1f5; color: var(--navy); white-space: nowrap; }
.av-overdue, .av-cancelled, .av-void, .av-no-show, .av-declined { background: #fbdede; color: #8a1f1f; }
.av-due-soon, .av-awaiting-parts, .av-awaiting-approval, .av-sent, .av-deferred { background: #fdecc8; color: #7a5200; }
.av-current, .av-complete, .av-completed, .av-paid, .av-invoiced, .av-approved { background: #dcf3e3; color: #12693b; }
.av-in-work, .av-inspection, .av-dispatched, .av-fleet { background: #dceefb; color: #0b5a86; }
.av-dialog { border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; width: min(560px, 92vw); }
.av-dialog::backdrop { background: rgba(15, 23, 42, 0.35); }
.av-dialog h2 { margin-top: 0; color: var(--navy); font-size: 16px; }
/* fuel */
.av-tanks { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
.av-tank { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px; }
.av-tank-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.av-gauge { height: 14px; border-radius: 7px; background: #eef1f5; overflow: hidden; }
.av-gauge div { height: 100%; background: var(--navy-light); }
.av-gauge div.low { background: #c2410c; }
.av-tank-nums { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 13px; margin-top: 6px; color: var(--text-muted); }
/* flight timeline */
.av-timeline { min-width: 680px; margin-top: 8px; }
.av-tl-row { display: flex; align-items: stretch; border-bottom: 1px solid var(--border); }
.av-tl-label { flex: 0 0 96px; padding: 8px 6px; display: flex; flex-direction: column; font-size: 13px; }
.av-tl-label small { color: var(--text-muted); font-size: 11px; }
.av-tl-track { position: relative; flex: 1; min-height: 46px; cursor: copy; background-image: linear-gradient(to right, #eef1f5 1px, transparent 1px); background-size: 6.25% 100%; }
.av-tl-head .av-tl-track { min-height: 22px; cursor: default; background: none; }
.av-tl-hours div { position: absolute; top: 3px; font-size: 11px; color: var(--text-muted); transform: translateX(-2px); }
.av-block { position: absolute; top: 6px; bottom: 6px; border-radius: 6px; background: var(--navy-light); color: #fff; font-size: 11.5px; padding: 3px 6px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer; box-shadow: 0 1px 2px rgba(0,0,0,.15); }
.av-block.av-cancelled { opacity: 0.35; text-decoration: line-through; }
/* invoices */
.av-lines input[name="description"] { width: 100%; min-width: 220px; }
.av-totals { display: flex; justify-content: flex-end; gap: 18px; margin: 12px 0; font-size: 14px; }
.av-grand strong { color: var(--navy); font-size: 17px; }
@media (max-width: 640px) {
  .av-tl-label { flex-basis: 70px; }
  .av-totals { flex-direction: column; align-items: flex-end; gap: 4px; }
}
[id^="av-"] .card-header-row .filters input,
[id^="av-"] .card-header-row .filters select { width: auto; }
[id^="av-"] .card-header-row .filters { justify-content: flex-end; }
.av-tank-edit { margin-top: 10px; margin-bottom: 0; }
.av-form .actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.av-form .actions select { width: auto; }
.av-form .actions button { white-space: nowrap; }
/* 24-hour flight timeline: night shading (midnight–sunrise, sunset–midnight) */
.av-timeline { min-width: 900px; }
.av-tl-track { background-size: calc(100% / 24) 100%; }
.av-night { position: absolute; top: 0; bottom: 0; background: rgba(100, 116, 139, 0.18); pointer-events: none; }
.av-tl-hours .av-night { background: rgba(100, 116, 139, 0.12); }
.av-tl-hours div:not(.av-night) { z-index: 1; }
.av-block { z-index: 1; }
.av-tl-hours .av-night { top: 0; transform: none; }
/* Flight timeline: colors by booking type + drag handles */
.av-block.av-c-solo { background: #1e40af; }
.av-block.av-c-dual { background: #15803d; }
.av-block.av-c-mx { background: repeating-linear-gradient(45deg, #64748b, #64748b 6px, #475569 6px, #475569 12px); }
.av-block { display: flex; align-items: flex-start; padding: 3px 8px; cursor: grab; touch-action: none; user-select: none; }
.av-block.av-locked { cursor: pointer; }
.av-block.av-dragging { cursor: grabbing; opacity: 0.85; box-shadow: 0 4px 10px rgba(0,0,0,.25); z-index: 3; }
.av-block-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; }
.av-h { position: absolute; top: 0; bottom: 0; width: 7px; cursor: ew-resize; }
.av-h-l { left: 0; border-radius: 6px 0 0 6px; }
.av-h-r { right: 0; border-radius: 0 6px 6px 0; }
.av-block:hover .av-h { background: rgba(255,255,255,.35); }
.av-ghost { opacity: 0.55; pointer-events: none; border: 2px dashed #fff; z-index: 2; }
.av-tl-track[data-aircraft] { touch-action: none; cursor: crosshair; }
.av-legend { display: flex; flex-wrap: wrap; gap: 14px; font-size: 12px; color: var(--text-muted); margin: 8px 0 0 96px; }
.av-legend span { display: inline-flex; align-items: center; gap: 6px; }
.av-legend i { display: inline-block; width: 14px; height: 10px; border-radius: 3px; }
.av-legend i.av-c-solo { background: #1e40af; }
.av-legend i.av-c-dual { background: #15803d; }
.av-legend i.av-c-mx { background: repeating-linear-gradient(45deg, #64748b, #64748b 3px, #475569 3px, #475569 6px); }
/* Zoomed timeline: width set by fitTimeline(); labels stay pinned while scrolling sideways */
.av-timeline { min-width: 0; }
#av-tl-scroll { overflow-x: auto; scroll-behavior: auto; }
.av-tl-label { position: sticky; left: 0; z-index: 5; background: var(--card-bg); box-shadow: 1px 0 0 var(--border); }
.av-legend { margin-left: 0; }
/* Customers import */
.av-import { border: 1px dashed var(--border); border-radius: var(--radius); padding: 12px 14px; margin: 12px 0; background: #fafbfd; }
.av-map { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 8px 12px; margin: 8px 0; }
.av-map .field { min-width: 0; }
.av-imp-table { max-height: 340px; overflow-y: auto; }
/* Customer portal + logins */
.av-block.av-c-other { background: #94a3b8; cursor: default; }
.av-legend i.av-c-other { background: #94a3b8; }
.av-login-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 4px 12px; max-height: 300px; overflow-y: auto; margin: 8px 0 12px; }
.av-login-list .av-check { padding: 0; margin: 0; align-self: auto; }
.av-login-box { border-top: 1px solid var(--border); margin-top: 12px; padding-top: 4px; }
.portal-only.hidden { display: none !important; }
/* Not-yet-saved booking dragged out on the flight timeline */
.av-block.av-pending { opacity: 0.85; border: 2px dashed #fff; outline: 2px solid rgba(30, 64, 175, 0.35); z-index: 2; pointer-events: none; }
.av-kind-boxes { display: flex; flex-wrap: wrap; gap: 4px 4px; }
.av-kind-boxes .av-check { padding-bottom: 2px; }
.av-kind-boxes { margin-bottom: 10px; }
