/* ===========================================================================
   site.css - all of FYP's visual styling lives in this one file.
   The colors below are the NZ Electrical Solutions palette used in the
   report form / dashboard mockups. Change a value here and it updates
   everywhere that uses it - that's the point of CSS "variables" (custom
   properties): one source of truth for each color/spacing value.
   =========================================================================== */

:root {
  --navy: #0c1a45;
  --navy-light: #17296e;
  --blue: #14a3e0;
  --blue-dark: #0f8ac0;
  --bg: #f4f6fa;
  --card: #ffffff;
  --border: #e2e6ee;
  --text: #1c2333;
  --text-muted: #6b7280;
  --red: #e0453f;
  --orange: #f2994a;
  --yellow: #e0b400;
  --green: #2ea86b;
  --radius: 10px;
  --shadow: 0 1px 3px rgba(16, 24, 64, 0.08);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
}

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- Top brand header (used on the public form + login pages) ---------- */
.fyp-topbar {
  background: var(--navy);
  color: #fff;
  padding: 18px 24px;
  display: flex;
  align-items: center;
  gap: 16px;
}
.fyp-topbar .topbar-logo { height: 40px; width: auto; display: block; }
.fyp-topbar .brand { display: flex; flex-direction: column; line-height: 1.15; }
.fyp-topbar .divider { width: 1px; height: 34px; background: rgba(255,255,255,0.25); }
.fyp-topbar .fyp-logo strong { font-size: 1.35rem; font-style: italic; }
.fyp-topbar .fyp-logo span { font-size: 0.65rem; letter-spacing: 0.06em; }

/* ---------- Generic page/card layout ---------- */
.page-shell { max-width: 640px; margin: 0 auto; padding: 24px 20px 60px; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 20px; }

h1.page-title { font-size: 1.8rem; margin: 4px 0 4px; color: var(--navy); }
p.page-subtitle { color: var(--text-muted); margin: 0 0 24px; }

/* ---------- Form controls ---------- */
label.field-label { display: block; font-weight: 600; margin: 20px 0 6px; color: var(--navy); }
label.field-label:first-of-type { margin-top: 0; }

.form-control, select.form-control {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 1rem;
  background: #fff;
  color: var(--text);
}
.form-control:focus, select.form-control:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(20,163,224,0.15); }

.photo-drop {
  display: block; /* <label> is inline by default - without this, its padding/border
                      doesn't reserve layout space and it overlaps the text above it */
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  padding: 34px 16px;
  margin-top: 10px;
  text-align: center;
  color: var(--blue);
  cursor: pointer;
  background: #fafbfd;
}
.photo-drop input[type=file] { display: none; }
.photo-drop svg { display: block; margin: 0 auto 8px; }
.photo-drop img.preview { max-width: 100%; max-height: 220px; border-radius: 8px; margin-top: 10px; }

.radio-row {
  display: flex; align-items: center; justify-content: space-between;
  border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; margin-top: 10px;
  cursor: pointer; background: #fff;
}
.radio-row:hover { border-color: var(--blue); }
.radio-row .left { display: flex; align-items: center; gap: 12px; }
.radio-row input[type=radio] { width: 20px; height: 20px; accent-color: var(--blue); }

.btn-primary {
  width: 100%; padding: 15px; margin-top: 26px;
  background: var(--blue); color: #fff; border: none; border-radius: 10px;
  font-size: 1.05rem; font-weight: 700; letter-spacing: 0.03em; cursor: pointer;
}
.btn-primary:hover { background: var(--blue-dark); }
.btn-primary:disabled { opacity: 0.6; cursor: default; }

.btn-secondary {
  padding: 10px 16px; background: #fff; color: var(--navy); border: 1px solid var(--border);
  border-radius: 8px; font-weight: 600; cursor: pointer;
}
.btn-secondary:hover { border-color: var(--blue); color: var(--blue); }

/* Overrides .btn-primary's width:100% (correct for a full-width form submit
   button, which is what it's for everywhere else) for one used inline next
   to another button instead - e.g. Notifications.cshtml's header actions.
   Without this, a flex sibling still holding width:100% forces its own
   flex-basis to the full row width, so a shrink-to-fit neighbour (like a
   .btn-secondary with no width set) gets squeezed far more than it should
   and can end up wrapping its own text, while the width:100% button ends up
   wider than its own content needs. Also pins an explicit height (rather
   than relying on flex stretch to match a sibling's auto-computed height,
   which turned out not to render consistently) so two buttons with
   different padding/border/font-size can never end up a different height
   no matter what - .btn-secondary's 1px border and smaller padding would
   otherwise still leave it a couple of px short even under stretch. Zeroed
   vertical padding since the fixed height plus centered flex content is
   what now does that job instead. Also zeroes margin-top - .btn-primary's
   base style sets margin-top:26px (spacing below the form field it
   normally follows), which .btn-secondary doesn't have, so even with
   identical heights the two buttons' boxes sat at different vertical
   positions in the row until this reset it. */
.btn-inline { width: auto; height: 50px; padding: 0 20px; margin-top: 0; flex-shrink: 0; white-space: nowrap; display: inline-flex; align-items: center; justify-content: center; }

/* NZES-only "Download QR code" choice (print vs engrave) on
   Dashboard/Sites - a native <details>/<summary> disclosure, no JS needed.
   A client manager's download link is a plain .btn-secondary <a>, untouched
   by any of this - see Sites.cshtml. */
.qr-download-menu { position: relative; display: inline-block; }
.qr-download-menu > summary { list-style: none; }
.qr-download-menu > summary::-webkit-details-marker { display: none; }
.qr-download-menu > summary::after { content: " \25BE"; }
.qr-download-menu .qr-download-options {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 10; min-width: 180px;
  background: #fff; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 6px; display: flex; flex-direction: column; gap: 2px;
}
.qr-download-options a { padding: 8px 10px; border-radius: 6px; color: var(--navy); text-decoration: none; font-weight: 600; font-size: 0.9rem; }
.qr-download-options a:hover { background: #eef6fc; color: var(--blue); }
.qr-download-options .option-hint { display: block; font-weight: 400; font-size: 0.76rem; color: var(--text-muted); }


.footer-note { text-align: center; color: var(--text-muted); font-size: 0.8rem; margin-top: 22px; }

.alert { padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; font-size: 0.92rem; }
.alert-success { background: #e6f7ee; color: #1c7a45; border: 1px solid #b9e6cb; }
.alert-error { background: #fdecec; color: #b3221e; border: 1px solid #f6c3c1; }
.field-error { color: var(--red); font-size: 0.82rem; margin-top: 4px; }

/* Email "chip"/tag entry (Notifications.cshtml's recipient group form -
   see setupEmailChipInput in site.js) - looks like a single .form-control
   box but each already-added address renders as its own removable pill,
   with a plain text input for typing the next one sharing the same box. */
.email-chip-box {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
  background: #fff; min-height: 46px;
}
.email-chip-box:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(20,163,224,0.15); }
.email-chip-list { display: flex; flex-wrap: wrap; gap: 6px; }
.email-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: #eef6fc; color: var(--navy); border-radius: 20px;
  padding: 4px 4px 4px 12px; font-size: 0.85rem; font-weight: 600; max-width: 100%;
}
.email-chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.email-chip-remove {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 18px; height: 18px; border-radius: 50%; border: none;
  background: rgba(12,26,69,0.12); color: var(--navy); font-size: 0.85rem;
  line-height: 1; cursor: pointer; padding: 0;
}
.email-chip-remove:hover { background: var(--red); color: #fff; }
.email-chip-entry {
  flex: 1 1 160px; min-width: 160px; border: none; outline: none;
  font-size: 1rem; padding: 6px 4px; background: transparent; color: var(--text);
}

/* ===========================================================================
   Dashboard layout (sidebar + top bar + content) - used only under /Dashboard
   =========================================================================== */
.dash-shell { display: flex; min-height: 100vh; }
.dash-sidebar {
  width: 230px; background: var(--navy); color: #fff; flex-shrink: 0;
  display: flex; flex-direction: column;
}
.dash-sidebar .brand-block { padding: 26px 20px; border-bottom: 1px solid rgba(255,255,255,0.12); text-align: center; }
.dash-sidebar .brand-block .brand-logo { width: 100%; max-width: 210px; height: auto; display: block; margin: 0 auto 16px; }
.dash-sidebar .brand-block .brand-fyp { color: #fff; font-weight: 800; font-style: italic; font-size: 1.6rem; line-height: 1.1; }
.dash-sidebar .brand-block .brand-tagline { color: rgba(255,255,255,0.75); font-size: 0.85rem; margin-top: 2px; }
.dash-nav { padding: 14px 10px; flex: 1; }
.dash-nav a {
  display: flex; align-items: center; gap: 10px; color: rgba(255,255,255,0.85);
  padding: 11px 14px; border-radius: 8px; font-size: 0.92rem; margin-bottom: 2px; text-decoration: none;
}
.dash-nav a:hover { background: rgba(255,255,255,0.08); }
.dash-nav a.active { background: var(--blue); color: #fff; font-weight: 600; }
.dash-nav .nav-icon { flex-shrink: 0; }

.dash-main { flex: 1; min-width: 0; }
.dash-topbar {
  background: #fff; border-bottom: 1px solid var(--border);
  padding: 14px 28px; display: flex; align-items: center; justify-content: space-between;
}
.dash-topbar .crumbs { color: var(--text-muted); font-size: 0.92rem; }
.dash-topbar .crumbs a { color: var(--blue); }
.dash-topbar .user { display: flex; align-items: center; gap: 10px; }
.dash-topbar .user .avatar {
  width: 34px; height: 34px; border-radius: 50%; background: var(--navy); color: #fff;
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.85rem;
}
.dash-topbar .user .who strong { display: block; font-size: 0.88rem; }
.dash-topbar .user .who span { display: block; font-size: 0.72rem; color: var(--text-muted); }

.dash-content { padding: 26px 28px 60px; }
.dash-content h1 { font-size: 1.7rem; color: var(--navy); margin: 0 0 18px; }

.site-picker select.form-control { max-width: 360px; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin: 18px 0; }
.stat-card { background: #fff; border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; box-shadow: var(--shadow); }
.stat-card .label { color: var(--text-muted); font-size: 0.72rem; letter-spacing: 0.05em; text-transform: uppercase; }
.stat-card .value { font-size: 1.9rem; font-weight: 700; margin: 6px 0 2px; color: var(--navy); }
.stat-card .sub { font-size: 0.8rem; color: var(--text-muted); }
.stat-card .sub.critical { color: var(--red); font-weight: 600; }
.stat-card .sub.good { color: var(--green); font-weight: 600; }

.grid-2col { display: grid; grid-template-columns: 2fr 1fr; gap: 18px; align-items: start; }
@media (max-width: 980px) { .grid-2col { grid-template-columns: 1fr; } }
/* Without this, a grid item's default min-width:auto lets a wide table
   deep inside it (e.g. the Reported issues table) push the whole column -
   and with it the whole page - wider than the screen, instead of the
   table's own overflow-x:auto scrolling in place like it's meant to. */
.grid-2col > * { min-width: 0; }

.panel { background: #fff; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px; margin-bottom: 18px; }
.panel h2 { font-size: 1.05rem; margin: 0 0 14px; color: var(--navy); }

/* ---------- Modal (native <dialog> - see Notifications.cshtml/site.js) ----------
   Using <dialog> rather than a hand-rolled overlay div gets Esc-to-close,
   a free ::backdrop, and focus containment for nothing - just needs
   opening/closing, which site.js does via showModal()/close(). */
dialog.modal-dialog {
  border: none;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px;
  width: min(560px, 92vw);
  max-height: 88vh;
  overflow-y: auto;
}
dialog.modal-dialog::backdrop { background: rgba(12, 26, 69, 0.55); }
/* Notifications.cshtml's groups popup holds a list AND (sometimes) a form
   at once, so it wants more room than the standard rule popup. */
dialog.modal-dialog.modal-wide { width: min(640px, 94vw); }
.modal-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 4px; }
.modal-header h2 { margin: 0; }
.modal-close { font-size: 1.6rem; line-height: 1; color: var(--text-muted); padding: 2px 6px; border-radius: 6px; }
.modal-close:hover { color: var(--navy); background: var(--border); }

/* Pairs up the optional area/asset/client/site filter dropdowns two-per-row
   inside the Add/Edit rule modals, instead of one long single-column stack -
   this is what actually keeps the modal short despite having this many
   optional fields. */
.field-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
@media (max-width: 560px) { .field-grid-2 { grid-template-columns: 1fr; } }

.filter-row { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
.filter-row .tab {
  padding: 7px 14px; border-radius: 7px; border: 1px solid var(--border); background: #fff;
  font-size: 0.85rem; cursor: pointer; color: var(--text);
}
.filter-row .tab.active { background: var(--blue); color: #fff; border-color: var(--blue); }
.filter-row input.search { margin-left: auto; padding: 7px 12px; border: 1px solid var(--border); border-radius: 7px; min-width: 200px; }

table.issues-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
table.issues-table th { text-align: left; color: var(--text-muted); font-weight: 600; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; padding: 8px 10px; border-bottom: 1px solid var(--border); }
table.issues-table td { padding: 11px 10px; border-bottom: 1px solid var(--border); vertical-align: middle; }
table.issues-table tr:last-child td { border-bottom: none; }
table.issues-table tr.row-link { cursor: pointer; }
table.issues-table tr.row-link:hover { background: #f8fafd; }
/* Keeps the column headings visible while the body scrolls (used with
   .table-scroll below) - harmless no-op on tables that aren't inside a
   scrolling container. */
table.issues-table thead th { position: sticky; top: 0; background: var(--card); }

/* A fixed-height, independently-scrolling wrapper for a long table, so the
   rest of the page (stat cards, other panels) stays put while you scroll
   through the list itself - used on the Overview page's "Reported issues"
   panel. */
.table-scroll { max-height: 300px; overflow-y: auto; overflow-x: auto; }

/* The mobile-only stacked-card alternative to a wide table (used on the
   Overview and Issues pages) - see the @media (max-width: 900px) rules
   below for why, and where it's switched on. Hidden by default (desktop
   shows the table instead). */
.issues-cards { display: none; }
.issue-card-bottom { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

/* A desktop-only wide table's wrapper, paired with .issues-cards above -
   same purpose as .table-scroll but without its height cap/vertical
   scroll (used on the Issues page, which is a full page rather than a
   compact Overview panel, so it can just be as tall as it needs to be). */
.wide-table-wrap { overflow-x: auto; }

/* Built by paginateCards() in site.js as a sibling right after .issues-cards,
   not a child of it - so it needs its own "mobile only" rule rather than
   inheriting .issues-cards' hidden-on-desktop default. */
.cards-pagination { display: none; align-items: center; justify-content: center; gap: 14px; margin-top: 14px; font-size: 0.85rem; color: var(--text-muted); }
.cards-pagination button { padding: 7px 14px; font-size: 0.85rem; }
.cards-pagination button:disabled { opacity: 0.45; cursor: default; }

.priority-dot { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; font-size: 0.85rem; }
.priority-dot::before { content: ""; width: 8px; height: 8px; border-radius: 50%; }
.priority-Critical::before { background: var(--red); } .priority-Critical { color: var(--red); }
.priority-High::before { background: var(--orange); } .priority-High { color: var(--orange); }
.priority-Medium::before { background: var(--yellow); } .priority-Medium { color: #9a7c00; }
.priority-Low::before { background: var(--green); } .priority-Low { color: var(--green); }
.priority-None::before { background: #b7bdcb; } .priority-None { color: var(--text-muted); }

.status-badge { display: inline-block; padding: 4px 11px; border-radius: 20px; font-size: 0.78rem; font-weight: 600; }
.status-Open { background: #fdecec; color: #b3221e; }
.status-Assigned { background: #e7f0fe; color: #1a56c4; }
.status-InProgress { background: #e7f0fe; color: #1a56c4; }
.status-Resolved { background: #e6f7ee; color: #1c7a45; }

/* Marks an Area/Asset that was typed in as "Other (not listed)" on the
   report form rather than picked from the real, registered list. */
.badge-other { display: inline-block; margin-left: 6px; padding: 2px 8px; border-radius: 12px; font-size: 0.7rem; font-weight: 600; background: #fff3e0; color: #b06a00; vertical-align: middle; }

.area-bars .bar-row { margin-bottom: 12px; }
.area-bars .bar-row .top { display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 5px; }
.area-bars .bar-track { background: #eef1f7; border-radius: 6px; height: 8px; overflow: hidden; }
.area-bars .bar-fill { background: var(--blue); height: 100%; border-radius: 6px; }

.donut-wrap { display: flex; align-items: center; gap: 18px; }
.donut { width: 110px; height: 110px; border-radius: 50%; flex-shrink: 0; }
.legend { list-style: none; margin: 0; padding: 0; font-size: 0.85rem; }
.legend li { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.legend .swatch { width: 10px; height: 10px; border-radius: 50%; }
.legend .count { margin-left: auto; font-weight: 600; }

.bulk-actions { display: flex; align-items: center; gap: 14px; padding: 10px 14px; margin-bottom: 12px; background: var(--card); border: 1px solid var(--blue); border-radius: 8px; }
.bulk-actions .btn-blue-outline, .bulk-actions .btn-secondary { padding: 7px 14px; font-size: 0.85rem; }

.btn-row { display: flex; gap: 10px; }
.btn-blue-outline { padding: 9px 16px; border-radius: 8px; border: 1px solid var(--blue); color: var(--blue); background: #fff; font-weight: 600; cursor: pointer; }
.btn-blue-fill { padding: 9px 16px; border-radius: 8px; border: none; color: #fff; background: var(--blue); font-weight: 600; cursor: pointer; }

.simple-list { list-style: none; margin: 0; padding: 0; }
.simple-list li { display: flex; justify-content: space-between; align-items: center; padding: 12px 4px; border-bottom: 1px solid var(--border); }
.simple-list li:last-child { border-bottom: none; }

.login-shell { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: var(--bg); }
.login-card { width: 380px; }
.qr-box { text-align: center; padding: 14px; border: 1px solid var(--border); border-radius: 10px; background: #fafbfd; }
.qr-box img { width: 160px; height: 160px; }

/* ===========================================================================
   Dashboard responsiveness. Desktop behaviour (sidebar always visible,
   generous padding) is the default above - everything below only changes
   things on a narrow screen, via max-width media queries.
   =========================================================================== */

/* The hamburger button and the dark overlay behind the slide-out sidebar
   only exist for the mobile drawer - hidden entirely on desktop, where the
   sidebar is always visible and there's nothing to toggle. */
.sidebar-toggle {
  display: none; flex-direction: column; justify-content: center; gap: 4px;
  width: 34px; height: 34px; padding: 6px; margin-right: 4px;
  background: none; border: none; cursor: pointer; flex-shrink: 0;
}
.sidebar-toggle span { display: block; height: 2px; width: 100%; background: var(--navy); border-radius: 2px; transition: transform 0.2s ease, opacity 0.2s ease; }
.sidebar-toggle.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.sidebar-toggle.open span:nth-child(2) { opacity: 0; }
.sidebar-toggle.open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.sidebar-overlay { display: none; position: fixed; inset: 0; background: rgba(12,26,69,0.5); z-index: 999; }

@media (max-width: 900px) {
  .sidebar-toggle { display: flex; }

  /* The sidebar becomes an off-canvas drawer: fixed in place, slid fully
     out of view by default, and slid in over the page (not squeezing it)
     when opened - see site.js for the open/close toggle. */
  .dash-sidebar {
    position: fixed; top: 0; left: 0; height: 100vh; width: 250px; z-index: 1000;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
  }
  .dash-sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.3); }
  .sidebar-overlay.open { display: block; }

  .dash-topbar { padding: 10px 14px; gap: 8px; flex-wrap: wrap; }
  .dash-topbar .crumbs { font-size: 0.82rem; }
  .dash-topbar .user { gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
  .dash-topbar .user .who strong { font-size: 0.82rem; }
  .dash-topbar .user .who span { font-size: 0.68rem; }

  .dash-content { padding: 16px 14px 40px; }
  .dash-content h1 { font-size: 1.4rem; }

  /* A label/value or name/actions pair stacked on top of each other reads
     far better on a narrow screen than being squeezed onto one line -
     used for issue Details/Triage fields, the Users list, etc. Any inline
     style="flex-direction:..." set directly on a specific <li> (a few of
     these are already stacked deliberately) still wins over this. */
  .simple-list li { flex-direction: column; align-items: flex-start; gap: 8px; }
  .simple-list li > span:last-child { flex-wrap: wrap; width: 100%; }

  /* Kept as one row of 4, same as desktop, rather than wrapping to 2x2 -
     each card just shrinks (smaller padding/text, labels allowed to wrap
     to 2 lines) instead of taking the full screen width per card. */
  .stat-grid { grid-template-columns: repeat(4, 1fr); gap: 6px; }
  .stat-card { padding: 10px 8px; }
  .stat-card .label { font-size: 0.6rem; letter-spacing: 0.02em; }
  .stat-card .value { font-size: 1.2rem; margin: 4px 0 2px; }
  .stat-card .sub { font-size: 0.65rem; line-height: 1.25; }

  /* A many-column table (the Overview page's Reported issues, and the
     Issues page's active-issues list) would need to either scroll sideways
     or shrink its text past readability on a phone - instead, the table
     (.table-scroll / .wide-table-wrap) is swapped out for .issues-cards (a
     stacked card per issue, same information, no horizontal scroll) via
     the pair of display rules right below. Both are always rendered by the
     page; only one is ever visible at a time. */
  .table-scroll, .wide-table-wrap { display: none; }
  .issues-cards { display: block; }
  .cards-pagination { display: flex; }
  .issue-card { padding: 12px 2px; border-bottom: 1px solid var(--border); cursor: pointer; }
  .issue-card:last-child { border-bottom: none; }
  .issue-card-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 6px; }
  .issue-card-desc { font-weight: 600; margin-bottom: 4px; }
  .issue-card-meta { font-size: 0.82rem; color: var(--text-muted); margin-bottom: 4px; }
  .issue-card-time { font-size: 0.78rem; color: var(--text-muted); }
  .issue-card .row-checkbox { width: 18px; height: 18px; flex-shrink: 0; }

  .site-picker select.form-control { max-width: 100%; }
  .donut-wrap { flex-wrap: wrap; }
}

@media (max-width: 480px) {
  /* .dash-topbar already wraps its three children (menu button, crumbs,
     user block) independently via flex-wrap - on the narrowest phones the
     user block (name/role/Change password/logout) just drops to its own
     row rather than anything being hidden or cut off. */
}

/* ============================================================
   Utility classes (24 Aug 2026 - CSP hardening pass)
   Small, atomic, freely-combinable classes replacing what used to be
   one-off style="..." attributes scattered across every page. Part of
   removing 'unsafe-inline' from the Content-Security-Policy - a style=""
   attribute is exactly what CSP's style-src blocks once that's gone,
   whether it came from server-rendered HTML or a JS-built innerHTML
   string (site.js used to build a few too). Combine several of these on
   one element instead of writing a new inline style - e.g.
   class="flex-row gap-8 items-center" instead of
   style="display:flex;gap:8px;align-items:center". Each one maps to
   exactly the CSS property/value it replaces, so combining them
   reproduces the original inline style exactly - nothing here changes
   what anything actually looks like.
   ============================================================ */
/* !important on the layout/alignment ones below is deliberate, not a hack:
   several existing component rules elsewhere in this file target compound
   selectors like ".simple-list li" (a class + an element = higher CSS
   specificity than any single utility class), so without this, applying
   e.g. class="flex-col items-start" to an <li> inside .simple-list would
   silently lose to that rule's own align-items:center - exactly the bug
   that happened with the issue-detail "Description" row and all 3
   edit-history lists. A utility class only works as an override if it's
   guaranteed to win once applied - that's the whole point of it existing -
   so these get !important rather than hoping every future component rule
   stays lower-specificity than a single class. */
.flex-row { display: flex !important; }
.flex-col { display: flex !important; flex-direction: column !important; }
.flex-wrap { flex-wrap: wrap !important; }
.items-center { align-items: center !important; }
.items-start { align-items: flex-start !important; }
.justify-between { justify-content: space-between !important; }
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
.flex-none { flex: 0 0 auto; }

.gap-2 { gap: 2px; }
.gap-4 { gap: 4px; }
.gap-6 { gap: 6px; }
.gap-8 { gap: 8px; }
.gap-10 { gap: 10px; }

.min-w-100 { min-width: 100px; }
.min-w-120 { min-width: 120px; }
.min-w-130 { min-width: 130px; }
.max-w-160 { max-width: 160px; }
.max-w-170 { max-width: 170px; }
.max-w-420 { max-width: 420px; }
.max-h-80 { max-height: 80px; }
.w-auto { width: auto; }
.w-full { width: 100%; }

.mt-0 { margin-top: 0; }
.mt-n6 { margin-top: -6px; }
.mt-4 { margin-top: 4px; }
.mt-6 { margin-top: 6px; }
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-14 { margin-top: 14px; }
.mt-18 { margin-top: 18px; }
.mt-24 { margin-top: 24px; }
.mt-26 { margin-top: 26px; }
.mb-0 { margin-bottom: 0; }
.mb-8 { margin-bottom: 8px; }
.mb-12 { margin-bottom: 12px; }
.mb-14 { margin-bottom: 14px; }
.m-0 { margin: 0; }

.text-muted { color: var(--text-muted); }
.text-danger { color: var(--red); }
.text-blue { color: var(--blue); }
.text-navy { color: var(--navy); }
.border-danger { border-color: var(--red); }
.text-xxs { font-size: 0.72rem; }
.text-xs { font-size: 0.78rem; }
.text-sm { font-size: 0.82rem; }
.text-sm2 { font-size: 0.85rem; }
.text-sm3 { font-size: 0.88rem; }
.text-sm4 { font-size: 0.9rem; }
.text-md { font-size: 0.95rem; }
.text-lg { font-size: 1.5rem; }
.font-bold { font-weight: 700; }
.font-semibold { font-weight: 600; }
.font-medium { font-weight: 500; }
.text-inherit { color: inherit; }
.no-underline { text-decoration: none; }
.text-right { text-align: right !important; }
.text-left { text-align: left !important; }

/* !important here too, same reasoning as flex-row/flex-col/items-* above -
   .d-none in particular is used for real show/hide logic (e.g. the bulk
   actions bar), not just cosmetics, so it needs to reliably win rather than
   depending on staying later in this file than every component rule that
   happens to also set display (currently true for .bulk-actions only by
   luck of ordering - not something to keep relying on). */
.d-none { display: none !important; }
.d-inline { display: inline !important; }
.d-block { display: block !important; }
.overflow-x-auto { overflow-x: auto; }

/* Small outline button variants (delete/remove-style actions), and a
   couple of one-off component styles that aren't worth genericizing. */
.btn-xs { padding: 2px 8px; }
.btn-sm { padding: 4px 10px; }
.btn-sm2 { padding: 9px 12px; }
.btn-sm3 { padding: 6px 12px; }
.text-sm5 { font-size: 0.8rem; }
.job-photo-thumb { width: 90px; height: 90px; object-fit: cover; border-radius: 6px; display: block; }
.report-photo-thumb { max-width: 100%; border-radius: 8px; }
.checkbox-icon { width: 16px; height: 16px; }
.btn-xs2 { padding: 2px 4px; }
.text-sm6 { font-size: 0.75rem; }
.font-normal { font-weight: 400; }
.mt-n10 { margin-top: -10px; }
.mb-6 { margin-bottom: 6px; }
.ml-6 { margin-left: 6px; }
.w-90 { width: 90px; }
.gap-12 { gap: 12px; }
.fieldset-reset { border: none; padding: 0; margin: 0; }
.job-visit-row-box { border: 1px solid var(--border); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
